PrivateoverlayPrivateconfigsPrivateobjectPrivatevideoVideo overlays by name, so visibility can start/stop playback.
PrivatematteStacked-alpha-matte compositors, for the VISIBLE clips only.
A compositor owns a WebGL context, and a browser caps how many may be live
at once — Chrome then evicts the OLDEST, which is the scene's own renderer.
A tour with more stacked clips than that cap (nineteen turntables, against
a cap around sixteen) therefore lost the renderer partway through when each
clip kept a context for the session. So a compositor is built when its clip
is shown and destroyed, canvas and all, once its hide transition completes:
see showMatte and scheduleMatteHide. The canvas cannot be
kept and merely emptied — WEBGL_lose_context is not reversible by asking
that same canvas for a context again, which silently leaves a dead one behind.
PrivatematteWhat showMatte needs to rebuild a compositor for a clip.
PrivatematteFade-out timers that hand matte contexts back after the canvas disappears.
PrivatematteClips whose compositor could not read them: show the raw clip, never retry.
PrivatebasePrivate OptionalreadPrivateboundPrivategloballyWhether overlays are globally hidden by the user toggle (U key)
PrivatetransitArrival gate for story flights (Waypoint.reveal = "on_arrival"): while
it returns true, a dimension-bound overlay that is not already showing
stays hidden. null = no gate.
PrivateshownNon-hover overlays currently shown.
Privatesettledshown as it was BEFORE the current dimension position was reached —
what the arrival gate keeps as is. Snapshotted when updateVisibility()
first sees a new position, so a second pass at the same position (the app
re-runs it once the driver has decided the gate) still knows which
overlays were already on screen and which only just appeared.
PrivatesettledPrivatehoverHover overlays that update from GPU picking results.
Private_Cache last hover result to skip redundant DOM updates.
Private_Private_Private_Private_Load overlays from parsed configs and render them.
Array of overlay configurations from zarr
Base URL of the zarr store (for directory image fetching)
OptionalreadFile: OverlayFileReader
Reader for opaque files held inside the active store
Toggle global overlay visibility.
True if there is at least one hover overlay that could currently
display a pick result. PickingSystem consults this via its
shouldPick predicate to avoid paying picking cost when no
tooltip would be displayed.
Show all overlays (subject to dimension filtering).
Hide all overlays globally.
Install (or clear, with null) the arrival gate. While gate() is true —
the waypoint driver is flying to a waypoint authored reveal: "on_arrival"
— updateVisibility() keeps a dimension-bound overlay that would NEWLY
appear hidden, hides one that stops matching at once (leaving is instant),
and leaves overlays without a visible_range alone. The app re-runs
updateVisibility() when the flight resolves so the held overlays fade in
together, on arrival.
PrivateresolveWhether config shows now: the dimension rule, the global toggle, and the
arrival gate (which only ever withholds a dimension-bound overlay that is
not already on screen). Keeps shown in step with the answer.
PrivaterefreshRe-snapshot settled when the dimension position changed since the last
pass. The overlay manager and the waypoint driver both listen to the dims
manager in unspecified order: if this pass runs first it may show the new
story's captions before the driver closes the gate; the app then re-runs
it at the SAME position, and the snapshot lets that second pass withhold
exactly the overlays the first one had just revealed.
Update overlay visibility based on current dimension state.
Update hover overlay content from a GPU picking result.
Substitutes template variables ({hover_label}, {hover_key}, {hover_image_label}, {hover_node}, {hover_index}) in all hover overlays. Fades out if result is null or has no content.
Pick result with label text, or null to clear
Return all currently visible overlay elements and their configs. Used by the recording panel to composite overlays onto the capture canvas.
Dispose all overlays and clean up listeners.
PrivatereadFor a zipped store, read the overlay's media payload (image or video) so it can be served from a blob URL. Video posters are loaded alongside the video; plain HTTP stores stream both resources by URL instead.
PrivatecreateCreate a DOM element for a single overlay.
PrivatecreateCreate video overlay content: a muted, looping <video> (the only autoplay
a browser permits without a gesture), served from a blob URL for zipped
stores or by URL otherwise. Playback is tied to visibility in
updateVisibility so a hidden turntable does not keep decoding.
OptionalarchivedVideo: Uint8Array<ArrayBufferLike>OptionalarchivedPoster: Uint8Array<ArrayBufferLike>PrivateisWhether the store's base URL is on another origin than the page (blob URLs are not).
PrivateneedsPrivateshowBuild and attach the compositor canvas for a stacked-matte clip that is
becoming visible, and return it started. The <video> stays in the DOM,
decoding, but visually hidden — it is the compositor's frame source — and
the poster sits behind the canvas until the first frame is drawn.
Idempotent: an already-live compositor is returned as it is. A clip that has been abandoned (a tainted cross-origin video, or no WebGL at all) is never rebuilt, so a failing clip is reported once rather than at every story step.
PrivatehideDestroy the compositor of a clip that has gone off screen, canvas and all,
so its WebGL context is handed back (see the note on matteCompositors).
The canvas is DISCARDED rather than kept and reused: a context lost through
WEBGL_lose_context cannot be re-acquired from the same canvas, and asking
it for one again yields a dead context that silently draws nothing.
PrivatescheduleKeep the last composited frame alive until a CSS fade has completed.
PrivatemattePrivateabandonThe compositor could not read the video (a tainted cross-origin clip): drop the canvas and show the raw clip — colour over matte, visible rather than a blank square — and say why, once.
PrivatesetOptionalarchivedVideo: Uint8Array<ArrayBufferLike>OptionalarchivedPoster: Uint8Array<ArrayBufferLike>PrivatesyncStart or stop a video overlay with its visibility (muted play needs no gesture).
PrivateapplyApply common positioning, transitions, and interaction styles.
PrivatecreatePrivatecreateCreate image overlay content.
OptionalarchivedImage: Uint8Array<ArrayBufferLike>PrivatecreatePrivatesanitizeClient-side HTML sanitization against DOM tag and attribute allowlists.
Pass 1 is an attribute ALLOWLIST (see ALLOWED_ATTRS): every attribute not
in the set is removed — this is what drops on* handlers, DOM-clobbering
id/name, ping/srcset/download, data-*, etc. The allowlisted
value-bearing attributes then pass a per-attribute guard (see below):
href/src block javascript:/vbscript:/data: schemes, style
blocks javascript:/vbscript:/expression( plus the CSS escape and
comment syntax (\, /*) that could smuggle those tokens past a
substring check, rel drops an opener token, and target is
restricted to _blank/_self — the last two neutralize reverse
tabnabbing. Pass 2 is a tag allowlist (see ALLOWED_TAGS).
A disallowed tag is unwrapped, not dropped — its children are lifted into its parent — so every element has to be scrubbed whether or not its own tag survives. Skipping the descendants of a disallowed tag hoists them into the output verbatim; that was issue #720.
Invariant: pass 1 scrubs attributes on every element unconditionally, and pass 2 only moves existing nodes and drops the elements it unwraps — it never creates, clones or re-parses one, so nothing can reach the output unscrubbed. An unwrapped element is removed only after its children have been lifted, so nothing still awaiting pass 2 is ever detached: every element left in the snapshot is still connected, and still scrubbed, when pass 2 reaches it. Pass 2 runs outermost-first so each node moves exactly once; bottom-up would re-lift the same payload once per enclosing wrapper.
Note: a nested <template> keeps its payload in a separate .content
fragment that querySelectorAll never sees. It is discarded because
template is not allowlisted — allowlisting it would ship that subtree
unsanitized.
PrivateisCheck if an overlay should be visible given current dimension state.
Manages the screen-space overlay layer (text, image, and HTML overlays) drawn over the WebGL canvas but beneath the UI controls.
Keeps a keyed set of overlay
divs and their OverlayConfigs, applies anchor/blend/font styling, and updates visibility as scene dimensions change (subscribing tosceneDimsManager) so overlays appear only on the slice positions they belong to, with CSS-transition fades.