import { useEffect, useState } from "react"; import { ReplayButton, SEARCH_RESULTS } from "./fakes"; const DEMOS = [ { query: "beach-day_042.jpg", mode: "Filename", description: "Plain text matches file names — the default, instant search. One name, one file.", results: SEARCH_RESULTS.filename, }, { query: "/s golden sunset over water", mode: "Semantic", description: "Describe what's in the picture. Visual embeddings find matches even when filenames say nothing.", results: SEARCH_RESULTS.semantic, }, { query: "/t landscape", mode: "Tags", description: "Search by AI or manual tags. Autocomplete suggests tags as you type.", results: SEARCH_RESULTS.tags, }, ] as const; const TYPE_MS = 55; const HOLD_MS = 2600; /// Types each demo query, shows matching results, advances through all three /// modes once, then stops on the last with a replay control. export function StepSearchDemo() { const [demoIndex, setDemoIndex] = useState(0); const [typed, setTyped] = useState(0); const [finished, setFinished] = useState(false); const demo = DEMOS[demoIndex]; const isLastDemo = demoIndex === DEMOS.length - 1; useEffect(() => { if (finished) return; if (typed < demo.query.length) { const timer = setTimeout(() => setTyped((n) => n + 1), TYPE_MS); return () => clearTimeout(timer); } // Fully typed: hold, then advance — or finish on the last demo. const timer = setTimeout(() => { if (isLastDemo) { setFinished(true); } else { setDemoIndex((i) => i + 1); setTyped(0); } }, HOLD_MS); return () => clearTimeout(timer); }, [typed, demo.query.length, isLastDemo, finished]); const replay = () => { setDemoIndex(0); setTyped(0); setFinished(false); }; const fullyTyped = typed >= demo.query.length; return (

One search bar, three modes — picked by prefix. No prefix searches filenames, /s searches by meaning, /t searches tags.

{/* Mock search bar */}
{demo.query.slice(0, typed)} {!finished ? | : null} {demo.mode}
{/* Results — hidden (not greyed) until the query finishes typing, so it doesn't look like the app is reading the user's mind. Space is held by the invisible tiles so the layout doesn't jump when they appear. Keyed by demoIndex so switching demos remounts the row fresh at opacity-0 instead of fading the new images out from the previous demo's visible state. */}
{demo.results.map((src, i) => (
))}

{demo.description}

{finished ? : null}
); }