"use client"; import { useState, useEffect, useRef, useCallback } from "react"; type Phase = "boot" | "prompt" | "sending" | "done"; const BOOT_LINES = [ { text: "$ cladm --subscribe", delay: 0 }, { text: "Scanning newsletters...", delay: 400, dim: true }, { text: "Found: cladm releases, new tools, project updates", delay: 800, dim: true }, { text: "", delay: 1100 }, { text: "Ready. Enter your email to subscribe.", delay: 1200, accent: true }, ]; export function SubscribeModal() { const [open, setOpen] = useState(false); const [phase, setPhase] = useState("boot"); const [bootIndex, setBotIndex] = useState(0); const [email, setEmail] = useState(""); const [error, setError] = useState(""); const inputRef = useRef(null); // Show modal after 15s or on scroll past 60% useEffect(() => { if (sessionStorage.getItem("cladm-subscribed")) return; const timer = setTimeout(() => setOpen(true), 15000); function onScroll() { const pct = window.scrollY / (document.body.scrollHeight - window.innerHeight); if (pct > 0.6) { setOpen(true); window.removeEventListener("scroll", onScroll); } } window.addEventListener("scroll", onScroll, { passive: true }); return () => { clearTimeout(timer); window.removeEventListener("scroll", onScroll); }; }, []); // Boot sequence useEffect(() => { if (!open || phase !== "boot") return; if (bootIndex >= BOOT_LINES.length) { setPhase("prompt"); return; } const t = setTimeout( () => setBotIndex((i) => i + 1), (BOOT_LINES[bootIndex]?.delay ?? 0) - (bootIndex > 0 ? BOOT_LINES[bootIndex - 1]?.delay ?? 0 : 0) || 300 ); return () => clearTimeout(t); }, [open, phase, bootIndex]); // Focus input when prompt phase starts useEffect(() => { if (phase === "prompt") inputRef.current?.focus(); }, [phase]); const dismiss = useCallback(() => { setOpen(false); setPhase("boot"); setBotIndex(0); setEmail(""); setError(""); }, []); // Esc to close useEffect(() => { if (!open) return; function onKey(e: KeyboardEvent) { if (e.key === "Escape") dismiss(); } window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, [open, dismiss]); async function handleSubmit(e: React.FormEvent) { e.preventDefault(); if (!email.trim()) return; setPhase("sending"); setError(""); try { const res = await fetch("/api/subscribe", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email: email.trim(), project: "cladm" }), }); const data = await res.json(); if (data.ok) { setPhase("done"); sessionStorage.setItem("cladm-subscribed", "1"); } else { setError(data.error || "failed"); setPhase("prompt"); } } catch { setError("network error"); setPhase("prompt"); } } if (!open) return null; return (
e.target === e.currentTarget && dismiss()} > {/* Backdrop */}
{/* Terminal */}
{/* Title bar */}
); }