"use client";
import * as React from "react";
import { MousePointer2, Pause, RotateCcw } from "lucide-react";
import { ChangelogTimeline } from "@/components/ui/changelog-timeline";
import type { ChangelogEntry } from "@/components/ui/changelog-timeline";
const settings = { visibleEntries: 9, liveStatus: true, autoplay: true };
export const CHANGELOG_DEMO_ENTRIES: ChangelogEntry[] = [
{ id: "2026-10-04-smooth-scroll", date: "2026-10-04", category: "improved", title: "Smoother leaderboard scrolling", summary: "A steadier scroll through rankings, country views and profiles. Small refinements make long lists easier to explore." },
{ id: "2026-10-04-cli-submit-speed", date: "2026-10-04", category: "improved", title: "Faster CLI submissions", summary: "The duplicate check joins the scan, with clearer progress from your first file to the final submission." },
{ id: "2026-09-29-sonnet-5-5-pricing", date: "2026-09-29", category: "improved", title: "Updated model cost tracking", summary: "A refreshed cost breakdown keeps model usage, input, output and cache activity together in one view." },
{ id: "2026-09-05-speed", date: "2026-09-05", category: "improved", title: "Quicker leaderboard loading", summary: "Rankings arrive with the page. The globe loads afterward, so the list is ready to explore first." },
{ id: "2026-09-04-badges", date: "2026-09-04", category: "new", title: "A new badge collection", summary: "Explore the collection by rarity, with a small reveal when a new badge joins your profile." },
{ id: "2026-09-04-badge-award", date: "2026-09-04", category: "fixed", title: "More reliable badge unlocks", summary: "Session and streak activity now reaches the badge check together with each submission." },
{ id: "2026-08-06-star", date: "2026-08-06", category: "new", title: "Star the project from your CLI", summary: "A quick project shortcut appears after a sync, with confirmation before you continue." },
{ id: "2026-07-28-globe-flags", date: "2026-07-28", category: "new", title: "Country flags on the globe", summary: "Flags add another way to find a country as you explore the community globe." },
{ id: "2026-07-24-sprint", date: "2026-07-24", category: "new", title: "Season Sprint board", summary: "Follow the monthly ranking and see the distance to the next position." },
];
const CCGATHER_URL = "https://ccgather.com/?utm_source=21st&utm_medium=component&utm_campaign=changelog";
type DemoProps = { visibleEntries?: number; liveStatus?: boolean; autoplay?: boolean };
export default function Demo(props: DemoProps) {
const s = { ...settings, ...props };
const [expanded, setExpanded] = React.useState(null as string | null);
const [pinned, setPinned] = React.useState(null as string | null);
const [paused, setPaused] = React.useState(false);
const [finished, setFinished] = React.useState(false);
const [reduced, setReduced] = React.useState(true);
const [phase, setPhase] = React.useState(0);
const [run, setRun] = React.useState(0);
const [pointer, setPointer] = React.useState({ x: 0, y: 0 });
const [deployedAt] = React.useState(() => new Date(Date.now() - 3 * 60 * 60 * 1000).toISOString());
const stage = React.useRef(null as HTMLDivElement | null);
const table = React.useRef(null as HTMLDivElement | null);
const playing = s.autoplay && !paused && !finished && !reduced;
React.useEffect(() => {
const media = window.matchMedia("(prefers-reduced-motion: reduce)");
const sync = () => setReduced(media.matches);
sync();
media.addEventListener("change", sync);
return () => media.removeEventListener("change", sync);
}, []);
React.useEffect(() => {
if (!playing) return;
const steps: [number, () => void][] = [
[1000, () => { setPhase(1); setExpanded(CHANGELOG_DEMO_ENTRIES[0]!.id); }],
[2200, () => { setPhase(2); setExpanded(CHANGELOG_DEMO_ENTRIES[1]!.id); }],
[3400, () => { setPhase(3); setExpanded(CHANGELOG_DEMO_ENTRIES[2]!.id); }],
[4600, () => { setPhase(4); setExpanded(CHANGELOG_DEMO_ENTRIES[3]!.id); }],
[5800, () => { setPhase(5); setExpanded(null); }],
[6600, () => { setPhase(6); setExpanded(CHANGELOG_DEMO_ENTRIES[0]!.id); setPinned(CHANGELOG_DEMO_ENTRIES[0]!.id); }],
[6900, () => setFinished(true)],
];
const timers = steps.map(([delay, action]) => window.setTimeout(action, delay));
return () => timers.forEach((timer) => window.clearTimeout(timer));
}, [playing, run]);
React.useLayoutEffect(() => {
const frame = stage.current;
const card = table.current;
if (!frame || !card) return;
const update = () => {
const origin = frame.getBoundingClientRect();
const row = Array.from(card.querySelectorAll("[data-entry-trigger]")).find((item) => (item as HTMLElement).dataset.entryTrigger === expanded)?.getBoundingClientRect();
const box = card.getBoundingClientRect();
setPointer({ x: row ? row.right - origin.left - 48 : box.right - origin.left - 30, y: row ? row.top + row.height / 2 - origin.top : box.top - origin.top + 24 });
};
update();
const observer = new ResizeObserver(update);
card.querySelectorAll("[data-entry]").forEach((row) => observer.observe(row));
observer.observe(frame);
return () => observer.disconnect();
}, [expanded, phase]);
const stop = () => setPaused(true);
return (
<main data-changelog-demo data-autoplay={s.autoplay} data-playing={playing} data-phase={phase} className="flex min-h-svh w-full items-center justify-center bg-background px-4 py-3 text-foreground">
<div ref={stage} className="relative flex h-[594px] w-full max-w-[672px] flex-col">
<div onPointerMoveCapture={stop} onPointerDownCapture={stop} onKeyDownCapture={stop}>
<ChangelogTimeline ref={table} entries={CHANGELOG_DEMO_ENTRIES} visibleEntries={s.visibleEntries} expanded={expanded} onExpandedChange={setExpanded} pinned={pinned} onPinnedChange={setPinned}
status={s.liveStatus ? { ref: "main", commit: "c99be8d", deployedAt, note: "Demo build" } : undefined}
historyHref="https://github.com/DHxWhy/CCgather/commits/main" />
</div>
<span data-demo-pointer aria-hidden="true" className="pointer-events-none absolute left-0 top-0 z-10 text-foreground transition-transform duration-200 ease-out motion-reduce:transition-none" style={{ transform: `translate(${pointer.x}px, ${pointer.y}px)`, opacity: phase > 0 && !paused && !reduced ? 1 : 0 }}><MousePointer2 className="size-5 fill-card drop-shadow-sm" strokeWidth={1.5} /></span>
<footer className="mt-auto flex min-h-7 items-center justify-between gap-2 px-1 text-[10px] text-muted-foreground">
<p>Demo snapshot ยท From <a href={CCGATHER_URL} target="_blank" rel="noreferrer" className="rounded underline underline-offset-4 hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring">CCgather</a></p>
{s.autoplay && !reduced && <button type="button" data-demo-playback onClick={() => {
if (playing) stop();
else { setExpanded(null); setPinned(null); setPhase(0); setPaused(false); setFinished(false); setRun((value) => value + 1); }
}} className="flex items-center gap-1.5 rounded px-1 py-1 hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring">{playing ? <Pause aria-hidden="true" className="size-3" /> : <RotateCcw aria-hidden="true" className="size-3" />}{playing ? "Pause demo" : "Replay demo"}</button>}
</footer>
</div>
</main>
);
}