"use client"; import { ChevronDownIcon, GlobeIcon } from "lucide-react"; import { AnimatePresence, motion, useReducedMotion } from "motion/react"; import { useEffect, useId, useRef, useState } from "react"; import { cn } from "@/lib/utils"; // Stays false until mounted so the server render and the first client render match. function useSafeReducedMotion() { const prefers = useReducedMotion(); const [mounted, setMounted] = useState(false); useEffect(() => setMounted(true), []); return mounted && !!prefers; } export interface WorkingSource { title: string; domain: string; } export interface WorkingStep { label: string; duration?: number; sources?: WorkingSource[]; } export interface WorkingOutLoudProps { title?: string; steps?: WorkingStep[]; loop?: boolean; defaultOpen?: boolean; className?: string; } const defaultSteps: WorkingStep[] = [ { label: "Understanding the question", duration: 1400 }, { label: "Searching the web", duration: 3600, sources: [ { title: "Heat pump sizing guide", domain: "northwind.energy" }, { title: "Cold-climate efficiency study", domain: "fjordlabs.org" }, { title: "Installer pricing survey 2026", domain: "hearthly.com" }, { title: "Rebate programs by region", domain: "civicgrid.gov" }, ], }, { label: "Comparing running costs", duration: 2200 }, { label: "Writing the answer", duration: 1800 }, ]; const spring = { type: "spring", stiffness: 380, damping: 32 } as const; const easeOut = [0.23, 1, 0.32, 1] as const; const tints = ["bg-sky-500", "bg-emerald-500", "bg-orange-500", "bg-violet-500", "bg-rose-500"]; export function WorkingOutLoud({ title = "Thinking", steps = defaultSteps, loop = false, defaultOpen = true, className, }: WorkingOutLoudProps) { const reduce = useSafeReducedMotion(); const panelId = useId(); const [open, setOpen] = useState(defaultOpen); const [active, setActive] = useState(0); const [revealed, setRevealed] = useState(0); const [elapsed, setElapsed] = useState(0); const startRef = useRef(0); const done = active >= steps.length; useEffect(() => { if (done) return; if (active === 0 && revealed === 0) startRef.current = performance.now(); const t = setInterval(() => setElapsed(performance.now() - startRef.current), 100); return () => clearInterval(t); }, [done, active, revealed]); useEffect(() => { if (done) { if (!loop) return; const t = setTimeout(() => { setActive(0); setRevealed(0); setElapsed(0); startRef.current = performance.now(); }, 4200); return () => clearTimeout(t); } const step = steps[active]; const duration = step?.duration ?? 1600; const sources = step?.sources ?? []; if (sources.length && revealed < sources.length) { const t = setTimeout(() => setRevealed((r) => r + 1), duration / (sources.length + 1)); return () => clearTimeout(t); } const remaining = sources.length ? duration / (sources.length + 1) : duration; const t = setTimeout(() => { setActive((a) => a + 1); setRevealed(0); }, remaining); return () => clearTimeout(t); }, [active, revealed, done, loop, steps]); const seconds = (elapsed / 1000).toFixed(1); const sourceCount = steps.reduce( (n, s, i) => n + (i < active ? (s.sources?.length ?? 0) : i === active ? revealed : 0), 0, ); return (
{open && (
    {steps.map((step, i) => { const state = i < active ? "done" : i === active ? "active" : "pending"; const shown = i < active ? (step.sources?.length ?? 0) : i === active ? revealed : 0; return (
  1. {i < steps.length - 1 && ( )}

    {step.label}

    {step.sources && step.sources.length > 0 && (
      {step.sources.slice(0, shown).map((src, si) => ( {src.domain[0]} {src.title} {src.domain} ))}
    )}
  2. ); })}
)}
); } function StepMark({ state }: { state: "done" | "active" | "pending" }) { const reduce = useSafeReducedMotion(); return ( {state === "active" && (reduce ? ( ) : ( ))} {state === "done" && ( )} ); } function PulseDot({ done }: { done: boolean }) { const reduce = useSafeReducedMotion(); return ( {!done && !reduce && ( )} ); }