// Hero with animated dot-grid canvas, parallax, floating tech panels function HeroCanvas() { const ref = React.useRef(null); React.useEffect(() => { const canvas = ref.current; if (!canvas) return; const ctx = canvas.getContext('2d'); let w,h,dpr = Math.min(window.devicePixelRatio || 1, 2); let mouse = { x: 0.5, y: 0.4, tx: 0.5, ty: 0.4 }; let raf; function resize() { w = canvas.clientWidth;h = canvas.clientHeight; canvas.width = w * dpr;canvas.height = h * dpr; ctx.setTransform(dpr, 0, 0, dpr, 0, 0); } resize(); window.addEventListener('resize', resize); const onMove = (e) => { const r = canvas.getBoundingClientRect(); mouse.tx = (e.clientX - r.left) / r.width; mouse.ty = (e.clientY - r.top) / r.height; }; window.addEventListener('mousemove', onMove); const spacing = 40; let t = 0; function frame() { mouse.x += (mouse.tx - mouse.x) * 0.06; mouse.y += (mouse.ty - mouse.y) * 0.06; ctx.clearRect(0, 0, w, h); t += 0.012; const cols = Math.ceil(w / spacing) + 1; const rows = Math.ceil(h / spacing) + 1; const mx = mouse.x * w; const my = mouse.y * h; for (let i = 0; i < cols; i++) { for (let j = 0; j < rows; j++) { const x = i * spacing; const y = j * spacing; const dx = x - mx,dy = y - my; const dist = Math.sqrt(dx * dx + dy * dy); const wave = Math.sin(dist * 0.012 - t * 1.6) * 0.5 + 0.5; const falloff = Math.max(0, 1 - dist / 520); const alpha = 0.06 + wave * 0.18 * falloff; const size = 1 + falloff * 1.6; if (falloff > 0.05) { ctx.fillStyle = `rgba(255, 46, 77, ${alpha * 0.8})`; } else { ctx.fillStyle = `rgba(180, 180, 200, ${0.05 + wave * 0.04})`; } ctx.beginPath(); ctx.arc(x, y, size, 0, Math.PI * 2); ctx.fill(); } } ctx.save(); const grad = ctx.createLinearGradient(0, 0, w, h); grad.addColorStop(0, 'rgba(255,46,77,0)'); grad.addColorStop(0.5, 'rgba(255,46,77,0.10)'); grad.addColorStop(1, 'rgba(92,240,255,0)'); ctx.fillStyle = grad; ctx.globalCompositeOperation = 'lighter'; ctx.fillRect(0, 0, w, h); ctx.restore(); raf = requestAnimationFrame(frame); } frame(); return () => { cancelAnimationFrame(raf); window.removeEventListener('resize', resize); window.removeEventListener('mousemove', onMove); }; }, []); return ; } function ScrambleWord({ text, delay = 0 }) { const [out, setOut] = React.useState(text); React.useEffect(() => { const chars = "ABCDEF0123456789#$&*"; let frame = 0; const total = 18; const start = setTimeout(() => { const id = setInterval(() => { frame++; const reveal = Math.floor(frame / total * text.length); const next = text.split('').map((c, i) => { if (c === ' ') return ' '; if (i < reveal) return c; return chars[Math.floor(Math.random() * chars.length)]; }).join(''); setOut(next); if (frame >= total) {clearInterval(id);setOut(text);} }, 38); }, delay); return () => clearTimeout(start); }, [text, delay]); return {out}; } /* ===== HERO SIDE — animated, interactive tech panels ===== */ // Typing code block — cycles through lines being written function TypingCode() { const lines = React.useMemo(() => [ { jsx: <>// 666/ws — production endpoint>, plain: '// 666/ws — production endpoint' }, { jsx: <>export const deploy = async () => {'{'}>, plain: "export const deploy = async () => {" }, { jsx: <> const r = await build({'{'} target: 'edge' {'}'});>, plain: " const r = await build({ target: 'edge' });" }, { jsx: <> await ship(r, 'asia-southeast1');>, plain: " await ship(r, 'asia-southeast1');" }, { jsx: <> return {'{'} ok: true {'}'};>, plain: " return { ok: true };" }, { jsx: <>{'}'};>, plain: "};" }], []); const [step, setStep] = React.useState(0); const [typed, setTyped] = React.useState(0); // chars typed of current line React.useEffect(() => { let cancelled = false; const lineLen = lines[step].plain.length; if (typed < lineLen) { const t = setTimeout(() => !cancelled && setTyped(typed + 1), 18 + Math.random() * 30); return () => {cancelled = true;clearTimeout(t);}; } // line done; pause then advance const t = setTimeout(() => { if (cancelled) return; if (step < lines.length - 1) { setStep(step + 1);setTyped(0); } else { // restart after pause setStep(0);setTyped(0); } }, step === lines.length - 1 ? 2400 : 240); return () => {cancelled = true;clearTimeout(t);}; }, [step, typed, lines]); return ( <> {lines.map((ln, i) => { const fullyDone = i < step; const currentTyping = i === step; const future = i > step; if (future) return