/** * THE generic, embeddable per-platform demo-video widget, in the two placements * hosts actually mount: and . * * SHARED GRAMMAR. A collapsed card (bite-identical hover grammar via * ) that opens a large in-page theater (Radix Dialog * primitives, NOT native fullscreen) showing ONLY the video: a bare 16:9 stage * with the player's own controls + captions. No card chrome, no summary. * - FLOATING pins that card to a bottom corner over the page — left unless * the video data says `position: 'right'`. * - INLINE lays it out in the host's own flow, filling its container at 16:9. * Every overlay-only behaviour drops out with the pinning: no fixed * positioning, no z-layer, no shadow, no appear delay (nothing to stagger * against an already-painted page), no footer fade (no footer to collide * with) and no dismissal (an X is an overlay's "get out of my way"; in a * host's own layout it just leaves a hole, and the cookie is per-platform, * so one dismissal in the corner would silently empty the in-page block). * * ONE ENGINE, TWO PRESETS. below takes `placement` and is * deliberately NOT exported: placement is a constant at every call site, so the * public API is two components that say what they are instead of one that takes * a mode — a reads as a * contradiction, and naming a family after one of its variants is what produced * it. The presets are one line each; everything below the wrapper — the player * mode machine, the audio invariant, the corner map, dismissal — stays shared, * because forking THAT is how the two placements would drift. * * The split pays off in the types: the overlay-only knobs are absent from * `InlineWalkthroughVideoProps` outright, so "floating only" is a compile error * rather than a line of prose each caller has to read. * * All UI lives here in the lib so every platform site AND the react-embedding * example mount the same component; the host supplies only the video data. * * STATE MODEL (read before adding a boolean — the recurring bugs in this file * all came from ad-hoc flags contradicting each other): * `cardMode` — ONE discriminant: 'resume' | 'preview' | 'poster'. Derived, * never stored, so the two players can't both be mounted. * `cardMuted` / `cardPaused` — transport state for whichever card player is * live. Both toggles ALWAYS render in both states; a control * that hides itself on use is a dead end (that bug shipped * twice). Never gate a control's visibility on its own value. * `cardFallback` — autoplay-BLOCKED prompt only. Distinct from `cardMuted` * (user intent); conflating them is what made unmute vanish. * `handoff` — theater→card continuation snapshot. Clearing it UNMOUNTS the * mini player, so never clear it to express "paused". * Layering: the media layer is `pointer-events-none` so a paused/idle player * can never swallow a click; every non-control click reaches the activation * overlay. Corners are exclusive: transport TL, dismiss TR, title+presenter BL. * * Audio invariant (both directions): never two overlapping streams, nor a blip. * - open: pause the hover preview synchronously (pointerdown), force the * surface inactive for the whole open duration, reset the YouTube suspend. * - close: snapshot then pause the theater player (file) / suspend (YouTube). * NOTE: this theater's Content carries no exit animation, so Radix unmounts * it in the same commit and the iframe dies before the suspend postMessage * can run. The suspend path exists for hosts that DO animate the exit; here * the unmount itself is what stops the audio. */ import React from 'react'; /** Wire-shape data for the widget. Kept as the shared contract the hub DAL * re-exports as `PublicWalkthroughVideo & { id }`. `mainVideoUrl`/`youtubeUrl` * stay SEPARATE — YouTube wins when both are set (card matches theater). */ export interface WalkthroughVideoData { /** Row id — used for id-match cookie dismissal (a new video re-shows the * card even after an old one was dismissed). */ id?: string | number; mainVideoUrl?: string | null; youtubeUrl?: string | null; posterUrl?: string | null; /** RELATIVE VTT path (/api/captions/...); embedders prefix their proxy base. */ captionsUrl?: string | null; title?: string | null; presenterAvatarUrl?: string | null; /** Which bottom corner the collapsed card pins to. Admin-controlled per * video; anything other than 'right' (including absent) means left. */ position?: 'left' | 'right' | null; } /** Everything the two placements share — the widget's actual contract. */ interface WalkthroughVideoBaseProps { video: WalkthroughVideoData | null | undefined; open?: boolean; onOpenChange?: (open: boolean) => void; defaultOpen?: boolean; /** With `defaultOpen`: the initial theater starts PAUSED instead of * autoplaying — for deep links (`?walkthrough=1`), where the open has no * user gesture and unrequested audio/motion would be hostile. Applies to * the initial `defaultOpen` session only: it ends when the NEXT open — * gesture-driven (card click) or host-driven (`open` flipping true) — * mounts a fresh theater, which autoplays as usual. */ defaultOpenPaused?: boolean; /** Query-param NAME that deep-links into the theater: when present in * `window.location.search` at first client render, the theater opens * immediately and PAUSED — `defaultOpen + defaultOpenPaused` decided * inside the component, so hosts don't defer their first render to read * the URL. Defaults to `WALKTHROUGH_OPEN_QUERY_PARAM` ('walkthrough'); * pass '' to disable. Presence-based (any value counts), read ONCE at * mount (deep links arrive by full page load, not client navigation). * SSR-safe: the server renders closed, and the theater lives in a portal, * so the hydrated (non-portal) markup is identical either way. */ deepLinkParam?: string; label?: string; className?: string; } /** * : the shared contract plus the knobs that only mean * something for an OVERLAY. Each of these is inert inline — the appear delay * staggers against a page the visitor hasn't seen yet, dismissal is an overlay * affordance, and the footer fade (with the `pathname` that re-queries its * target) exists to keep a pinned card off the page chrome. So they live here, * not in the base. */ export interface FloatingWalkthroughVideoProps extends WalkthroughVideoBaseProps { appearDelayMs?: number; /** Cookie-based dismissal (id-match, mirrors the announcement bar). `false` * disables the X entirely. `storageKey` is the per-platform cookie name. */ dismissal?: { storageKey?: string; } | false; /** Fades the card out while `hideNearSelector` is in view. */ hideNearSelector?: string; /** Route identity from the host (the lib can't observe navigation). Changing * it re-queries the footer IO target. */ pathname?: string; } /** : the shared contract and nothing else — see above. */ export type InlineWalkthroughVideoProps = WalkthroughVideoBaseProps; /** * The widget pinned into a bottom corner over the page — the app-shell / site * placement. Every host that wants "a demo video somewhere on the page, out of * the way" mounts this. */ export declare function FloatingWalkthroughVideo(props: FloatingWalkthroughVideoProps): React.ReactElement; /** * The same widget as an in-flow block, sized by whatever the host puts it in — * for a page that gives the video a slot of its own rather than a corner. * * The two overlay defaults are fixed here rather than left to the caller: an * in-page block has nothing to stagger against (`appearDelayMs={0}`), and a * dismissable one would leave the host's layout with a hole while sharing the * floating card's per-platform cookie (`dismissal={false}`). Both are stated * once here instead of at every call site, where forgetting either is silent. */ export declare function InlineWalkthroughVideo(props: InlineWalkthroughVideoProps): React.ReactElement; export {}; //# sourceMappingURL=walkthrough-video.d.ts.map