"use client"; import { useEffect, useState } from "react"; import { cx, Spinner } from "@/components/ui"; import { exitMs, usePresence } from "@/utils/motion"; /** * Loading veil over the content area (`absolute`: the nearest positioned ancestor is the episode * page's content column, which keeps the same geometry from the data-loading shell to the loaded * page, so the spinner holds its spot through both phases while the header and sidebar stay put). * * - `visible={false}` fades the veil out (with clicks passing through) and then unmounts it, so * the content appears through it instead of popping. * - `appear={false}` is for taking over from the previous phase's veil: it shows at once, without * replaying the fade-in (which would read as a blink) or announcing the status a second time. * A later reopen of the same instance fades in again. Taking over with nothing left to load * (`visible={false}` from the start, e.g. a view with no loading phase of its own) plays the * previous veil's fade-out from here instead of letting it vanish. */ export default function Loading({ visible = true, appear = true, }: { visible?: boolean; appear?: boolean; } = {}) { const presence = usePresence(visible, "--duration-fast", 250); const [handoff, setHandoff] = useState(!appear && !visible); useEffect(() => { if (!handoff) return; const t = window.setTimeout( () => setHandoff(false), exitMs("--duration-fast", 250), ); return () => window.clearTimeout(t); }, [handoff]); const state = handoff && presence === "closed" ? "closing" : presence; const [fresh, setFresh] = useState(appear); if (state === "closed" && !fresh) setFresh(true); if (state === "closed") return null; const closing = state === "closing"; return (
{/* The rotation lives on this div, not on the SVG: a transformed HTML box runs on the compositor, so it keeps spinning while the main thread is busy decoding parquet and building charts. Stepped like the shared Spinner. */}

Loading

Preparing data and videos

); }