Code — how this page is built
Under the hood
The actual source of the components on this page, read straight from the repo. Copy it, read it, or open it on GitHub.
WatchItGrowGameview on GitHub ↗
Accrues open-time to localStorage on a 1s tick; the SVG plant derives stem height (√time), leaf count, and a flower from the accumulated total.
'use client';
import * as React from 'react';
const KEY = 'kaspirius:watch-it-grow:seconds';
function fmtDuration(s: number) {
const h = Math.floor(s / 3600);
const m = Math.floor((s % 3600) / 60);
const sec = s % 60;
return [h ? `${h}h` : '', m ? `${m}m` : '', `${sec}s`].filter(Boolean).join(' ');
}
export function WatchItGrowGame() {
const [total, setTotal] = React.useState(0);
React.useEffect(() => {
let t = Number(localStorage.getItem(KEY)) || 0;
setTotal(t);
const id = window.setInterval(() => {
t += 1;
setTotal(t);
if (t % 5 === 0) localStorage.setItem(KEY, String(t));
}, 1000);
const save = () => localStorage.setItem(KEY, String(t));
window.addEventListener('beforeunload', save);
return () => {
window.clearInterval(id);
save();
window.removeEventListener('beforeunload', save);
};
}, []);
const stemH = Math.min(230, Math.sqrt(total) * 13);
const leaves = Math.min(7, Math.floor(total / 40));
const flower = total > 480;
const baseY = 270;
const topY = baseY - stemH;
return (
<div className="flex flex-col items-center gap-5">
<svg width="220" height="290" viewBox="0 0 220 290" aria-label="a growing plant">
{/* pot */}
<path d="M80 270 L140 270 L132 290 L88 290 Z" fill="#b8772e" />
<rect x="76" y="262" width="68" height="10" rx="2" fill="#a8692a" />
{/* stem */}
{stemH > 4 ? (
<line x1="110" y1={baseY} x2="110" y2={topY} stroke="#6f7d4a" strokeWidth="5" strokeLinecap="round" />
) : null}
{/* leaves */}
{Array.from({ length: leaves }, (_, i) => {
const y = baseY - (stemH * (i + 1)) / (leaves + 1);
const left = i % 2 === 0;
return (
<ellipse
key={i}
cx={left ? 92 : 128}
cy={y}
rx="18"
ry="8"
fill="#6f7d4a"
transform={`rotate(${left ? -25 : 25} ${left ? 92 : 128} ${y})`}
/>
);
})}
{/* flower */}
{flower ? (
<g>
{Array.from({ length: 6 }, (_, i) => {
const a = (i / 6) * Math.PI * 2;
return <circle key={i} cx={110 + Math.cos(a) * 13} cy={topY + Math.sin(a) * 13} r="9" fill="#c2453a" />;
})}
<circle cx="110" cy={topY} r="8" fill="#c9a23f" />
</g>
) : (
stemH > 8 && <circle cx="110" cy={topY} r="5" fill="#6f7d4a" />
)}
</svg>
<div className="text-center">
<div className="font-mono text-sm opacity-75">grown for {fmtDuration(total)}</div>
<div className="mt-1 font-mono text-[11px] uppercase tracking-[0.12em] opacity-45">
{flower ? 'in bloom — leave it open and it keeps living' : 'leave this tab open to grow it'}
</div>
</div>
</div>
);
}