stagebook 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-I2WMGA4Q.cjs +409 -0
- package/dist/chunk-I2WMGA4Q.cjs.map +1 -0
- package/dist/chunk-SSOMQ3BK.js +409 -0
- package/dist/chunk-SSOMQ3BK.js.map +1 -0
- package/dist/components/index.cjs +6084 -0
- package/dist/components/index.cjs.map +1 -0
- package/dist/components/index.d.cts +400 -0
- package/dist/components/index.d.ts +400 -0
- package/dist/components/index.js +6084 -0
- package/dist/components/index.js.map +1 -0
- package/dist/evaluateConditions-DzlC6fez.d.cts +1741 -0
- package/dist/evaluateConditions-DzlC6fez.d.ts +1741 -0
- package/dist/index.cjs +1551 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +5644 -0
- package/dist/index.d.ts +5644 -0
- package/dist/index.js +1551 -0
- package/dist/index.js.map +1 -0
- package/package.json +94 -0
- package/src/styles.css +275 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/StagebookProvider.tsx","../../src/components/Stage.tsx","../../src/components/Element.tsx","../../src/components/form/Separator.tsx","../../src/components/elements/Display.tsx","../../src/components/form/Button.tsx","../../src/components/elements/SubmitButton.tsx","../../src/components/elements/AudioElement.tsx","../../src/components/elements/ImageElement.tsx","../../src/components/elements/KitchenTimer.tsx","../../src/components/elements/TrackedLink.tsx","../../src/components/elements/MediaPlayer.tsx","../../src/components/elements/mediaPlayer/isYouTubeURL.ts","../../src/components/elements/mediaPlayer/parseVTT.ts","../../src/components/elements/mediaPlayer/YouTubePlayer.tsx","../../src/utils/formatTime.ts","../../src/components/elements/mediaPlayer/icons.tsx","../../src/components/elements/mediaPlayer/controls.tsx","../../src/components/playback/PlaybackProvider.tsx","../../src/components/elements/mediaPlayer/waveformCapture.ts","../../src/components/elements/Timeline.tsx","../../src/components/elements/timeline/timelineLayout.ts","../../src/components/elements/timeline/TimeRuler.tsx","../../src/components/elements/timeline/WaveformRenderer.tsx","../../src/components/elements/timeline/TimelineTrack.tsx","../../src/components/elements/timeline/Playhead.tsx","../../src/components/elements/timeline/SelectionOverlay.tsx","../../src/components/elements/timeline/selections.ts","../../src/components/elements/timeline/viewport.ts","../../src/components/elements/timeline/TimelineFooter.tsx","../../src/components/elements/timeline/Minimap.tsx","../../src/components/elements/timeline/HelpPopover.tsx","../../src/components/elements/timeline/selectionsReducer.ts","../../src/components/elements/timeline/keyboardActions.ts","../../src/components/elements/Prompt.tsx","../../src/components/form/Markdown.tsx","../../src/components/form/RadioGroup.tsx","../../src/components/form/CheckboxGroup.tsx","../../src/components/form/TextArea.tsx","../../src/components/form/Slider.tsx","../../src/components/form/ListSorter.tsx","../../src/components/elements/Qualtrics.tsx","../../src/components/form/Loading.tsx","../../src/components/conditions/TimeConditionalRender.tsx","../../src/components/conditions/PositionConditionalRender.tsx","../../src/components/conditions/ConditionsConditionalRender.tsx","../../src/components/conditions/SubmissionConditionalRender.tsx","../../src/components/scroll/ScrollIndicator.tsx","../../src/components/scroll/useScrollAwareness.ts","../../src/components/scroll/scrollUtils.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/unbound-method */\nimport React, { createContext, useContext, useState, useEffect } from \"react\";\nimport type { DiscussionType } from \"../schemas/treatment.js\";\n\n// --------------- StagebookContext Interface ---------------\n\nexport interface StagebookContext {\n // Read state via DSL reference strings\n resolve(reference: string, position?: string): unknown[];\n\n // Write state under a DSL-derived key\n save(key: string, value: unknown, scope?: \"player\" | \"shared\"): void;\n\n // Seconds since current step started\n getElapsedTime(): number;\n\n // Advance to next step\n submit(): void;\n\n // Content resolution — platform handles fetching, caching, retries\n getAssetURL(path: string): string;\n getTextContent(path: string): Promise<string>;\n\n // Identity and progress\n progressLabel: string;\n playerId: string;\n position: number | undefined;\n playerCount: number | undefined;\n isSubmitted: boolean;\n\n // Idle state — components call this to signal when the participant\n // should be allowed to appear idle (e.g., watching a video, on an\n // external link). Platform handles detection and UI.\n setAllowIdle?: (allow: boolean) => void;\n\n // Platform-provided renderers for service-coupled elements\n renderDiscussion?: (config: DiscussionType) => React.ReactNode;\n renderSharedNotepad?: (config: { padName: string }) => React.ReactNode;\n renderTalkMeter?: () => React.ReactNode;\n renderSurvey?: (config: {\n surveyName: string;\n onComplete: (results: unknown) => void;\n }) => React.ReactNode;\n}\n\n// --------------- Context ---------------\n\nconst StagebookReactContext = createContext<StagebookContext | null>(null);\n\n// --------------- Provider ---------------\n\nexport function StagebookProvider({\n value,\n children,\n}: {\n value: StagebookContext;\n children: React.ReactNode;\n}) {\n return (\n <StagebookReactContext.Provider value={value}>\n {children}\n </StagebookReactContext.Provider>\n );\n}\n\n// --------------- Hooks ---------------\n\nexport function useStagebookContext(): StagebookContext {\n const ctx = useContext(StagebookReactContext);\n if (!ctx) {\n throw new Error(\n \"useStagebookContext must be used within a <StagebookProvider>. \" +\n \"Wrap your component tree with <StagebookProvider value={...}>.\",\n );\n }\n return ctx;\n}\n\nexport function useResolve(reference: string, position?: string): unknown[] {\n const { resolve } = useStagebookContext();\n return resolve(reference, position);\n}\n\nexport function useSave(): StagebookContext[\"save\"] {\n const { save } = useStagebookContext();\n return save;\n}\n\nexport function useElapsedTime(): number {\n const { getElapsedTime } = useStagebookContext();\n return getElapsedTime();\n}\n\n// --------------- Content hooks ---------------\n\nexport interface TextContentResult {\n data: string | undefined;\n isLoading: boolean;\n error: Error | undefined;\n}\n\nexport function useTextContent(path: string): TextContentResult {\n const { getTextContent } = useStagebookContext();\n const [data, setData] = useState<string | undefined>(undefined);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<Error | undefined>(undefined);\n\n useEffect(() => {\n let cancelled = false;\n setIsLoading(true);\n setError(undefined);\n\n getTextContent(path)\n .then((text) => {\n if (!cancelled) {\n setData(text);\n setIsLoading(false);\n }\n })\n .catch((err: unknown) => {\n if (!cancelled) {\n setError(err instanceof Error ? err : new Error(String(err)));\n setIsLoading(false);\n }\n });\n\n return () => {\n cancelled = true;\n };\n }, [path, getTextContent]);\n\n return { data, isLoading, error };\n}\n","/* eslint-disable @typescript-eslint/unbound-method */\nimport React, { useRef } from \"react\";\nimport { useStagebookContext } from \"./StagebookProvider.js\";\nimport { Element, type ElementConfig } from \"./Element.js\";\nimport { TimeConditionalRender } from \"./conditions/TimeConditionalRender.js\";\nimport { PositionConditionalRender } from \"./conditions/PositionConditionalRender.js\";\nimport { ConditionsConditionalRender } from \"./conditions/ConditionsConditionalRender.js\";\nimport { SubmissionConditionalRender } from \"./conditions/SubmissionConditionalRender.js\";\nimport { ScrollIndicator } from \"./scroll/ScrollIndicator.js\";\nimport { useScrollAwareness } from \"./scroll/useScrollAwareness.js\";\nimport { PlaybackProvider } from \"./playback/PlaybackProvider.js\";\nimport type { DiscussionType } from \"../schemas/treatment.js\";\nimport type { Condition } from \"./conditions/ConditionsConditionalRender.js\";\n\n// Max-width per element type — wider for surveys/qualtrics/video\nfunction maxWidthForElement(element: ElementConfig): string {\n switch (element.type) {\n case \"survey\":\n case \"qualtrics\":\n return \"64rem\"; // ~1024px\n case \"mediaPlayer\":\n case \"timeline\":\n return \"56rem\"; // ~896px\n default:\n return \"42rem\"; // ~672px\n }\n}\n\nexport interface StageConfig {\n name: string;\n duration?: number;\n elements: ElementConfig[];\n discussion?: DiscussionType;\n}\n\nexport interface StageProps {\n stage: StageConfig;\n onSubmit: () => void;\n}\n\nfunction WrappedElement({\n element,\n onSubmit,\n stageDuration,\n}: {\n element: ElementConfig;\n onSubmit: () => void;\n stageDuration?: number;\n}) {\n const { getElapsedTime, position, resolve } = useStagebookContext();\n\n return (\n <TimeConditionalRender\n displayTime={element.displayTime}\n hideTime={element.hideTime}\n getElapsedTime={getElapsedTime}\n >\n <PositionConditionalRender\n showToPositions={element.showToPositions as number[] | undefined}\n hideFromPositions={element.hideFromPositions as number[] | undefined}\n position={position}\n >\n <ConditionsConditionalRender\n conditions={(element.conditions as Condition[]) ?? []}\n resolve={resolve}\n >\n <div\n data-testid={`element-${element.type}${element.name ? `-${element.name}` : \"\"}`}\n style={{\n margin: \"0 auto\",\n width: \"100%\",\n maxWidth: maxWidthForElement(element),\n padding: \"0.5rem 1rem\",\n }}\n >\n <Element\n element={element}\n onSubmit={onSubmit}\n stageDuration={stageDuration}\n />\n </div>\n </ConditionsConditionalRender>\n </PositionConditionalRender>\n </TimeConditionalRender>\n );\n}\n\nfunction ElementsColumn({\n elements,\n onSubmit,\n stageDuration,\n}: {\n elements: ElementConfig[];\n onSubmit: () => void;\n stageDuration?: number;\n}) {\n return (\n <>\n {elements.map((element, i) => (\n <WrappedElement\n key={element.name ?? `element-${i}`}\n element={element}\n onSubmit={onSubmit}\n stageDuration={stageDuration}\n />\n ))}\n </>\n );\n}\n\n// Check whether the current position should see the discussion\nfunction positionAllowsDiscussion(\n discussion: DiscussionType | undefined,\n position: number | undefined,\n): boolean {\n if (!discussion) return false;\n if (position === undefined || position === null) return false;\n\n // Defensive coercion — host platforms may pass position as a string\n const numPosition =\n typeof position === \"number\" ? position : Number(position);\n if (Number.isNaN(numPosition)) return false;\n\n const show = discussion.showToPositions;\n const hide = discussion.hideFromPositions;\n\n if (show && !show.includes(numPosition)) return false;\n if (hide && hide.includes(numPosition)) return false;\n\n return true;\n}\n\nexport function Stage({ stage, onSubmit }: StageProps) {\n const ctx = useStagebookContext();\n const { isSubmitted, playerCount, position, resolve, renderDiscussion } = ctx;\n\n const showDiscussion = positionAllowsDiscussion(stage.discussion, position);\n\n // Scroll awareness — shows indicator when content overflows\n const discussionContentRef = useRef<HTMLDivElement>(null);\n const singleColumnRef = useRef<HTMLDivElement>(null);\n const { showIndicator: showDiscussionScrollIndicator } =\n useScrollAwareness(discussionContentRef);\n const { showIndicator: showSingleColumnScrollIndicator } =\n useScrollAwareness(singleColumnRef);\n\n const elementsColumn = (\n <ElementsColumn\n elements={stage.elements}\n onSubmit={onSubmit}\n stageDuration={stage.duration}\n />\n );\n\n // Two-column layout: discussion on left, elements on right\n if (showDiscussion && renderDiscussion && stage.discussion) {\n const discussionConditions = stage.discussion.conditions as\n | Condition[]\n | undefined;\n\n const discussionPage = (\n <div\n style={{\n display: \"flex\",\n height: \"100%\",\n width: \"100%\",\n flexDirection: \"row\",\n alignItems: \"stretch\",\n gap: \"1rem\",\n paddingBottom: \"1rem\",\n paddingLeft: \"1.5rem\",\n paddingRight: \"1.5rem\",\n minHeight: \"calc(100vh - 4rem)\",\n }}\n >\n {/* Discussion column */}\n <div\n data-testid=\"discussion\"\n style={{\n position: \"relative\",\n flex: 1,\n minWidth: \"24rem\",\n minHeight: \"16rem\",\n }}\n >\n {renderDiscussion(stage.discussion)}\n </div>\n\n {/* Elements column — scrollable independently */}\n <div\n ref={discussionContentRef}\n data-testid=\"stageContent\"\n style={{\n width: \"40vw\",\n minWidth: \"20rem\",\n maxWidth: \"48rem\",\n overflowY: \"auto\",\n scrollBehavior: \"smooth\",\n alignSelf: \"stretch\",\n }}\n >\n {elementsColumn}\n <ScrollIndicator visible={showDiscussionScrollIndicator} />\n </div>\n </div>\n );\n\n return (\n <PlaybackProvider>\n <SubmissionConditionalRender\n isSubmitted={isSubmitted}\n playerCount={playerCount}\n >\n {discussionConditions && discussionConditions.length > 0 ? (\n <ConditionsConditionalRender\n conditions={discussionConditions}\n resolve={resolve}\n fallback={\n <div\n data-testid=\"stageContent\"\n style={{\n display: \"flex\",\n height: \"100%\",\n width: \"100%\",\n flexDirection: \"column\",\n paddingBottom: \"0.5rem\",\n overflow: \"auto\",\n }}\n >\n {elementsColumn}\n </div>\n }\n >\n {discussionPage}\n </ConditionsConditionalRender>\n ) : (\n discussionPage\n )}\n </SubmissionConditionalRender>\n </PlaybackProvider>\n );\n }\n\n // Single-column layout: elements only\n return (\n <PlaybackProvider>\n <SubmissionConditionalRender\n isSubmitted={isSubmitted}\n playerCount={playerCount}\n >\n <div\n ref={singleColumnRef}\n data-testid=\"stageContent\"\n style={{\n display: \"flex\",\n height: \"100%\",\n width: \"100%\",\n flexDirection: \"column\",\n paddingBottom: \"0.5rem\",\n overflow: \"auto\",\n }}\n >\n {elementsColumn}\n <ScrollIndicator visible={showSingleColumnScrollIndicator} />\n </div>\n </SubmissionConditionalRender>\n </PlaybackProvider>\n );\n}\n","/* eslint-disable @typescript-eslint/unbound-method */\nimport React from \"react\";\nimport { useStagebookContext, useTextContent } from \"./StagebookProvider.js\";\nimport { promptFileSchema } from \"../schemas/promptFile.js\";\nimport { Separator } from \"./form/Separator.js\";\nimport { Display } from \"./elements/Display.js\";\nimport { SubmitButton } from \"./elements/SubmitButton.js\";\nimport { AudioElement } from \"./elements/AudioElement.js\";\nimport { ImageElement } from \"./elements/ImageElement.js\";\nimport { KitchenTimer } from \"./elements/KitchenTimer.js\";\nimport { TrackedLink, type ResolvedParam } from \"./elements/TrackedLink.js\";\nimport { MediaPlayer } from \"./elements/MediaPlayer.js\";\nimport { Timeline } from \"./elements/Timeline.js\";\nimport { Prompt } from \"./elements/Prompt.js\";\nimport { Qualtrics } from \"./elements/Qualtrics.js\";\nimport { Loading } from \"./form/Loading.js\";\n\n// Resolve URL params for TrackedLink using the StagebookProvider's resolve\nfunction useResolvedParams(\n urlParams:\n | Array<{\n key: string;\n value?: unknown;\n reference?: string;\n position?: string;\n }>\n | undefined,\n resolve: (ref: string, pos?: string) => unknown[],\n): ResolvedParam[] {\n if (!urlParams) return [];\n return urlParams.map((param) => {\n if (!param.reference) {\n return {\n key: param.key,\n value:\n param.value == null\n ? \"\"\n : String(param.value as string | number | boolean),\n };\n }\n const values = resolve(param.reference, param.position);\n const picked = values.find((v) => v !== undefined);\n return {\n key: param.key,\n value: picked == null ? \"\" : String(picked as string | number | boolean),\n };\n });\n}\n\nexport interface ElementConfig {\n type: string;\n name?: string;\n file?: string;\n style?: \"\" | \"thin\" | \"regular\" | \"thick\";\n reference?: string;\n position?: string;\n shared?: boolean;\n buttonText?: string;\n url?: string;\n source?: string;\n displayText?: string;\n helperText?: string;\n urlParams?: Array<{\n key: string;\n value?: unknown;\n reference?: string;\n position?: string;\n }>;\n width?: number;\n startTime?: number;\n endTime?: number;\n displayTime?: number;\n hideTime?: number;\n warnTimeRemaining?: number;\n surveyName?: string;\n [key: string]: unknown;\n}\n\nexport interface ElementProps {\n element: ElementConfig;\n onSubmit: () => void;\n stageDuration?: number;\n}\n\nexport function Element({ element, onSubmit, stageDuration }: ElementProps) {\n const ctx = useStagebookContext();\n const {\n resolve,\n save,\n getElapsedTime,\n getAssetURL,\n progressLabel,\n renderSharedNotepad,\n renderDiscussion,\n renderTalkMeter,\n renderSurvey,\n playerId,\n setAllowIdle,\n } = ctx;\n\n // Wrap save to add consistent metadata to every element's saved data\n const wrappedSave = React.useCallback(\n (key: string, value: unknown, scope?: \"player\" | \"shared\") => {\n const enriched =\n value !== null && typeof value === \"object\" && !Array.isArray(value)\n ? {\n ...value,\n step: progressLabel,\n stageTimeElapsed: getElapsedTime(),\n }\n : value;\n save(key, enriched, scope);\n },\n [save, progressLabel, getElapsedTime],\n );\n\n // For prompt elements, load the file content\n const promptFile = element.type === \"prompt\" ? element.file : undefined;\n const {\n data: promptMarkdown,\n isLoading: promptLoading,\n error: promptError,\n } = useTextContent(promptFile ?? \"\");\n\n const resolvedParams = useResolvedParams(\n element.type === \"trackedLink\" ? element.urlParams : undefined,\n resolve,\n );\n\n switch (element.type) {\n case \"audio\":\n return (\n <AudioElement\n src={getAssetURL(element.file ?? \"\")}\n save={wrappedSave}\n name={element.name ?? element.file}\n />\n );\n\n case \"display\": {\n const ref = element.reference ?? `prompt.${element.name}`;\n const values = resolve(ref, element.position);\n return (\n <Display reference={ref} position={element.position} values={values} />\n );\n }\n\n case \"image\":\n return (\n <ImageElement\n src={getAssetURL(element.file ?? \"\")}\n width={element.width}\n />\n );\n\n case \"prompt\": {\n if (promptError) {\n return (\n <p style={{ color: \"#dc2626\", fontSize: \"0.875rem\" }}>\n Error loading prompt: {promptError.message}\n </p>\n );\n }\n if (promptLoading || !promptMarkdown) {\n return <Loading />;\n }\n const parsed = promptFileSchema.safeParse(promptMarkdown);\n if (!parsed.success) {\n return (\n <p\n style={{\n color: \"var(--stagebook-danger, #dc2626)\",\n fontSize: \"0.875rem\",\n }}\n >\n Error parsing prompt: {parsed.error.issues[0]?.message}\n </p>\n );\n }\n const { metadata, body, responseItems } = parsed.data;\n const promptName =\n element.name ?? `${progressLabel}_${metadata.name ?? element.file}`;\n\n // Read current value from state\n const scope = element.shared ? \"shared\" : \"player\";\n const currentValues = resolve(`prompt.${promptName}`, scope);\n const currentValue = currentValues[0];\n\n return (\n <Prompt\n metadata={metadata}\n body={body}\n responseItems={responseItems}\n name={promptName}\n file={element.file}\n shared={element.shared}\n value={currentValue}\n save={wrappedSave}\n resolveURL={getAssetURL}\n renderSharedNotepad={renderSharedNotepad}\n />\n );\n }\n\n case \"separator\":\n return <Separator style={element.style} />;\n\n case \"submitButton\": {\n const buttonName = element.name ?? progressLabel;\n return (\n <SubmitButton\n onSubmit={onSubmit}\n name={buttonName}\n buttonText={element.buttonText}\n save={wrappedSave}\n />\n );\n }\n\n case \"timer\":\n return (\n <KitchenTimer\n startTime={element.startTime ?? element.displayTime ?? 0}\n endTime={element.endTime ?? element.hideTime ?? stageDuration ?? 0}\n warnTimeRemaining={element.warnTimeRemaining}\n getElapsedTime={getElapsedTime}\n />\n );\n\n case \"mediaPlayer\": {\n const rawURL = String(element.url ?? \"\");\n const resolvedURL =\n rawURL.startsWith(\"http://\") || rawURL.startsWith(\"https://\")\n ? rawURL\n : getAssetURL(rawURL);\n const rawCaptions =\n typeof element.captionsFile === \"string\" ? element.captionsFile : null;\n const resolvedCaptionsURL =\n rawCaptions == null\n ? undefined\n : rawCaptions.startsWith(\"http://\") ||\n rawCaptions.startsWith(\"https://\")\n ? rawCaptions\n : getAssetURL(rawCaptions);\n return (\n <MediaPlayer\n name={String(element.name ?? rawURL)}\n url={resolvedURL}\n save={wrappedSave}\n getElapsedTime={getElapsedTime}\n onComplete={onSubmit}\n captionsURL={resolvedCaptionsURL}\n syncToStageTime={element.syncToStageTime as boolean | undefined}\n submitOnComplete={element.submitOnComplete as boolean | undefined}\n playVideo={element.playVideo as boolean | undefined}\n playAudio={element.playAudio as boolean | undefined}\n startAt={element.startAt as number | undefined}\n stopAt={element.stopAt as number | undefined}\n allowScrubOutsideBounds={\n element.allowScrubOutsideBounds as boolean | undefined\n }\n stepDuration={element.stepDuration as number | undefined}\n controls={\n element.controls as\n | {\n playPause?: boolean;\n seek?: boolean;\n step?: boolean;\n speed?: boolean;\n }\n | undefined\n }\n />\n );\n }\n\n case \"timeline\": {\n const timelineName = String(element.name ?? \"\");\n // Read previously saved selections so participants who reload the\n // stage see their existing marks. Matches the form-input convention\n // (Prompt reads `prompt.<name>`, Timeline reads `timeline.<name>`).\n const savedSelections = resolve(`timeline.${timelineName}`)[0];\n return (\n <Timeline\n source={String(element.source ?? \"\")}\n name={timelineName}\n selectionType={\n (element.selectionType as \"range\" | \"point\") ?? \"range\"\n }\n selectionScope={element.selectionScope as \"track\" | \"all\" | undefined}\n multiSelect={element.multiSelect as boolean | undefined}\n showWaveform={element.showWaveform as boolean | undefined}\n trackLabels={element.trackLabels as string[] | undefined}\n initialSelections={savedSelections as unknown[] | undefined}\n save={wrappedSave}\n />\n );\n }\n\n case \"trackedLink\":\n return (\n <TrackedLink\n name={element.name ?? \"\"}\n url={element.url ?? \"\"}\n displayText={element.displayText ?? \"\"}\n helperText={element.helperText}\n resolvedParams={resolvedParams}\n save={wrappedSave}\n getElapsedTime={getElapsedTime}\n progressLabel={progressLabel}\n setAllowIdle={setAllowIdle}\n />\n );\n\n case \"talkMeter\":\n return renderTalkMeter?.() ?? null;\n\n case \"sharedNotepad\":\n return (\n renderSharedNotepad?.({\n padName: element.name ?? \"\",\n }) ?? null\n );\n\n case \"qualtrics\": {\n const qualtricsParams = useResolvedParams(element.urlParams, resolve);\n return (\n <Qualtrics\n url={element.url ?? \"\"}\n resolvedParams={qualtricsParams}\n participantId={playerId}\n save={wrappedSave}\n onComplete={onSubmit}\n />\n );\n }\n\n case \"survey\": {\n const surveyName = element.surveyName ?? \"\";\n const surveyKey = element.name ?? surveyName;\n return (\n renderSurvey?.({\n surveyName,\n onComplete: (results: unknown) => {\n wrappedSave(`survey_${surveyKey}`, results);\n onSubmit();\n },\n }) ?? null\n );\n }\n\n case \"discussion\":\n return renderDiscussion?.(element as never) ?? null;\n\n default:\n console.warn(`Unknown element type: ${element.type}`);\n return null;\n }\n}\n","import React from \"react\";\n\nexport interface SeparatorProps {\n style?: \"\" | \"thin\" | \"regular\" | \"thick\";\n}\n\nexport function Separator({ style = \"\" }: SeparatorProps) {\n return (\n <div>\n {style === \"thin\" && <hr className=\"h-1px my-4 w-full bg-gray-400\" />}\n {(style === \"\" || style === \"regular\") && (\n <hr className=\"h-3px my-4 w-full bg-gray-400\" />\n )}\n {style === \"thick\" && <hr className=\"h-5px my-4 w-full bg-gray-500\" />}\n </div>\n );\n}\n","import React from \"react\";\n\nexport interface DisplayProps {\n reference: string;\n position?: string;\n values: unknown[];\n}\n\n// Inline blockquote style — see Markdown.tsx for the full rationale.\n//\n// Short version: Stagebook is consumed as a library, host CSS resets routinely\n// strip default styling, and inline styles win against everything except\n// !important. Shipping the visual as inline styles guarantees the Display\n// element looks the same on every host without each platform reinventing\n// the styling.\n//\n// The colors ARE overridable: hosts can set --stagebook-blockquote-border and\n// --stagebook-blockquote-bg on a parent element to retune without writing any\n// selector-based CSS.\n//\n// Intentional duplication with Markdown.tsx's blockquote entry — both\n// render <blockquote> and should look identical so a markdown blockquote\n// and a Display element are visually consistent. See issue #33.\nconst blockquoteStyle: React.CSSProperties = {\n maxWidth: \"36rem\",\n wordBreak: \"break-word\",\n padding: \"1rem\",\n margin: \"1rem 0\",\n borderLeftWidth: \"0.25rem\",\n borderLeftStyle: \"solid\",\n borderLeftColor: \"var(--stagebook-blockquote-border, #d1d5db)\",\n background: \"var(--stagebook-blockquote-bg, #f9fafb)\",\n};\n\nexport function Display({ reference, values }: DisplayProps) {\n return (\n <blockquote style={blockquoteStyle} data-reference={reference}>\n {values\n .map((v) => (typeof v === \"string\" ? v : JSON.stringify(v)))\n .join(\"\\n\")}\n </blockquote>\n );\n}\n","import React, { useId } from \"react\";\n\nexport interface ButtonProps {\n children: React.ReactNode;\n onClick?: React.MouseEventHandler<HTMLButtonElement> | null;\n className?: string;\n style?: React.CSSProperties;\n primary?: boolean;\n type?: \"button\" | \"submit\" | \"reset\";\n autoFocus?: boolean;\n disabled?: boolean;\n id?: string;\n \"data-testid\"?: string;\n}\n\nexport function Button({\n children,\n onClick = null,\n className = \"\",\n style = {},\n primary = true,\n type = \"button\",\n autoFocus = false,\n disabled = false,\n id = \"\",\n \"data-testid\": dataTestId,\n}: ButtonProps) {\n const generatedId = useId();\n const buttonId = id || `button${generatedId}`;\n\n const baseStyle: React.CSSProperties = {\n display: \"inline-flex\",\n alignItems: \"center\",\n padding: \"0.5rem 1rem\",\n border: \"1px solid\",\n fontSize: \"0.875rem\",\n fontWeight: 500,\n borderRadius: \"0.375rem\",\n cursor: disabled ? \"not-allowed\" : \"pointer\",\n opacity: disabled ? 0.5 : 1,\n };\n\n const colorStyle: React.CSSProperties = primary\n ? {\n color: \"#fff\",\n backgroundColor: \"var(--stagebook-primary, #3b82f6)\",\n borderColor: \"transparent\",\n boxShadow: \"0 1px 2px 0 rgba(0, 0, 0, 0.05)\",\n }\n : {\n color: \"var(--stagebook-text-secondary, #374151)\",\n backgroundColor: \"#fff\",\n borderColor: \"var(--stagebook-border, #d1d5db)\",\n boxShadow: \"0 1px 2px 0 rgba(0, 0, 0, 0.05)\",\n };\n\n return (\n <button\n type={type}\n onClick={onClick ?? undefined}\n className={className}\n autoFocus={autoFocus}\n style={{ ...baseStyle, ...colorStyle, ...style }}\n id={buttonId}\n data-testid={dataTestId}\n disabled={disabled}\n >\n {children}\n </button>\n );\n}\n","import React from \"react\";\nimport { Button } from \"../form/Button.js\";\n\nexport interface SubmitButtonProps {\n onSubmit: () => void;\n name: string;\n buttonText?: string;\n save: (key: string, value: unknown) => void;\n}\n\nexport function SubmitButton({\n onSubmit,\n name,\n buttonText = \"Next\",\n save,\n}: SubmitButtonProps) {\n const handleClick = () => {\n save(`submitButton_${name}`, {});\n onSubmit();\n };\n\n return (\n <div style={{ marginTop: \"1rem\" }}>\n <Button onClick={handleClick} data-testid=\"submitButton\">\n {buttonText}\n </Button>\n </div>\n );\n}\n","import { useEffect } from \"react\";\n\nexport interface AudioElementProps {\n src: string;\n save?: (key: string, value: unknown) => void;\n name?: string;\n}\n\nexport function AudioElement({ src, save, name }: AudioElementProps) {\n useEffect(() => {\n if (!src) return undefined;\n\n const key = `audio_${name ?? src}`;\n const sound = new Audio(src);\n\n const handleCanPlay = () => {\n sound\n .play()\n .then(() => {\n console.log(`[AudioElement] Playing: ${src}`);\n save?.(key, { event: \"play\", src });\n })\n .catch((err: Error) => {\n console.warn(`[AudioElement] Play failed: ${src}`, err.message);\n save?.(key, { event: \"playFailed\", src, error: err.message });\n });\n };\n\n sound.addEventListener(\"canplaythrough\", handleCanPlay);\n\n return () => {\n sound.removeEventListener(\"canplaythrough\", handleCanPlay);\n sound.pause();\n sound.src = \"\";\n };\n }, [src, save, name]);\n\n return null;\n}\n","import React from \"react\";\n\nexport interface ImageElementProps {\n src: string;\n width?: number;\n}\n\nexport function ImageElement({ src, width }: ImageElementProps) {\n if (!src) return null;\n\n return (\n <div className=\"flex justify-center\">\n <img src={src} alt=\"\" width={width ? `${width}%` : \"100%\"} />\n </div>\n );\n}\n","import React, { useState, useEffect } from \"react\";\n\nexport interface KitchenTimerProps {\n startTime: number;\n endTime: number;\n warnTimeRemaining?: number;\n getElapsedTime: () => number;\n}\n\nexport function KitchenTimer({\n startTime,\n endTime,\n warnTimeRemaining = 10,\n getElapsedTime,\n}: KitchenTimerProps) {\n // Re-render periodically to update the timer display\n const [, setTick] = useState(false);\n\n useEffect(() => {\n const interval = setInterval(() => setTick((prev) => !prev), 1000);\n return () => clearInterval(interval);\n }, []);\n\n const stageElapsed = getElapsedTime();\n const timerDuration = endTime - startTime;\n\n let timerElapsed = 0;\n let timerRemaining = timerDuration;\n\n if (stageElapsed > startTime) {\n timerElapsed = stageElapsed - startTime;\n timerRemaining = endTime - stageElapsed;\n }\n\n if (stageElapsed > endTime) {\n timerElapsed = timerDuration;\n timerRemaining = 0;\n }\n\n const percent = Math.min((timerElapsed / timerDuration) * 100, 100);\n const displayRemaining = new Date(1000 * Math.max(timerRemaining, 0))\n .toISOString()\n .slice(timerRemaining < 3600 ? 14 : 11, 19);\n\n const isWarning = timerRemaining <= warnTimeRemaining;\n const barColor = isWarning\n ? \"var(--stagebook-danger, #ef4444)\"\n : \"var(--stagebook-timer-fill, #60a5fa)\"; // red-500 / blue-400\n\n return (\n <div\n style={{\n margin: \"0.375rem\",\n maxWidth: \"36rem\",\n display: \"flex\",\n alignItems: \"center\",\n gap: \"0.75rem\",\n }}\n data-testid=\"kitchen-timer\"\n data-state={isWarning ? \"warning\" : \"normal\"}\n >\n {/* Progress bar */}\n <div\n style={{\n position: \"relative\",\n flex: 1,\n height: \"1.5rem\",\n backgroundColor: \"var(--stagebook-bg-track, #e5e7eb)\",\n borderRadius: \"9999px\",\n overflow: \"hidden\",\n }}\n >\n <div\n data-testid=\"timer-fill\"\n style={{\n height: \"100%\",\n borderRadius: \"9999px\",\n width: `${percent}%`,\n backgroundColor: barColor,\n transition: \"width 1s linear, background-color 0.3s ease\",\n }}\n />\n </div>\n {/* Time label to the right */}\n <span\n style={{\n fontSize: \"0.875rem\",\n fontWeight: 500,\n color: \"var(--stagebook-text-secondary, #374151)\",\n fontVariantNumeric: \"tabular-nums\",\n whiteSpace: \"nowrap\",\n minWidth: \"3rem\",\n textAlign: \"right\",\n }}\n >\n {displayRemaining}\n </span>\n </div>\n );\n}\n","import React, { useCallback, useEffect, useMemo, useRef } from \"react\";\n\nfunction ExternalLinkIcon() {\n return (\n <svg\n width={16}\n height={16}\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n style={{ flexShrink: 0 }}\n >\n <path d=\"M11.5 2a.75.75 0 0 0 0 1.5h3.19L9.97 8.22a.75.75 0 1 0 1.06 1.06l4.72-4.72v3.19a.75.75 0 0 0 1.5 0V2.75A.75.75 0 0 0 16.5 2h-5z\" />\n <path d=\"M5.25 4A2.25 2.25 0 0 0 3 6.25v8.5A2.25 2.25 0 0 0 5.25 17h8.5A2.25 2.25 0 0 0 16 14.75V11.5a.75.75 0 0 0-1.5 0v3.25c0 .414-.336.75-.75.75h-8.5a.75.75 0 0 1-.75-.75v-8.5c0-.414.336-.75.75-.75H9.5a.75.75 0 0 0 0-1.5H5.25z\" />\n </svg>\n );\n}\n\nexport interface ResolvedParam {\n key: string;\n value: string;\n}\n\nexport interface TrackedLinkProps {\n name: string;\n url: string;\n displayText: string;\n helperText?: string;\n resolvedParams?: ResolvedParam[];\n save: (key: string, value: unknown) => void;\n getElapsedTime: () => number;\n progressLabel: string;\n setAllowIdle?: (allow: boolean) => void;\n}\n\nconst DEFAULT_HELPER_TEXT =\n \"Link opens in a new tab. Return to this tab to complete the study.\";\n\ninterface LinkEvent {\n type: string;\n timestamp: number;\n stage: string;\n stageTimeSeconds: number;\n timeAwaySeconds?: number;\n}\n\ninterface LinkRecord {\n name: string;\n url: string;\n displayText: string;\n events: LinkEvent[];\n totalTimeAwaySeconds: number;\n lastEventType?: string;\n lastTimeAwaySeconds?: number;\n lastUpdated?: number;\n}\n\nexport function TrackedLink({\n name,\n url,\n displayText,\n helperText,\n resolvedParams = [],\n save,\n getElapsedTime,\n progressLabel,\n setAllowIdle,\n}: TrackedLinkProps) {\n const resolvedHelperText = helperText ?? DEFAULT_HELPER_TEXT;\n const awayTrackerRef = useRef<{ startedAt: number; clickAt: number } | null>(\n null,\n );\n const lastClickRef = useRef<number | null>(null);\n const recordRef = useRef<LinkRecord>({\n name,\n url,\n displayText,\n events: [],\n totalTimeAwaySeconds: 0,\n });\n const recordKey = `trackedLink_${name}`;\n\n const buildEvent = useCallback(\n (type: string, extra: Record<string, unknown> = {}): LinkEvent => ({\n type,\n timestamp: Date.now(),\n stage: progressLabel,\n stageTimeSeconds: getElapsedTime(),\n ...extra,\n }),\n [getElapsedTime, progressLabel],\n );\n\n const logEvent = useCallback(\n (type: string, extra?: Record<string, unknown>) => {\n const event = buildEvent(type, extra);\n const prev = recordRef.current;\n const updatedEvents = [...prev.events, event];\n const totalTimeAwaySeconds =\n prev.totalTimeAwaySeconds + (event.timeAwaySeconds ?? 0);\n\n const updated: LinkRecord = {\n ...prev,\n events: updatedEvents,\n lastEventType: event.type,\n lastTimeAwaySeconds: event.timeAwaySeconds ?? prev.lastTimeAwaySeconds,\n totalTimeAwaySeconds,\n lastUpdated: event.timestamp,\n };\n recordRef.current = updated;\n save(recordKey, updated);\n },\n [buildEvent, recordKey, save],\n );\n\n const href = useMemo(() => {\n if (!resolvedParams.length) return url;\n const params = new URLSearchParams();\n resolvedParams.forEach(({ key, value }) => {\n params.append(key, value ?? \"\");\n });\n const queryString = params.toString();\n if (!queryString) return url;\n return url.includes(\"?\")\n ? `${url}&${queryString}`\n : `${url}?${queryString}`;\n }, [resolvedParams, url]);\n\n const handleClick = useCallback(() => {\n lastClickRef.current = Date.now();\n logEvent(\"click\");\n }, [logEvent]);\n\n const handleBlur = useCallback(() => {\n if (awayTrackerRef.current || !lastClickRef.current) return;\n awayTrackerRef.current = {\n startedAt: Date.now(),\n clickAt: lastClickRef.current,\n };\n lastClickRef.current = null;\n setAllowIdle?.(true);\n logEvent(\"blur\");\n }, [logEvent, setAllowIdle]);\n\n const handleFocus = useCallback(() => {\n const awayContext = awayTrackerRef.current;\n if (awayContext) {\n awayTrackerRef.current = null;\n const timeAwaySeconds = (Date.now() - awayContext.startedAt) / 1000;\n logEvent(\"focus\", { timeAwaySeconds });\n } else {\n logEvent(\"focus\");\n }\n setAllowIdle?.(false);\n }, [logEvent, setAllowIdle]);\n\n useEffect(() => {\n window.addEventListener(\"blur\", handleBlur);\n window.addEventListener(\"focus\", handleFocus);\n return () => {\n window.removeEventListener(\"blur\", handleBlur);\n window.removeEventListener(\"focus\", handleFocus);\n };\n }, [handleBlur, handleFocus]);\n\n return (\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: \"0.25rem\" }}>\n <a\n href={href}\n target=\"_blank\"\n rel=\"noreferrer noopener\"\n onClick={handleClick}\n style={{\n display: \"inline-flex\",\n alignItems: \"center\",\n gap: \"0.5rem\",\n color: \"var(--stagebook-primary, #3b82f6)\",\n fontWeight: 600,\n textDecoration: \"none\",\n }}\n >\n <span>{displayText}</span>\n <ExternalLinkIcon />\n </a>\n {resolvedHelperText && (\n <p\n style={{\n fontSize: \"0.75rem\",\n color: \"var(--stagebook-text-muted, #6b7280)\",\n margin: 0,\n }}\n >\n {resolvedHelperText}\n </p>\n )}\n </div>\n );\n}\n","import React, {\n useRef,\n useCallback,\n useEffect,\n useMemo,\n useState,\n} from \"react\";\nimport { isYouTubeURL } from \"./mediaPlayer/isYouTubeURL.js\";\nimport { parseVTT, type CaptionCue } from \"./mediaPlayer/parseVTT.js\";\nimport { YouTubePlayer } from \"./mediaPlayer/YouTubePlayer.js\";\nimport { HTML5Controls, YouTubeControls } from \"./mediaPlayer/controls.js\";\nimport { useRegisterPlayback } from \"../playback/PlaybackProvider.js\";\nimport type { PlaybackHandle } from \"../playback/PlaybackHandle.js\";\nimport { computeWatchedRanges } from \"../../utils/watchedRanges.js\";\nimport {\n computeBucketCount,\n createPeaksArrays,\n accumulatePeaks,\n allBuffersSilent,\n} from \"./mediaPlayer/waveformCapture.js\";\n\nexport interface VideoEvent {\n type: \"play\" | \"pause\" | \"ended\" | \"seek\" | \"speed\" | \"stopAt\";\n videoTime: number;\n stageTimeElapsed: number;\n /** Present on seek events: the position before seeking */\n fromTime?: number;\n /** Present on speed events: the new playback rate */\n playbackRate?: number;\n}\n\ninterface VideoRecord {\n name: string;\n url: string;\n startAt?: number;\n stopAt?: number;\n events: VideoEvent[];\n lastVideoTime: number;\n /** Merged closed intervals [startSeconds, endSeconds] derived from the event log. */\n watchedRanges: [number, number][];\n}\n\nexport interface MediaPlayerProps {\n name: string;\n url: string;\n save: (key: string, value: unknown) => void;\n getElapsedTime: () => number;\n onComplete?: () => void;\n syncToStageTime?: boolean;\n submitOnComplete?: boolean;\n playVideo?: boolean;\n playAudio?: boolean;\n captionsURL?: string;\n startAt?: number;\n stopAt?: number;\n allowScrubOutsideBounds?: boolean;\n stepDuration?: number;\n controls?: {\n playPause?: boolean;\n seek?: boolean;\n step?: boolean;\n speed?: boolean;\n };\n}\n\nconst SPEEDS = [0.5, 0.75, 1, 1.25, 1.5, 2] as const;\n\n/** Reject URLs with dangerous protocols (javascript:, data:, vbscript:, etc.) */\nfunction isSafeURL(url: string): boolean {\n try {\n const parsed = new URL(url, window.location.href);\n return parsed.protocol === \"http:\" || parsed.protocol === \"https:\";\n } catch {\n return false;\n }\n}\n\n// Number of repeated keydown events before entering fast-scrub mode\nconst HOLD_REPEAT_THRESHOLD = 10;\n\nexport function MediaPlayer({\n name,\n url,\n save,\n getElapsedTime,\n onComplete,\n syncToStageTime = false,\n submitOnComplete = false,\n playVideo = true,\n playAudio = true,\n captionsURL,\n startAt,\n stopAt,\n allowScrubOutsideBounds = false,\n stepDuration = 1,\n controls,\n}: MediaPlayerProps) {\n const youtubeVideoId = isYouTubeURL(url);\n const saveKey = `mediaPlayer_${name}`;\n\n // Defense-in-depth: reject dangerous URL protocols. Element.tsx already\n // resolves relative paths via getAssetURL(), so this guards against\n // javascript:, data:, and other non-HTTP schemes reaching a <video> src.\n if (!youtubeVideoId && !isSafeURL(url)) {\n return (\n <div data-testid=\"mediaPlayer\" role=\"alert\">\n Invalid media URL\n </div>\n );\n }\n\n const eventsRef = useRef<VideoEvent[]>([]);\n const videoRef = useRef<HTMLVideoElement>(null);\n\n const [isPaused, setIsPaused] = useState(true);\n const [isHovered, setIsHovered] = useState(false);\n const [currentTime, setCurrentTime] = useState(0);\n const [duration, setDuration] = useState<number>(0);\n const [bufferedEnd, setBufferedEnd] = useState(0);\n const [playbackRate, setPlaybackRate] = useState(1);\n const [cues, setCues] = useState<CaptionCue[]>([]);\n const [captionText, setCaptionText] = useState<string | null>(null);\n const [loadError, setLoadError] = useState<string | null>(null);\n\n // Clear any prior error state when the source changes (e.g. parent\n // swaps to a different clip). Without this the player would stay\n // stuck in the error state forever after a single load failure.\n useEffect(() => {\n setLoadError(null);\n }, [url]);\n\n // YouTube-only: handle registered by YouTubePlayer once the IFrame API is ready\n const [ytHandle, setYtHandle] = useState<PlaybackHandle | null>(null);\n\n // Track whether the video was playing when a scrub drag started, so we can\n // pause on grab and resume on release (records proper play/pause events for\n // watchedRanges without spamming the server during the drag).\n const scrubWasPlayingRef = useRef(false);\n\n // Set to true just before programmatically pausing the video at stopAt, so\n // handlePause can suppress the phantom \"pause\" event and we record \"ended\".\n const stopAtReachedRef = useRef(false);\n\n // ── Waveform capture (lazy — only activated when a Timeline requests it) ──\n const BUCKETS_PER_SECOND = 10;\n const audioCtxRef = useRef<AudioContext | null>(null);\n const analysersRef = useRef<AnalyserNode[]>([]);\n // The lib.d.ts signature for getByteTimeDomainData expects the strict\n // Uint8Array<ArrayBuffer> variant, not Uint8Array<ArrayBufferLike>.\n // Type the array element explicitly so TS doesn't widen it.\n const analyserBuffersRef = useRef<Uint8Array<ArrayBuffer>[]>([]);\n const peaksRef = useRef<Float32Array[]>([]);\n // Render token: bumps every time peaks are mutated, so consumers can\n // re-run effects despite the array reference being stable.\n const peaksVersionRef = useRef(0);\n const [channelCount, setChannelCount] = useState(0);\n const waveformRafRef = useRef<number>(0);\n // Promote waveformActive to state so React effects (e.g., the RAF loop)\n // re-evaluate when capture is requested mid-playback.\n const [waveformActive, setWaveformActive] = useState(false);\n\n const startWaveformCapture = useCallback(() => {\n if (waveformActive) return; // already active\n const v = videoRef.current;\n if (!v) return;\n\n try {\n const ctx = new AudioContext();\n const source = ctx.createMediaElementSource(v);\n const splitter = ctx.createChannelSplitter(source.channelCount || 1);\n source.connect(splitter);\n\n const numChannels = source.channelCount || 1;\n const analysers: AnalyserNode[] = [];\n const buffers: Uint8Array<ArrayBuffer>[] = [];\n const merger = ctx.createChannelMerger(numChannels);\n\n for (let ch = 0; ch < numChannels; ch++) {\n const analyser = ctx.createAnalyser();\n analyser.fftSize = 2048;\n splitter.connect(analyser, ch);\n analyser.connect(merger, 0, ch);\n analysers.push(analyser);\n buffers.push(new Uint8Array(analyser.frequencyBinCount));\n }\n\n merger.connect(ctx.destination);\n\n audioCtxRef.current = ctx;\n analysersRef.current = analysers;\n analyserBuffersRef.current = buffers;\n setChannelCount(numChannels);\n setWaveformActive(true);\n } catch (err) {\n console.warn(\"[MediaPlayer] Waveform capture unavailable:\", err);\n }\n }, [waveformActive]);\n\n // Close the AudioContext on unmount. Chrome enforces a hard limit of ~6\n // simultaneous AudioContexts per tab; without this, an experiment that\n // navigates through several stages each with a Timeline+MediaPlayer would\n // exhaust the limit and silently fail.\n useEffect(() => {\n return () => {\n const ctx = audioCtxRef.current;\n if (ctx && ctx.state !== \"closed\") {\n void ctx.close().catch(() => {\n // Best-effort cleanup; ignore errors during teardown.\n });\n }\n audioCtxRef.current = null;\n analysersRef.current = [];\n analyserBuffersRef.current = [];\n };\n }, []);\n\n // Initialize peaks array when duration or channelCount becomes known\n useEffect(() => {\n if (channelCount === 0 || !Number.isFinite(duration) || duration <= 0)\n return;\n const buckets = computeBucketCount(duration, BUCKETS_PER_SECOND);\n if (buckets === 0) return;\n // Only allocate if not already the right size\n if (\n peaksRef.current.length === channelCount &&\n peaksRef.current[0]?.length === buckets * 2\n )\n return;\n peaksRef.current = createPeaksArrays(channelCount, buckets);\n }, [channelCount, duration]);\n\n // Silent-tainting detector. CORS-tainted media plays normally but the\n // AnalyserNode receives all-zero (centered at 128) samples — the waveform\n // appears as a flat line forever. We watch for the \"still no signal after\n // several seconds of playback\" pattern and log a clear warning so\n // researchers debugging a flat waveform know exactly where to look.\n const TAINTING_DETECTION_THRESHOLD_SEC = 5;\n const taintWarnedRef = useRef(false);\n const captureStartTimeRef = useRef<number | null>(null);\n\n // RAF loop: accumulate peaks while playing. Reads from refs each frame so\n // it picks up newly-allocated peak arrays after duration changes; depends\n // on waveformActive (state) so it re-runs when capture begins mid-playback.\n useEffect(() => {\n if (!waveformActive || isPaused) return;\n\n function tick() {\n const v = videoRef.current;\n if (!v || v.paused) return;\n const analysers = analysersRef.current;\n const buffers = analyserBuffersRef.current;\n const peaks = peaksRef.current;\n\n if (analysers.length === 0 || peaks.length === 0) {\n // Capture not yet wired up — skip and try again next frame.\n waveformRafRef.current = requestAnimationFrame(tick);\n return;\n }\n\n // Initialize capture start time on first tick\n if (captureStartTimeRef.current === null) {\n captureStartTimeRef.current = v.currentTime;\n }\n\n for (let i = 0; i < analysers.length; i++) {\n analysers[i].getByteTimeDomainData(buffers[i]);\n }\n\n accumulatePeaks(peaks, buffers, v.currentTime, BUCKETS_PER_SECOND);\n // Bump the render token so consumers (Timeline → WaveformRenderer)\n // know the in-place mutation needs a redraw.\n peaksVersionRef.current += 1;\n\n // Check for silent tainting: have we played enough but still see zero\n // signal? Almost certainly a CORS issue.\n if (\n !taintWarnedRef.current &&\n v.currentTime - (captureStartTimeRef.current ?? 0) >\n TAINTING_DETECTION_THRESHOLD_SEC &&\n allBuffersSilent(buffers)\n ) {\n taintWarnedRef.current = true;\n console.warn(\n \"[MediaPlayer] Waveform capture is producing all-zero data after \" +\n `${String(TAINTING_DETECTION_THRESHOLD_SEC)}s of playback. This ` +\n \"usually means the media is hosted cross-origin without proper \" +\n \"CORS headers (Access-Control-Allow-Origin), so the AnalyserNode \" +\n \"is silently tainted. Configure the media server to allow CORS, \" +\n \"or host the media same-origin.\",\n );\n }\n\n waveformRafRef.current = requestAnimationFrame(tick);\n }\n\n waveformRafRef.current = requestAnimationFrame(tick);\n return () => cancelAnimationFrame(waveformRafRef.current);\n }, [isPaused, waveformActive]);\n\n // Reset the tainting detector state when capture ends or media changes,\n // so we re-arm for the next capture attempt.\n useEffect(() => {\n if (!waveformActive) {\n taintWarnedRef.current = false;\n captureStartTimeRef.current = null;\n }\n }, [waveformActive, url]);\n\n // Poll YouTube currentTime ~4×/sec while playing (no timeupdate event from IFrame API)\n useEffect(() => {\n if (!ytHandle || isPaused) return;\n const id = setInterval(() => {\n const t = ytHandle.getCurrentTime();\n setCurrentTime(t);\n if (stopAt !== undefined && t >= stopAt) {\n // Signal that the upcoming onPause is from stopAt, not a user action.\n stopAtReachedRef.current = true;\n ytHandle.pause();\n }\n }, 250);\n return () => clearInterval(id);\n }, [ytHandle, isPaused, stopAt]);\n\n // HTML5 PlaybackHandle — exposes this player to sibling components via PlaybackProvider\n const handle = useMemo<PlaybackHandle>(\n () => ({\n play: () => {\n void videoRef.current?.play();\n },\n pause: () => videoRef.current?.pause(),\n seekTo: (s: number) => {\n if (videoRef.current) videoRef.current.currentTime = s;\n },\n getCurrentTime: () => videoRef.current?.currentTime ?? 0,\n getDuration: () => videoRef.current?.duration ?? 0,\n isPaused: () => videoRef.current?.paused ?? true,\n isYouTube: false,\n get channelCount() {\n return channelCount;\n },\n get peaks() {\n return peaksRef.current;\n },\n get peaksVersion() {\n return peaksVersionRef.current;\n },\n requestWaveformCapture: startWaveformCapture,\n }),\n [channelCount, startWaveformCapture], // re-create when channelCount changes so consumers see the update\n );\n // Use the YouTube handle when available, fall back to the HTML5 handle\n useRegisterPlayback(name, ytHandle ?? handle);\n\n // Hold-to-scrub state\n const arrowRepeatCountRef = useRef(0);\n const isFastScrubbing = useRef(false);\n const pausedBeforeScrub = useRef(true);\n const holdTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const holdIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null);\n\n // Fetch and parse captions when captionsURL changes\n useEffect(() => {\n if (!captionsURL) return;\n if (!isSafeURL(captionsURL)) {\n console.warn(\n `[MediaPlayer] Rejected unsafe captions URL: ${captionsURL}`,\n );\n return;\n }\n let cancelled = false;\n fetch(captionsURL)\n .then((r) => r.text())\n .then((text) => {\n if (!cancelled) setCues(parseVTT(text));\n })\n .catch((err: unknown) => {\n console.warn(`[MediaPlayer] Failed to load captions:`, err);\n });\n return () => {\n cancelled = true;\n };\n }, [captionsURL]);\n\n // Seek to startAt on mount (or to elapsedTime+startAt when syncToStageTime)\n useEffect(() => {\n if (!videoRef.current) return;\n if (syncToStageTime) {\n videoRef.current.currentTime = getElapsedTime() + (startAt ?? 0);\n } else if (startAt !== undefined) {\n videoRef.current.currentTime = startAt;\n }\n }, []); // mount-only: reads initial values of getElapsedTime/startAt\n\n const recordEvent = useCallback(\n (\n type: VideoEvent[\"type\"],\n videoTime: number,\n extra?: Partial<Pick<VideoEvent, \"fromTime\" | \"playbackRate\">>,\n ) => {\n const event: VideoEvent = {\n type,\n videoTime,\n stageTimeElapsed: getElapsedTime(),\n ...extra,\n };\n eventsRef.current = [...eventsRef.current, event];\n const record: VideoRecord = {\n name,\n url,\n ...(startAt !== undefined && { startAt }),\n ...(stopAt !== undefined && { stopAt }),\n events: eventsRef.current,\n lastVideoTime: videoTime,\n watchedRanges: computeWatchedRanges(eventsRef.current),\n };\n save(saveKey, record);\n },\n [getElapsedTime, name, url, startAt, stopAt, save, saveKey],\n );\n\n const handlePlay = useCallback(\n (e: React.SyntheticEvent<HTMLVideoElement>) => {\n setIsPaused(false);\n recordEvent(\"play\", e.currentTarget.currentTime);\n },\n [recordEvent],\n );\n\n const handlePause = useCallback(\n (e: React.SyntheticEvent<HTMLVideoElement>) => {\n // Suppress the phantom pause event triggered by our own stopAt enforcement.\n // handleTimeUpdate records \"ended\" and calls onComplete in that path.\n if (stopAtReachedRef.current) {\n stopAtReachedRef.current = false;\n setIsPaused(true);\n return;\n }\n setIsPaused(true);\n recordEvent(\"pause\", e.currentTarget.currentTime);\n },\n [recordEvent],\n );\n\n const handleEnded = useCallback(\n (e: React.SyntheticEvent<HTMLVideoElement>) => {\n setIsPaused(true);\n recordEvent(\"ended\", e.currentTarget.currentTime);\n if (submitOnComplete) {\n onComplete?.();\n }\n },\n [recordEvent, submitOnComplete, onComplete],\n );\n\n const handleLoadedMetadata = useCallback(\n (e: React.SyntheticEvent<HTMLVideoElement>) => {\n const v = e.currentTarget;\n setDuration(v.duration);\n\n // Detect likely server-side range-request misconfiguration for\n // finite-duration media. Skip the check entirely for live streams or\n // unknown durations, where seekable ranges legitimately don't cover\n // the full timeline. See issue #32.\n const hasFiniteDuration = Number.isFinite(v.duration) && v.duration > 0;\n if (!hasFiniteDuration) return;\n\n const seekable = v.seekable;\n const fullySeekable =\n seekable.length > 0 &&\n seekable.end(seekable.length - 1) >= v.duration - 0.5;\n if (!fullySeekable) {\n console.warn(\n `[MediaPlayer] Video at ${v.currentSrc} does not appear fully ` +\n `seekable. A server range-request configuration issue (for ` +\n `example, missing \"Accept-Ranges: bytes\") may prevent the ` +\n `browser from seeking correctly — seek/step controls may snap ` +\n `back toward the start. ` +\n `(seekable.length=${String(seekable.length)}, ` +\n `duration=${String(v.duration)})`,\n );\n }\n },\n [],\n );\n\n const handleError = useCallback(\n (e: React.SyntheticEvent<HTMLVideoElement>) => {\n const err = e.currentTarget.error;\n const codeMessages: Record<number, string> = {\n 1: \"Loading was aborted\",\n 2: \"Network error\",\n 3: \"Failed to decode video\",\n 4: \"Video format is not supported (or the file could not be loaded)\",\n };\n const friendly = err\n ? (codeMessages[err.code] ?? `Error code ${String(err.code)}`)\n : \"Unknown error\";\n console.error(\n `[MediaPlayer] Video error (code ${err?.code}): ${err?.message ?? \"unknown\"}`,\n );\n setLoadError(friendly);\n },\n [],\n );\n\n const handleProgress = useCallback(\n (e: React.SyntheticEvent<HTMLVideoElement>) => {\n const v = e.currentTarget;\n if (\n v.buffered.length > 0 &&\n Number.isFinite(v.duration) &&\n v.duration > 0\n ) {\n setBufferedEnd(v.buffered.end(v.buffered.length - 1));\n }\n },\n [],\n );\n\n const handleTimeUpdate = useCallback(\n (e: React.SyntheticEvent<HTMLVideoElement>) => {\n const { currentTime: ct } = e.currentTarget;\n setCurrentTime(ct);\n\n // stopAt enforcement — records \"stopAt\" event (distinct from natural \"ended\")\n if (stopAt !== undefined && ct >= stopAt) {\n stopAtReachedRef.current = true;\n e.currentTarget.pause(); // fires \"pause\" event; handlePause suppresses it\n recordEvent(\"stopAt\", ct);\n if (submitOnComplete) onComplete?.();\n return;\n }\n\n // Caption update\n if (cues.length > 0) {\n const active = cues.find((c) => ct >= c.startTime && ct <= c.endTime);\n setCaptionText(active?.text ?? null);\n }\n },\n [stopAt, cues, recordEvent, submitOnComplete, onComplete],\n );\n\n // Clamp seek target to allowed range — works for both HTML5 and YouTube\n const seek = useCallback(\n (delta: number) => {\n if (ytHandle) {\n const cur = ytHandle.getCurrentTime();\n const dur = ytHandle.getDuration();\n const min = allowScrubOutsideBounds ? 0 : (startAt ?? 0);\n const max = allowScrubOutsideBounds\n ? Number.isFinite(dur)\n ? dur\n : Infinity\n : (stopAt ?? (Number.isFinite(dur) ? dur : Infinity));\n const newTime = Math.min(Math.max(cur + delta, min), max);\n ytHandle.seekTo(newTime);\n recordEvent(\"seek\", newTime, { fromTime: cur });\n return;\n }\n const v = videoRef.current;\n if (!v) return;\n const fromTime = v.currentTime;\n const min = allowScrubOutsideBounds ? 0 : (startAt ?? 0);\n const max = allowScrubOutsideBounds\n ? Number.isFinite(v.duration)\n ? v.duration\n : Infinity\n : (stopAt ?? (Number.isFinite(v.duration) ? v.duration : Infinity));\n v.currentTime = Math.min(Math.max(v.currentTime + delta, min), max);\n recordEvent(\"seek\", v.currentTime, { fromTime });\n },\n [allowScrubOutsideBounds, startAt, stopAt, ytHandle, recordEvent],\n );\n\n const exitFastScrub = useCallback(() => {\n const v = videoRef.current;\n if (!v || !isFastScrubbing.current) return;\n isFastScrubbing.current = false;\n v.playbackRate = playbackRate;\n if (pausedBeforeScrub.current) v.pause();\n }, [playbackRate]);\n\n const enterFastScrubForward = useCallback(() => {\n const v = videoRef.current;\n if (!v || isFastScrubbing.current) return;\n isFastScrubbing.current = true;\n pausedBeforeScrub.current = v.paused;\n v.playbackRate = 2;\n if (v.paused) void v.play();\n }, []);\n\n const cycleSpeed = useCallback(() => {\n const v = videoRef.current;\n if (!v) return;\n const idx = SPEEDS.indexOf(playbackRate as (typeof SPEEDS)[number]);\n const next = SPEEDS[(idx + 1) % SPEEDS.length];\n v.playbackRate = next;\n setPlaybackRate(next);\n recordEvent(\"speed\", v.currentTime, { playbackRate: next });\n }, [playbackRate, recordEvent]);\n\n const handleKeyDown = useCallback(\n (e: React.KeyboardEvent) => {\n // YouTube: only Space/K play-pause and J/L/Arrow seek work\n if (ytHandle) {\n switch (e.key) {\n case \" \":\n case \"k\":\n case \"K\":\n e.preventDefault();\n if (ytHandle.isPaused()) ytHandle.play();\n else ytHandle.pause();\n break;\n case \"j\":\n case \"J\":\n e.preventDefault();\n seek(-10);\n break;\n case \"l\":\n case \"L\":\n e.preventDefault();\n seek(10);\n break;\n case \"ArrowRight\":\n e.preventDefault();\n seek(1);\n break;\n case \"ArrowLeft\":\n e.preventDefault();\n seek(-1);\n break;\n default:\n break;\n }\n return;\n }\n const v = videoRef.current;\n if (!v) return;\n switch (e.key) {\n case \" \":\n case \"k\":\n case \"K\":\n e.preventDefault();\n if (v.paused) void v.play();\n else v.pause();\n break;\n case \"ArrowRight\":\n e.preventDefault();\n if (e.repeat) {\n arrowRepeatCountRef.current++;\n if (arrowRepeatCountRef.current >= HOLD_REPEAT_THRESHOLD) {\n enterFastScrubForward();\n }\n } else {\n arrowRepeatCountRef.current = 0;\n seek(1);\n }\n break;\n case \"ArrowLeft\":\n e.preventDefault();\n if (e.repeat) {\n arrowRepeatCountRef.current++;\n if (arrowRepeatCountRef.current >= HOLD_REPEAT_THRESHOLD) {\n // Rewind: keep seeking back rapidly\n seek(-0.5);\n }\n } else {\n arrowRepeatCountRef.current = 0;\n seek(-1);\n }\n break;\n case \"l\":\n case \"L\":\n e.preventDefault();\n seek(10);\n break;\n case \"j\":\n case \"J\":\n e.preventDefault();\n seek(-10);\n break;\n case \".\":\n e.preventDefault();\n seek(stepDuration);\n break;\n case \",\":\n e.preventDefault();\n seek(-stepDuration);\n break;\n case \">\": {\n e.preventDefault();\n const faster =\n SPEEDS.find((s) => s > playbackRate) ?? SPEEDS[SPEEDS.length - 1];\n v.playbackRate = faster;\n setPlaybackRate(faster);\n break;\n }\n case \"<\": {\n e.preventDefault();\n const slower =\n [...SPEEDS].reverse().find((s) => s < playbackRate) ?? SPEEDS[0];\n v.playbackRate = slower;\n setPlaybackRate(slower);\n break;\n }\n default:\n break;\n }\n },\n [seek, stepDuration, playbackRate, enterFastScrubForward],\n );\n\n const handleKeyUp = useCallback(\n (e: React.KeyboardEvent) => {\n if (e.key === \"ArrowRight\" || e.key === \"ArrowLeft\") {\n arrowRepeatCountRef.current = 0;\n exitFastScrub();\n }\n },\n [exitFastScrub],\n );\n\n // Button hold-to-scrub\n const startButtonHold = useCallback(\n (direction: 1 | -1) => {\n holdTimeoutRef.current = setTimeout(() => {\n const v = videoRef.current;\n if (!v) return;\n if (direction === 1) {\n enterFastScrubForward();\n } else {\n isFastScrubbing.current = true;\n pausedBeforeScrub.current = v.paused;\n // Step back rapidly via interval\n holdIntervalRef.current = setInterval(() => {\n seek(-0.5);\n }, 100);\n }\n }, 500);\n },\n [enterFastScrubForward, seek],\n );\n\n const endButtonHold = useCallback(\n (didSeekOnClick: boolean) => {\n if (holdTimeoutRef.current !== null) {\n clearTimeout(holdTimeoutRef.current);\n holdTimeoutRef.current = null;\n }\n if (holdIntervalRef.current !== null) {\n clearInterval(holdIntervalRef.current);\n holdIntervalRef.current = null;\n }\n exitFastScrub();\n isFastScrubbing.current = false;\n void didSeekOnClick;\n },\n [exitFastScrub],\n );\n\n // ---------------------------------------------------------------------------\n // Callbacks forwarded to HTML5Controls / YouTubeControls\n // ---------------------------------------------------------------------------\n\n // Seek button: read isFastScrubbing before endButtonHold resets it, then\n // do a single-step seek only if the hold didn't already move the playhead.\n const onSeekButtonRelease = useCallback(\n (direction: 1 | -1) => {\n const wasHeld = isFastScrubbing.current;\n endButtonHold(false);\n if (!wasHeld) seek(direction);\n },\n [endButtonHold, seek],\n );\n\n const onSeekButtonLeave = useCallback(\n () => endButtonHold(false),\n [endButtonHold],\n );\n\n const onPlayPause = useCallback(() => {\n const v = videoRef.current;\n if (!v) return;\n if (v.paused) void v.play();\n else v.pause();\n }, []);\n\n // Scrub bar: pause on grab (records \"pause\" event at pre-scrub position),\n // seek in real-time during drag, resume on release (records \"play\" event).\n const onScrubStart = useCallback((t: number) => {\n const v = videoRef.current;\n if (!v) return;\n if (!v.paused) {\n scrubWasPlayingRef.current = true;\n v.pause();\n }\n v.currentTime = t;\n setCurrentTime(t);\n }, []);\n\n const onScrubMove = useCallback((t: number) => {\n if (videoRef.current) videoRef.current.currentTime = t;\n setCurrentTime(t);\n }, []);\n\n const onScrubEnd = useCallback((t: number) => {\n const v = videoRef.current;\n if (!v) return;\n v.currentTime = t;\n setCurrentTime(t);\n if (scrubWasPlayingRef.current) {\n scrubWasPlayingRef.current = false;\n void v.play();\n }\n }, []);\n\n // YouTube-specific scrub callbacks\n const ytOnPlayPause = useCallback(() => {\n if (isPaused) ytHandle?.play();\n else ytHandle?.pause();\n }, [isPaused, ytHandle]);\n\n const ytOnSeekBack = useCallback(() => {\n seek(-1);\n }, [seek]);\n\n const ytOnSeekForward = useCallback(() => {\n seek(1);\n }, [seek]);\n\n const ytOnScrubStart = useCallback(\n (t: number) => {\n if (ytHandle && !ytHandle.isPaused()) {\n scrubWasPlayingRef.current = true;\n ytHandle.pause();\n }\n ytHandle?.seekTo(t);\n setCurrentTime(t);\n },\n [ytHandle],\n );\n\n const ytOnScrubMove = useCallback(\n (t: number) => {\n ytHandle?.seekTo(t);\n setCurrentTime(t);\n },\n [ytHandle],\n );\n\n const ytOnScrubEnd = useCallback(\n (t: number) => {\n ytHandle?.seekTo(t);\n setCurrentTime(t);\n if (scrubWasPlayingRef.current) {\n scrubWasPlayingRef.current = false;\n ytHandle?.play();\n }\n },\n [ytHandle],\n );\n\n // ---------------------------------------------------------------------------\n // Derived display values\n // ---------------------------------------------------------------------------\n\n const hasControls =\n !syncToStageTime &&\n controls !== undefined &&\n (controls.playPause || controls.seek || controls.step || controls.speed);\n // Controls are always visible when paused or hovered (video mode).\n // In audio-only mode (playVideo:false) there's no video to obscure, so always show.\n const controlsVisible = hasControls && (isPaused || isHovered || !playVideo);\n\n // Scrub bar bounds\n const scrubMin = allowScrubOutsideBounds ? 0 : (startAt ?? 0);\n const scrubMax = allowScrubOutsideBounds\n ? Number.isFinite(duration) && duration > 0\n ? duration\n : 0\n : (stopAt ?? duration);\n\n // Scrub bar fill percentages\n const playedPct =\n scrubMax > scrubMin\n ? Math.min(\n Math.max(((currentTime - scrubMin) / (scrubMax - scrubMin)) * 100, 0),\n 100,\n )\n : 0;\n const bufferedPct =\n scrubMax > scrubMin\n ? Math.min(((bufferedEnd - scrubMin) / (scrubMax - scrubMin)) * 100, 100)\n : 0;\n\n // Shared props for HTML5Controls (used in both video-overlay and audio-flat layouts)\n const html5ControlsProps = {\n controls,\n isPaused,\n stepDuration,\n playbackRate,\n scrubMin,\n scrubMax,\n currentTime,\n duration,\n playedPct,\n bufferedPct,\n onSeek: seek,\n onCycleSpeed: cycleSpeed,\n onSeekButtonPress: startButtonHold,\n onSeekButtonRelease,\n onSeekButtonLeave,\n onPlayPause,\n onScrubStart,\n onScrubMove,\n onScrubEnd,\n };\n\n // ---------------------------------------------------------------------------\n // YouTube branch\n // ---------------------------------------------------------------------------\n\n if (youtubeVideoId) {\n const ytHasControls =\n !syncToStageTime &&\n controls !== undefined &&\n (controls.playPause || controls.seek);\n const ytControlsVisible =\n ytHasControls && (isPaused || isHovered || !playVideo);\n\n return (\n <div\n data-testid=\"mediaPlayer\"\n role=\"region\"\n aria-label=\"Media player\"\n tabIndex={0}\n onKeyDown={handleKeyDown}\n onKeyUp={handleKeyUp}\n onMouseEnter={() => setIsHovered(true)}\n onMouseLeave={() => setIsHovered(false)}\n style={{ position: \"relative\" }}\n >\n <div\n data-testid=\"mediaPlayer-viewport\"\n style={{ position: \"relative\" }}\n >\n <YouTubePlayer\n videoId={youtubeVideoId}\n startAt={startAt}\n onHandleReady={(h) => {\n setYtHandle(h);\n setDuration(h.getDuration());\n }}\n onPlay={(t) => {\n setIsPaused(false);\n setCurrentTime(t);\n recordEvent(\"play\", t);\n }}\n onPause={(t) => {\n setIsPaused(true);\n setCurrentTime(t);\n // stopAt reached via the poll: record \"stopAt\" not \"pause\"\n if (stopAtReachedRef.current) {\n stopAtReachedRef.current = false;\n recordEvent(\"stopAt\", t);\n if (submitOnComplete) onComplete?.();\n return;\n }\n recordEvent(\"pause\", t);\n }}\n onEnded={(t) => {\n setIsPaused(true);\n setCurrentTime(t);\n recordEvent(\"ended\", t);\n if (submitOnComplete) onComplete?.();\n }}\n />\n {ytControlsVisible && (\n <div\n data-testid=\"mediaPlayer-controls\"\n style={{\n position: \"absolute\",\n bottom: 0,\n left: 0,\n right: 0,\n background:\n \"linear-gradient(to top, rgba(0,0,0,0.75) 0%, rgba(0,0,0,0) 100%)\",\n padding: \"1.5rem 0.75rem 0.5rem\",\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"0.25rem\",\n }}\n >\n <YouTubeControls\n controls={controls}\n isPaused={isPaused}\n scrubMin={scrubMin}\n scrubMax={scrubMax}\n currentTime={currentTime}\n duration={duration}\n playedPct={playedPct}\n onPlayPause={ytOnPlayPause}\n onSeekBack={ytOnSeekBack}\n onSeekForward={ytOnSeekForward}\n onScrubStart={ytOnScrubStart}\n onScrubMove={ytOnScrubMove}\n onScrubEnd={ytOnScrubEnd}\n />\n </div>\n )}\n </div>\n </div>\n );\n }\n\n // ---------------------------------------------------------------------------\n // HTML5 branch\n // ---------------------------------------------------------------------------\n\n return (\n <div\n data-testid=\"mediaPlayer\"\n role=\"region\"\n aria-label=\"Media player\"\n tabIndex={0}\n onKeyDown={handleKeyDown}\n onKeyUp={handleKeyUp}\n onMouseEnter={() => setIsHovered(true)}\n onMouseLeave={() => setIsHovered(false)}\n style={{ position: \"relative\" }}\n >\n {/* Audio-only: hidden video element (no viewport div) */}\n {!playVideo && (\n <video\n ref={videoRef}\n data-testid=\"mediaPlayer-video\"\n src={url}\n muted={!playAudio}\n // crossOrigin=\"anonymous\" is required for Web Audio API capture\n // (Timeline waveform). Without it, cross-origin media plays but\n // taints the AnalyserNode → all-zero peaks. Stagebook convention:\n // media MUST be served with proper CORS headers. Same-origin media\n // is unaffected.\n crossOrigin=\"anonymous\"\n onPlay={handlePlay}\n onPause={handlePause}\n onEnded={handleEnded}\n onLoadedMetadata={handleLoadedMetadata}\n onTimeUpdate={handleTimeUpdate}\n onProgress={handleProgress}\n onError={handleError}\n style={{ display: \"none\" }}\n >\n <track kind=\"captions\" />\n </video>\n )}\n\n {/* Video viewport — video + captions + overlay controls */}\n {playVideo && (\n <div\n data-testid=\"mediaPlayer-viewport\"\n style={{ position: \"relative\" }}\n >\n <video\n ref={videoRef}\n data-testid=\"mediaPlayer-video\"\n src={url}\n muted={!playAudio}\n // See above — required for waveform capture. Same-origin no-op.\n crossOrigin=\"anonymous\"\n onPlay={handlePlay}\n onPause={handlePause}\n onEnded={handleEnded}\n onLoadedMetadata={handleLoadedMetadata}\n onTimeUpdate={handleTimeUpdate}\n onProgress={handleProgress}\n onError={handleError}\n style={{\n width: \"100%\",\n aspectRatio: \"16/9\",\n display: loadError ? \"none\" : \"block\",\n background: \"#000\",\n }}\n >\n <track kind=\"captions\" />\n </video>\n\n {loadError && (\n <div\n data-testid=\"mediaPlayer-error\"\n role=\"alert\"\n style={{\n width: \"100%\",\n aspectRatio: \"16/9\",\n background: \"#1c1c1e\",\n color: \"#fff\",\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n justifyContent: \"center\",\n gap: \"0.5rem\",\n padding: \"1rem\",\n textAlign: \"center\",\n fontSize: \"0.875rem\",\n }}\n >\n <svg\n width={32}\n height={32}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n style={{ opacity: 0.7 }}\n >\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <line x1=\"12\" y1=\"8\" x2=\"12\" y2=\"12\" />\n <line x1=\"12\" y1=\"16\" x2=\"12.01\" y2=\"16\" />\n </svg>\n <div style={{ fontWeight: 500 }}>Video unavailable</div>\n <div style={{ opacity: 0.75, fontSize: \"0.75rem\" }}>\n {loadError}\n </div>\n </div>\n )}\n\n {captionText !== null && (\n <div\n data-testid=\"mediaPlayer-caption\"\n style={{\n textAlign: \"center\",\n padding: \"0.5rem\",\n background: \"rgba(0,0,0,0.7)\",\n color: \"#fff\",\n }}\n >\n {captionText}\n </div>\n )}\n\n {controlsVisible && !loadError && (\n <div\n data-testid=\"mediaPlayer-controls\"\n style={{\n position: \"absolute\",\n bottom: 0,\n left: 0,\n right: 0,\n background:\n \"linear-gradient(to top, rgba(0,0,0,0.75) 0%, rgba(0,0,0,0) 100%)\",\n padding: \"1.5rem 0.75rem 0.5rem\",\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"0.25rem\",\n }}\n >\n <HTML5Controls {...html5ControlsProps} />\n </div>\n )}\n </div>\n )}\n\n {/* Audio-only: flat controls bar (always visible — no hover needed) */}\n {!playVideo && controlsVisible && !loadError && (\n <div\n data-testid=\"mediaPlayer-controls\"\n style={{\n background: \"rgba(28,28,30,0.96)\",\n borderRadius: \"0.5rem\",\n padding: \"0.5rem 0.75rem\",\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"0.25rem\",\n }}\n >\n <HTML5Controls {...html5ControlsProps} />\n </div>\n )}\n\n {/* Audio-only: error placeholder when load fails */}\n {!playVideo && loadError && (\n <div\n data-testid=\"mediaPlayer-error\"\n role=\"alert\"\n style={{\n background: \"#1c1c1e\",\n color: \"#fff\",\n borderRadius: \"0.5rem\",\n padding: \"1rem\",\n display: \"flex\",\n alignItems: \"center\",\n gap: \"0.75rem\",\n fontSize: \"0.875rem\",\n }}\n >\n <svg\n width={24}\n height={24}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n style={{ opacity: 0.7, flexShrink: 0 }}\n >\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <line x1=\"12\" y1=\"8\" x2=\"12\" y2=\"12\" />\n <line x1=\"12\" y1=\"16\" x2=\"12.01\" y2=\"16\" />\n </svg>\n <div>\n <div style={{ fontWeight: 500 }}>Audio unavailable</div>\n <div style={{ opacity: 0.75, fontSize: \"0.75rem\" }}>\n {loadError}\n </div>\n </div>\n </div>\n )}\n </div>\n );\n}\n","/**\n * Detects whether a URL points to a YouTube video and extracts the video ID.\n * Returns the video ID string if it is a YouTube URL, or null otherwise.\n *\n * Supported formats:\n * https://www.youtube.com/watch?v=VIDEO_ID\n * https://youtu.be/VIDEO_ID\n * https://www.youtube.com/embed/VIDEO_ID\n * https://youtube.com/watch?v=VIDEO_ID\n * https://m.youtube.com/watch?v=VIDEO_ID\n */\nexport function isYouTubeURL(url: string): string | null {\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n return null;\n }\n\n const { hostname, pathname, searchParams } = parsed;\n\n // Validate hostname: youtube.com (with optional www. or m. prefix) or youtu.be\n const isYouTubeDomain =\n hostname === \"youtube.com\" ||\n hostname === \"www.youtube.com\" ||\n hostname === \"m.youtube.com\" ||\n hostname === \"youtu.be\";\n\n if (!isYouTubeDomain) return null;\n\n // youtu.be/<videoId>\n if (hostname === \"youtu.be\") {\n const id = pathname.slice(1).split(\"?\")[0];\n return id.length > 0 ? id : null;\n }\n\n // youtube.com/embed/<videoId>\n if (pathname.startsWith(\"/embed/\")) {\n const id = pathname.slice(\"/embed/\".length);\n return id.length > 0 ? id : null;\n }\n\n // youtube.com/watch?v=<videoId>\n const videoId = searchParams.get(\"v\");\n return videoId && videoId.length > 0 ? videoId : null;\n}\n","export interface CaptionCue {\n startTime: number;\n endTime: number;\n text: string;\n}\n\n/**\n * Parses a WebVTT string into an array of caption cues.\n *\n * Handles:\n * - UTF-8 BOM\n * - CRLF line endings\n * - Cue identifiers (numeric or text labels)\n * - Multiline cue text\n * - NOTE and STYLE blocks (skipped)\n * - Both mm:ss.mmm and hh:mm:ss.mmm timestamp formats\n *\n * Does not merge overlapping cues — returns them as-is.\n */\nexport function parseVTT(content: string): CaptionCue[] {\n // Strip BOM and normalize line endings\n const normalized = content.replace(/^\\uFEFF/, \"\").replace(/\\r\\n/g, \"\\n\");\n\n if (!normalized.startsWith(\"WEBVTT\")) return [];\n\n // Split into blocks by blank lines\n const blocks = normalized.split(/\\n\\n+/);\n\n const cues: CaptionCue[] = [];\n\n for (const block of blocks.slice(1)) {\n const lines = block.trim().split(\"\\n\");\n if (lines.length === 0) continue;\n\n // Skip NOTE and STYLE blocks\n if (lines[0].startsWith(\"NOTE\") || lines[0].startsWith(\"STYLE\")) continue;\n\n let timingLineIndex = 0;\n\n // If the first line doesn't contain '-->', treat it as a cue identifier\n if (!lines[0].includes(\"-->\")) {\n timingLineIndex = 1;\n }\n\n if (timingLineIndex >= lines.length) continue;\n\n const timingLine = lines[timingLineIndex];\n if (!timingLine?.includes(\"-->\")) continue;\n\n const [startStr, endStr] = timingLine.split(\"-->\").map((s) => s.trim());\n if (!startStr || !endStr) continue;\n\n const startTime = parseTimestamp(startStr);\n const endTime = parseTimestamp(endStr.split(\" \")[0]); // strip cue settings\n\n if (startTime === null || endTime === null) continue;\n\n const text = lines.slice(timingLineIndex + 1).join(\"\\n\");\n if (text.length === 0) continue;\n\n cues.push({ startTime, endTime, text });\n }\n\n return cues;\n}\n\n/** Parses hh:mm:ss.mmm or mm:ss.mmm into seconds. Returns null if invalid. */\nfunction parseTimestamp(ts: string): number | null {\n // Match hh:mm:ss.mmm or mm:ss.mmm\n const match = ts.match(/^(?:(\\d+):)?(\\d{2}):(\\d{2})\\.(\\d{3})$/);\n if (!match) return null;\n\n const hours = match[1] ? parseInt(match[1], 10) : 0;\n const minutes = parseInt(match[2], 10);\n const seconds = parseInt(match[3], 10);\n const millis = parseInt(match[4], 10);\n\n return hours * 3600 + minutes * 60 + seconds + millis / 1000;\n}\n","/**\n * YouTubePlayer: HTML5 <video>-compatible wrapper around the YouTube IFrame API.\n *\n * Exports:\n * loadYouTubeAPI() — lazy-loads the IFrame API script once per page\n * buildYouTubeHandle() — constructs a PlaybackHandle from a YT.Player instance\n * createYouTubePlayer() — wires up a YT.Player with event callbacks\n * YouTubePlayer — React component used by MediaPlayer\n */\nimport React, { useEffect, useRef } from \"react\";\nimport type { PlaybackHandle } from \"../../playback/PlaybackHandle.js\";\n\n// ── YouTube IFrame API types (subset we actually use) ────────────────────────\n\ninterface YTPlayer {\n playVideo(): void;\n pauseVideo(): void;\n seekTo(seconds: number, allowSeekAhead: boolean): void;\n getCurrentTime(): number;\n getDuration(): number;\n getPlayerState(): number;\n destroy(): void;\n}\n\ninterface YTPlayerOptions {\n videoId: string;\n playerVars?: {\n start?: number;\n autoplay?: 0 | 1;\n enablejsapi?: 1;\n modestbranding?: 1;\n rel?: 0 | 1;\n };\n events?: {\n onReady?: () => void;\n onStateChange?: (event: { data: number }) => void;\n };\n}\n\ninterface YTNamespace {\n Player: new (el: HTMLElement, opts: YTPlayerOptions) => YTPlayer;\n PlayerState: {\n UNSTARTED: -1;\n ENDED: 0;\n PLAYING: 1;\n PAUSED: 2;\n BUFFERING: 3;\n CUED: 5;\n };\n}\n\ndeclare global {\n interface Window {\n YT?: YTNamespace;\n onYouTubeIframeAPIReady?: () => void;\n }\n}\n\n// ── API loader ────────────────────────────────────────────────────────────────\n\n// Pending settle functions waiting for the API to finish loading\nconst pendingSettlers: Array<{\n resolve: () => void;\n reject: (err: Error) => void;\n timer: ReturnType<typeof setTimeout>;\n}> = [];\nlet scriptInjected = false;\n\nconst YOUTUBE_API_TIMEOUT_MS = 10_000;\n\n/** Resolves when window.YT.Player is available. Safe to call multiple times. */\nexport function loadYouTubeAPI(): Promise<void> {\n return new Promise((resolve, reject) => {\n // Already loaded — resolve immediately\n if (typeof window !== \"undefined\" && window.YT?.Player) {\n resolve();\n return;\n }\n\n const timer = setTimeout(() => {\n reject(new Error(\"YouTube IFrame API failed to load within timeout\"));\n }, YOUTUBE_API_TIMEOUT_MS);\n\n pendingSettlers.push({ resolve, reject, timer });\n\n if (!scriptInjected) {\n scriptInjected = true;\n // The API calls window.onYouTubeIframeAPIReady when ready\n window.onYouTubeIframeAPIReady = () => {\n const settlers = pendingSettlers.splice(0);\n settlers.forEach((s) => {\n clearTimeout(s.timer);\n s.resolve();\n });\n };\n const script = document.createElement(\"script\");\n script.src = \"https://www.youtube.com/iframe_api\";\n script.onerror = () => {\n const settlers = pendingSettlers.splice(0);\n const err = new Error(\"Failed to load YouTube IFrame API script\");\n settlers.forEach((s) => {\n clearTimeout(s.timer);\n s.reject(err);\n });\n };\n document.head.appendChild(script);\n }\n });\n}\n\n// ── PlaybackHandle factory ────────────────────────────────────────────────────\n\n/** Build a PlaybackHandle that delegates to a live YT.Player instance. */\nexport function buildYouTubeHandle(player: YTPlayer): PlaybackHandle {\n return {\n play: () => player.playVideo(),\n pause: () => player.pauseVideo(),\n seekTo: (seconds: number) => player.seekTo(seconds, true),\n getCurrentTime: () => player.getCurrentTime(),\n getDuration: () => player.getDuration(),\n isPaused: () => player.getPlayerState() !== 1 /* YT.PlayerState.PLAYING */,\n isYouTube: true,\n channelCount: 0,\n peaks: [],\n peaksVersion: 0,\n requestWaveformCapture() {}, // no-op for YouTube\n };\n}\n\n// ── YT.Player factory ─────────────────────────────────────────────────────────\n\nexport interface CreateYouTubePlayerOptions {\n container: HTMLElement;\n videoId: string;\n startAt?: number;\n onHandleReady: (handle: PlaybackHandle) => void;\n onPlay: (currentTime: number) => void;\n onPause: (currentTime: number) => void;\n onEnded: (currentTime: number) => void;\n}\n\nexport interface YouTubePlayerController {\n destroy: () => void;\n}\n\n/**\n * Creates a YT.Player instance (after the API loads) and wires event callbacks.\n * Returns a { destroy } controller so the caller can clean up on unmount.\n */\nexport function createYouTubePlayer(\n opts: CreateYouTubePlayerOptions,\n): YouTubePlayerController {\n let player: YTPlayer | null = null;\n let destroyed = false;\n\n function setup() {\n if (destroyed || !window.YT) return;\n player = new window.YT.Player(opts.container, {\n videoId: opts.videoId,\n playerVars: {\n ...(opts.startAt !== undefined && { start: Math.floor(opts.startAt) }),\n enablejsapi: 1,\n modestbranding: 1,\n rel: 0,\n },\n events: {\n onReady: () => {\n if (destroyed || !player) return;\n opts.onHandleReady(buildYouTubeHandle(player));\n },\n onStateChange: (event) => {\n if (destroyed || !player) return;\n const t = player.getCurrentTime();\n if (event.data === 1 /* PLAYING */) opts.onPlay(t);\n else if (event.data === 2 /* PAUSED */) opts.onPause(t);\n else if (event.data === 0 /* ENDED */) opts.onEnded(t);\n },\n },\n });\n }\n\n // If the API is already loaded, set up synchronously so tests and fast\n // re-mounts don't wait for a microtask flush.\n if (window.YT?.Player) {\n setup();\n } else {\n void loadYouTubeAPI()\n .then(setup)\n .catch((err: unknown) => {\n console.error(\"[YouTubePlayer]\", err);\n });\n }\n\n return {\n destroy: () => {\n destroyed = true;\n player?.destroy();\n player = null;\n },\n };\n}\n\n// ── React component ───────────────────────────────────────────────────────────\n\nexport interface YouTubePlayerProps {\n videoId: string;\n startAt?: number;\n onHandleReady: (handle: PlaybackHandle) => void;\n onPlay: (currentTime: number) => void;\n onPause: (currentTime: number) => void;\n onEnded: (currentTime: number) => void;\n}\n\n/**\n * Renders a div the IFrame API replaces with an iframe.\n * Exposes playback events upward via callbacks.\n */\nexport function YouTubePlayer({\n videoId,\n startAt,\n onHandleReady,\n onPlay,\n onPause,\n onEnded,\n}: YouTubePlayerProps) {\n const containerRef = useRef<HTMLDivElement>(null);\n\n // Store callbacks in refs so the useEffect always calls the latest version\n // without needing to destroy/recreate the YT.Player on every render.\n const onHandleReadyRef = useRef(onHandleReady);\n const onPlayRef = useRef(onPlay);\n const onPauseRef = useRef(onPause);\n const onEndedRef = useRef(onEnded);\n onHandleReadyRef.current = onHandleReady;\n onPlayRef.current = onPlay;\n onPauseRef.current = onPause;\n onEndedRef.current = onEnded;\n\n useEffect(() => {\n if (!containerRef.current) return;\n const { destroy } = createYouTubePlayer({\n container: containerRef.current,\n videoId,\n startAt,\n onHandleReady: (h) => onHandleReadyRef.current(h),\n onPlay: (t) => onPlayRef.current(t),\n onPause: (t) => onPauseRef.current(t),\n onEnded: (t) => onEndedRef.current(t),\n });\n return destroy;\n }, [videoId, startAt]); // re-mount player when videoId or startAt changes\n\n return (\n <div\n ref={containerRef}\n data-testid=\"mediaPlayer-youtube\"\n style={{ width: \"100%\", aspectRatio: \"16/9\" }}\n />\n );\n}\n","/**\n * Format a duration in seconds as a human-readable time string.\n * Under an hour: \"M:SS\". One hour or more: \"H:MM:SS\".\n * Non-finite input (NaN, Infinity) returns \"0:00\".\n */\nexport function formatTime(seconds: number): string {\n if (!Number.isFinite(seconds) || seconds < 0) return \"0:00\";\n const total = Math.floor(seconds);\n const h = Math.floor(total / 3600);\n const m = Math.floor((total % 3600) / 60);\n const s = total % 60;\n const ss = String(s).padStart(2, \"0\");\n if (h > 0) {\n return `${String(h)}:${String(m).padStart(2, \"0\")}:${ss}`;\n }\n return `${String(m)}:${ss}`;\n}\n","/**\n * Inline SVG icons for MediaPlayer controls.\n * Self-contained — no external icon library dependency.\n * Play/Pause are filled (fill=\"currentColor\"); the chevron-based seek/step\n * icons are stroke-only (stroke=\"currentColor\"). Both inherit text color\n * from the parent button.\n */\nimport React from \"react\";\n\nexport function PlayIcon() {\n return (\n <svg\n width={24}\n height={24}\n viewBox=\"0 0 24 24\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path d=\"M6 4l14 8-14 8V4z\" />\n </svg>\n );\n}\n\nexport function PauseIcon() {\n return (\n <svg\n width={24}\n height={24}\n viewBox=\"0 0 24 24\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n >\n <rect x=\"5\" y=\"4\" width=\"5\" height=\"16\" rx=\"1\" />\n <rect x=\"14\" y=\"4\" width=\"5\" height=\"16\" rx=\"1\" />\n </svg>\n );\n}\n\n// Seek vs step icons use chevron counts to communicate magnitude:\n// double chevron (seek) = larger jump (1s), holdable for fast-scrub\n// single chevron (step) = smaller, deterministic step (stepDuration)\n// Reading left-to-right the row goes «« « ▶ » »» — granularity decreases\n// toward the center, like every other video player's transport row.\n\nexport function SeekBackIcon() {\n return (\n <svg\n width={20}\n height={20}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2.5}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n >\n {/* Two chevrons pointing left */}\n <polyline points=\"11,6 5,12 11,18\" />\n <polyline points=\"19,6 13,12 19,18\" />\n </svg>\n );\n}\n\nexport function SeekForwardIcon() {\n return (\n <svg\n width={20}\n height={20}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2.5}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n >\n {/* Two chevrons pointing right */}\n <polyline points=\"5,6 11,12 5,18\" />\n <polyline points=\"13,6 19,12 13,18\" />\n </svg>\n );\n}\n\nexport function StepBackIcon() {\n return (\n <svg\n width={20}\n height={20}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2.5}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n >\n {/* Single chevron pointing left */}\n <polyline points=\"15,6 9,12 15,18\" />\n </svg>\n );\n}\n\nexport function StepForwardIcon() {\n return (\n <svg\n width={20}\n height={20}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2.5}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n >\n {/* Single chevron pointing right */}\n <polyline points=\"9,6 15,12 9,18\" />\n </svg>\n );\n}\n","/**\n * Transport controls for MediaPlayer.\n *\n * Two components, one per player type:\n * HTML5Controls — full controls (play/pause, seek±1s, step±Ns, speed, scrub bar)\n * YouTubeControls — YouTube subset (play/pause, seek±1s, scrub bar;\n * no step/speed — the IFrame API doesn't support frame stepping)\n *\n * All mutable state lives in MediaPlayer; these components receive callbacks and\n * are purely presentational. Inline styles are deliberate — Tailwind is unreliable\n * in Playwright component tests.\n */\nimport React from \"react\";\nimport { formatTime } from \"../../../utils/formatTime.js\";\nimport {\n PlayIcon,\n PauseIcon,\n SeekBackIcon,\n SeekForwardIcon,\n StepBackIcon,\n StepForwardIcon,\n} from \"./icons.js\";\n\n// ---------------------------------------------------------------------------\n// Shared button styles\n// ---------------------------------------------------------------------------\n\nexport const controlBtnBase: React.CSSProperties = {\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n borderRadius: \"9999px\",\n color: \"#fff\",\n background: \"none\",\n border: \"none\",\n padding: 0,\n cursor: \"pointer\",\n};\n\nexport const controlBtnSmall: React.CSSProperties = {\n ...controlBtnBase,\n width: 36,\n height: 36,\n};\n\nexport const controlBtnLarge: React.CSSProperties = {\n ...controlBtnBase,\n width: 48,\n height: 48,\n};\n\n// ---------------------------------------------------------------------------\n// HTML5Controls\n// ---------------------------------------------------------------------------\n\nexport interface HTML5ControlsProps {\n controls:\n | { playPause?: boolean; seek?: boolean; step?: boolean; speed?: boolean }\n | undefined;\n isPaused: boolean;\n stepDuration: number;\n playbackRate: number;\n scrubMin: number;\n scrubMax: number;\n currentTime: number;\n duration: number;\n playedPct: number;\n bufferedPct: number;\n /** Seek the video by a signed delta in seconds (buttons and keyboard). */\n onSeek: (delta: number) => void;\n /** Advance to the next playback speed step. */\n onCycleSpeed: () => void;\n /** MouseDown on a seek button — starts the hold-to-fast-scrub timer. */\n onSeekButtonPress: (direction: 1 | -1) => void;\n /**\n * MouseUp on a seek button — ends the hold timer and, if no hold occurred,\n * performs the single-step seek.\n */\n onSeekButtonRelease: (direction: 1 | -1) => void;\n /** MouseLeave on a seek button — cancels the hold timer without seeking. */\n onSeekButtonLeave: () => void;\n /** Toggle play / pause on the HTML5 video element. */\n onPlayPause: () => void;\n /**\n * Scrub bar pointer down — pauses the video if playing, then seeks to\n * targetTime. Callers must also call setPointerCapture.\n */\n onScrubStart: (targetTime: number) => void;\n /** Scrub bar pointer move — seek-only, no play/pause side effect. */\n onScrubMove: (targetTime: number) => void;\n /** Scrub bar pointer up — final seek; resumes play if paused on grab. */\n onScrubEnd: (targetTime: number) => void;\n}\n\nexport function HTML5Controls({\n controls,\n isPaused,\n stepDuration,\n playbackRate,\n scrubMin,\n scrubMax,\n currentTime,\n duration,\n playedPct,\n bufferedPct,\n onSeek,\n onCycleSpeed,\n onSeekButtonPress,\n onSeekButtonRelease,\n onSeekButtonLeave,\n onPlayPause,\n onScrubStart,\n onScrubMove,\n onScrubEnd,\n}: HTML5ControlsProps) {\n function posFromEvent(e: React.PointerEvent<HTMLDivElement>): number {\n const rect = e.currentTarget.getBoundingClientRect();\n const pct = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));\n return scrubMin + pct * (scrubMax - scrubMin);\n }\n\n return (\n <>\n {/* Transport buttons row — centered, play in the middle */}\n <div\n style={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n gap: \"0.25rem\",\n }}\n >\n {controls?.seek && (\n <button\n data-testid=\"mediaPlayer-seekBack\"\n aria-label=\"Back 1s\"\n title=\"Back 1s (←) · Hold to scrub · J for 10s\"\n style={controlBtnSmall}\n onMouseDown={() => onSeekButtonPress(-1)}\n onMouseUp={() => onSeekButtonRelease(-1)}\n onMouseLeave={onSeekButtonLeave}\n >\n <SeekBackIcon />\n </button>\n )}\n\n {controls?.step && (\n <button\n data-testid=\"mediaPlayer-stepBack\"\n aria-label={`Step back ${String(stepDuration)}s`}\n title={`Step back ${String(stepDuration)}s (,)`}\n style={controlBtnSmall}\n onClick={() => onSeek(-stepDuration)}\n >\n <StepBackIcon />\n </button>\n )}\n\n {controls?.playPause && (\n <button\n data-testid=\"mediaPlayer-playPause\"\n aria-label={isPaused ? \"Play\" : \"Pause\"}\n title={isPaused ? \"Play (Space)\" : \"Pause (Space)\"}\n style={controlBtnLarge}\n onClick={onPlayPause}\n >\n {isPaused ? <PlayIcon /> : <PauseIcon />}\n </button>\n )}\n\n {controls?.step && (\n <button\n data-testid=\"mediaPlayer-stepForward\"\n aria-label={`Step forward ${String(stepDuration)}s`}\n title={`Step forward ${String(stepDuration)}s (.)`}\n style={controlBtnSmall}\n onClick={() => onSeek(stepDuration)}\n >\n <StepForwardIcon />\n </button>\n )}\n\n {controls?.seek && (\n <button\n data-testid=\"mediaPlayer-seekForward\"\n aria-label=\"Forward 1s\"\n title=\"Forward 1s (→) · Hold to scrub · L for 10s\"\n style={controlBtnSmall}\n onMouseDown={() => onSeekButtonPress(1)}\n onMouseUp={() => onSeekButtonRelease(1)}\n onMouseLeave={onSeekButtonLeave}\n >\n <SeekForwardIcon />\n </button>\n )}\n\n {controls?.speed && (\n <button\n data-testid=\"mediaPlayer-speed\"\n aria-label=\"Playback speed\"\n title=\"Playback speed (< / >)\"\n style={{\n ...controlBtnSmall,\n fontSize: \"0.875rem\",\n fontWeight: 500,\n fontVariantNumeric: \"tabular-nums\",\n }}\n onClick={onCycleSpeed}\n >\n {playbackRate}×\n </button>\n )}\n </div>\n\n {/* Scrub bar + time display */}\n {controls?.seek && (\n <div style={{ display: \"flex\", alignItems: \"center\", gap: \"0.5rem\" }}>\n <div\n data-testid=\"mediaPlayer-scrubBar\"\n role=\"slider\"\n aria-label=\"Seek\"\n aria-valuemin={scrubMin}\n aria-valuemax={scrubMax}\n aria-valuenow={currentTime}\n data-step={stepDuration}\n tabIndex={0}\n style={{\n flex: 1,\n position: \"relative\",\n height: 20,\n cursor: \"pointer\",\n display: \"flex\",\n alignItems: \"center\",\n }}\n onPointerDown={(e) => {\n e.currentTarget.setPointerCapture(e.pointerId);\n onScrubStart(posFromEvent(e));\n }}\n onPointerMove={(e) => {\n if (!(e.buttons & 1)) return;\n onScrubMove(posFromEvent(e));\n }}\n onPointerUp={(e) => onScrubEnd(posFromEvent(e))}\n >\n {/* Track */}\n <div\n style={{\n position: \"absolute\",\n left: 0,\n right: 0,\n height: 4,\n borderRadius: 2,\n background: \"rgba(255,255,255,0.2)\",\n }}\n >\n {/* Buffered fill */}\n <div\n data-testid=\"mediaPlayer-buffered\"\n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n height: \"100%\",\n width: `${String(bufferedPct)}%`,\n background: \"rgba(255,255,255,0.35)\",\n borderRadius: 2,\n pointerEvents: \"none\",\n }}\n />\n {/* Played fill */}\n <div\n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n height: \"100%\",\n width: `${String(playedPct)}%`,\n background: \"#fff\",\n borderRadius: 2,\n pointerEvents: \"none\",\n }}\n />\n </div>\n {/* Thumb */}\n <div\n style={{\n position: \"absolute\",\n left: `${String(playedPct)}%`,\n transform: \"translateX(-50%)\",\n width: 12,\n height: 12,\n borderRadius: \"50%\",\n background: \"#fff\",\n boxShadow: \"0 1px 3px rgba(0,0,0,0.4)\",\n pointerEvents: \"none\",\n }}\n />\n </div>\n <span\n data-testid=\"mediaPlayer-time\"\n style={{\n color: \"#fff\",\n fontSize: \"0.75rem\",\n whiteSpace: \"nowrap\",\n fontVariantNumeric: \"tabular-nums\",\n }}\n >\n {formatTime(currentTime)} / {formatTime(duration)}\n </span>\n </div>\n )}\n </>\n );\n}\n\n// ---------------------------------------------------------------------------\n// YouTubeControls\n// ---------------------------------------------------------------------------\n\nexport interface YouTubeControlsProps {\n controls: { playPause?: boolean; seek?: boolean } | undefined;\n isPaused: boolean;\n scrubMin: number;\n scrubMax: number;\n currentTime: number;\n duration: number;\n playedPct: number;\n /** Toggle play / pause on the YouTube player. */\n onPlayPause: () => void;\n /** Seek back 1 second. */\n onSeekBack: () => void;\n /** Seek forward 1 second. */\n onSeekForward: () => void;\n /** Scrub bar pointer down — pauses if playing, seeks to targetTime. */\n onScrubStart: (targetTime: number) => void;\n /** Scrub bar pointer move — seek-only. */\n onScrubMove: (targetTime: number) => void;\n /** Scrub bar pointer up — final seek; resumes play if paused on grab. */\n onScrubEnd: (targetTime: number) => void;\n}\n\nexport function YouTubeControls({\n controls,\n isPaused,\n scrubMin,\n scrubMax,\n currentTime,\n duration,\n playedPct,\n onPlayPause,\n onSeekBack,\n onSeekForward,\n onScrubStart,\n onScrubMove,\n onScrubEnd,\n}: YouTubeControlsProps) {\n function posFromEvent(e: React.PointerEvent<HTMLDivElement>): number {\n const rect = e.currentTarget.getBoundingClientRect();\n const pct = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));\n return scrubMin + pct * (scrubMax - scrubMin);\n }\n\n return (\n <>\n {/* Transport buttons */}\n <div\n style={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n gap: \"0.25rem\",\n }}\n >\n {controls?.seek && (\n <button\n data-testid=\"mediaPlayer-seekBack\"\n aria-label=\"Back 1s\"\n title=\"Back 1s · J for 10s\"\n style={controlBtnSmall}\n onClick={onSeekBack}\n >\n <SeekBackIcon />\n </button>\n )}\n\n {controls?.playPause && (\n <button\n data-testid=\"mediaPlayer-playPause\"\n aria-label={isPaused ? \"Play\" : \"Pause\"}\n title={isPaused ? \"Play (Space)\" : \"Pause (Space)\"}\n style={controlBtnLarge}\n onClick={onPlayPause}\n >\n {isPaused ? <PlayIcon /> : <PauseIcon />}\n </button>\n )}\n\n {controls?.seek && (\n <button\n data-testid=\"mediaPlayer-seekForward\"\n aria-label=\"Forward 1s\"\n title=\"Forward 1s · L for 10s\"\n style={controlBtnSmall}\n onClick={onSeekForward}\n >\n <SeekForwardIcon />\n </button>\n )}\n </div>\n\n {/* Scrub bar + time display (no buffered fill — IFrame API doesn't expose it) */}\n {controls?.seek && (\n <div style={{ display: \"flex\", alignItems: \"center\", gap: \"0.5rem\" }}>\n <div\n data-testid=\"mediaPlayer-scrubBar\"\n role=\"slider\"\n aria-label=\"Seek\"\n aria-valuemin={scrubMin}\n aria-valuemax={scrubMax}\n aria-valuenow={currentTime}\n tabIndex={0}\n style={{\n flex: 1,\n position: \"relative\",\n height: 20,\n cursor: \"pointer\",\n display: \"flex\",\n alignItems: \"center\",\n }}\n onPointerDown={(e) => {\n e.currentTarget.setPointerCapture(e.pointerId);\n onScrubStart(posFromEvent(e));\n }}\n onPointerMove={(e) => {\n if (!(e.buttons & 1)) return;\n onScrubMove(posFromEvent(e));\n }}\n onPointerUp={(e) => onScrubEnd(posFromEvent(e))}\n >\n <div\n style={{\n position: \"absolute\",\n left: 0,\n right: 0,\n height: 4,\n borderRadius: 2,\n background: \"rgba(255,255,255,0.2)\",\n }}\n >\n <div\n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n height: \"100%\",\n width: `${String(playedPct)}%`,\n background: \"#fff\",\n borderRadius: 2,\n pointerEvents: \"none\",\n }}\n />\n </div>\n <div\n style={{\n position: \"absolute\",\n left: `${String(playedPct)}%`,\n transform: \"translateX(-50%)\",\n width: 12,\n height: 12,\n borderRadius: \"50%\",\n background: \"#fff\",\n boxShadow: \"0 1px 3px rgba(0,0,0,0.4)\",\n pointerEvents: \"none\",\n }}\n />\n </div>\n <span\n data-testid=\"mediaPlayer-time\"\n style={{\n color: \"#fff\",\n fontSize: \"0.75rem\",\n whiteSpace: \"nowrap\",\n fontVariantNumeric: \"tabular-nums\",\n }}\n >\n {formatTime(currentTime)} / {formatTime(duration)}\n </span>\n </div>\n )}\n </>\n );\n}\n","import React, {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport type { PlaybackHandle } from \"./PlaybackHandle.js\";\n\n// ---------------------------------------------------------------------------\n// Context\n// ---------------------------------------------------------------------------\n\ninterface PlaybackRegistry {\n handles: Map<string, PlaybackHandle>;\n register(name: string, handle: PlaybackHandle): void;\n unregister(name: string): void;\n}\n\nconst PlaybackContext = createContext<PlaybackRegistry | null>(null);\n\n// ---------------------------------------------------------------------------\n// Provider\n// ---------------------------------------------------------------------------\n\nexport function PlaybackProvider({ children }: { children: React.ReactNode }) {\n const [handles, setHandles] = useState<Map<string, PlaybackHandle>>(\n () => new Map(),\n );\n\n const register = useCallback((name: string, handle: PlaybackHandle) => {\n setHandles((prev) => new Map(prev).set(name, handle));\n }, []);\n\n const unregister = useCallback((name: string) => {\n setHandles((prev) => {\n const next = new Map(prev);\n next.delete(name);\n return next;\n });\n }, []);\n\n const value = useMemo(\n () => ({ handles, register, unregister }),\n [handles, register, unregister],\n );\n\n return (\n <PlaybackContext.Provider value={value}>\n {children}\n </PlaybackContext.Provider>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Hooks\n// ---------------------------------------------------------------------------\n\n/**\n * Register a PlaybackHandle under `name` for the duration of the component's\n * life. Safe to call without a PlaybackProvider — silently no-ops.\n */\nexport function useRegisterPlayback(\n name: string,\n handle: PlaybackHandle,\n): void {\n const ctx = useContext(PlaybackContext);\n // Keep a stable ref to the handle so we don't re-register on every render.\n const handleRef = useRef(handle);\n handleRef.current = handle;\n\n useEffect(() => {\n if (!ctx) return; // no PlaybackProvider above — intentional no-op\n ctx.register(name, handle);\n return () => ctx.unregister(name);\n }, [name, ctx, handle]);\n}\n\n/**\n * Look up a PlaybackHandle by the name registered by a sibling MediaPlayer.\n * Returns `undefined` if no player with that name is mounted yet.\n * Must be called inside a PlaybackProvider.\n */\nexport function usePlayback(source: string): PlaybackHandle | undefined {\n const ctx = useContext(PlaybackContext);\n return ctx?.handles.get(source);\n}\n","// Pure waveform capture logic — no React/DOM dependencies.\n// Used by MediaPlayer to accumulate peak data from AnalyserNodes.\n\n/**\n * Hard cap on the number of buckets allocated per channel. At the default\n * 10 buckets/second this caps memory at ~16 MB per channel (8 bytes per\n * Float32 × 2 entries × 1_000_000 buckets), which is ~28 hours of audio.\n * Beyond this, we degrade gracefully — capture still runs but the buffer\n * stops growing rather than blowing up memory on pathological inputs.\n */\nexport const MAX_BUCKETS = 1_000_000;\n\n/**\n * How many time buckets are needed to cover the given duration.\n * Returns 0 for non-finite or non-positive durations, capped at MAX_BUCKETS.\n */\nexport function computeBucketCount(\n duration: number,\n bucketsPerSecond: number,\n): number {\n if (!Number.isFinite(duration) || duration <= 0) return 0;\n return Math.min(Math.ceil(duration * bucketsPerSecond), MAX_BUCKETS);\n}\n\n/**\n * Map a playback time to a bucket index. Clamps negative times to 0.\n */\nexport function timeToBucket(time: number, bucketsPerSecond: number): number {\n return Math.floor(Math.max(0, time) * bucketsPerSecond);\n}\n\n/**\n * Create the peaks storage arrays for all channels.\n * Each array has `2 * bucketCount` elements: interleaved [min, max] per bucket.\n * Initialized with sentinel values (min=1, max=-1) so we can detect\n * which buckets have been filled.\n */\nexport function createPeaksArrays(\n channelCount: number,\n bucketCount: number,\n): Float32Array[] {\n const arrays: Float32Array[] = [];\n for (let ch = 0; ch < channelCount; ch++) {\n const arr = new Float32Array(bucketCount * 2);\n for (let i = 0; i < bucketCount; i++) {\n arr[i * 2] = 1; // min sentinel\n arr[i * 2 + 1] = -1; // max sentinel\n }\n arrays.push(arr);\n }\n return arrays;\n}\n\n/**\n * Returns true if every analyser buffer contains only the silence midpoint\n * (128 from getByteTimeDomainData). Used by the tainting detector — if all\n * buffers are flat after several seconds of playback, the AnalyserNode is\n * almost certainly receiving CORS-tainted (zeroed) audio.\n */\nexport function allBuffersSilent(buffers: Uint8Array[]): boolean {\n if (buffers.length === 0) return false;\n for (const buf of buffers) {\n for (let i = 0; i < buf.length; i++) {\n if (buf[i] !== 128) return false;\n }\n }\n return true;\n}\n\n/**\n * Accumulate one frame of analyser data into the peaks arrays.\n *\n * @param peaks - Per-channel Float32Arrays (interleaved min/max)\n * @param analyserBuffers - Per-channel Uint8Array from getByteTimeDomainData()\n * @param currentTime - Current playback position in seconds\n * @param bucketsPerSecond - Resolution of the peaks data\n */\nexport function accumulatePeaks(\n peaks: Float32Array[],\n analyserBuffers: Uint8Array[],\n currentTime: number,\n bucketsPerSecond: number,\n): void {\n const bucket = timeToBucket(currentTime, bucketsPerSecond);\n\n for (let ch = 0; ch < peaks.length; ch++) {\n const peakArr = peaks[ch];\n const data = analyserBuffers[ch];\n if (!peakArr || !data) continue;\n\n const bucketCount = peakArr.length / 2;\n if (bucket >= bucketCount) continue;\n\n // Find min/max of this frame's samples, normalized to [-1, 1]\n let frameMin = 1;\n let frameMax = -1;\n for (let i = 0; i < data.length; i++) {\n const normalized = (data[i] - 128) / 128;\n if (normalized < frameMin) frameMin = normalized;\n if (normalized > frameMax) frameMax = normalized;\n }\n\n // Update the bucket: expand the min/max envelope\n const minIdx = bucket * 2;\n const maxIdx = bucket * 2 + 1;\n const existingMin = peakArr[minIdx];\n const existingMax = peakArr[maxIdx];\n\n if (frameMin < existingMin) peakArr[minIdx] = frameMin;\n if (frameMax > existingMax) peakArr[maxIdx] = frameMax;\n }\n}\n","import React, {\n useCallback,\n useEffect,\n useReducer,\n useRef,\n useState,\n} from \"react\";\nimport { usePlayback } from \"../playback/PlaybackProvider.js\";\nimport { TimeRuler } from \"./timeline/TimeRuler.js\";\nimport { TimelineTrack, GUTTER_WIDTH } from \"./timeline/TimelineTrack.js\";\nimport { Playhead } from \"./timeline/Playhead.js\";\nimport { SelectionOverlay } from \"./timeline/SelectionOverlay.js\";\nimport { TimelineFooter } from \"./timeline/TimelineFooter.js\";\nimport { Minimap } from \"./timeline/Minimap.js\";\nimport { HelpPopover } from \"./timeline/HelpPopover.js\";\nimport { computeBucketCount } from \"./mediaPlayer/waveformCapture.js\";\nimport {\n initialSelectionState,\n selectionsReducer,\n} from \"./timeline/selectionsReducer.js\";\nimport { keyToAction } from \"./timeline/keyboardActions.js\";\nimport type { PointSelection, RangeSelection } from \"./timeline/selections.js\";\nimport {\n AUTO_SCROLL_THRESHOLD,\n SEEK_JUMP_THRESHOLD,\n clampViewportStart,\n computeViewportAfterScroll,\n computeViewportAfterSeek,\n computeViewportAfterZoom,\n isPlayheadPastThreshold,\n zoomIn as nextZoomIn,\n zoomOut as nextZoomOut,\n} from \"./timeline/viewport.js\";\n\nexport interface TimelineProps {\n source: string;\n name: string;\n selectionType: \"range\" | \"point\";\n selectionScope?: \"track\" | \"all\";\n multiSelect?: boolean;\n showWaveform?: boolean;\n trackLabels?: string[];\n /**\n * Previously saved selections to restore on mount. Element.tsx resolves\n * this from `timeline.<name>` so participants who reload the stage see\n * their existing marks. Untrusted shape — validated before use.\n */\n initialSelections?: unknown;\n save: (key: string, value: unknown) => void;\n}\n\nconst TRACK_HEIGHT = 48;\nconst BUCKETS_PER_SECOND = 10;\n\n/**\n * Validate restored selections from saved state. Returns an empty array if\n * the input is malformed — better to start fresh than to crash on a bad save.\n */\nfunction validateSavedSelections(\n raw: unknown,\n selectionType: \"range\" | \"point\",\n): RangeSelection[] | PointSelection[] {\n if (!Array.isArray(raw)) return [];\n if (selectionType === \"range\") {\n const valid: RangeSelection[] = [];\n for (const item of raw) {\n if (\n item &&\n typeof item === \"object\" &&\n typeof (item as { start?: unknown }).start === \"number\" &&\n typeof (item as { end?: unknown }).end === \"number\" &&\n Number.isFinite((item as { start: number }).start) &&\n Number.isFinite((item as { end: number }).end)\n ) {\n const r: RangeSelection = {\n start: (item as { start: number }).start,\n end: (item as { end: number }).end,\n };\n const t = (item as { track?: unknown }).track;\n if (typeof t === \"number\" && Number.isFinite(t)) r.track = t;\n valid.push(r);\n }\n }\n return valid;\n }\n const valid: PointSelection[] = [];\n for (const item of raw) {\n if (\n item &&\n typeof item === \"object\" &&\n typeof (item as { time?: unknown }).time === \"number\" &&\n Number.isFinite((item as { time: number }).time)\n ) {\n const p: PointSelection = { time: (item as { time: number }).time };\n const t = (item as { track?: unknown }).track;\n if (typeof t === \"number\" && Number.isFinite(t)) p.track = t;\n valid.push(p);\n }\n }\n return valid;\n}\n\nexport function Timeline({\n source,\n name,\n selectionType,\n selectionScope = \"all\",\n multiSelect = false,\n showWaveform = true,\n trackLabels,\n initialSelections,\n save,\n}: TimelineProps) {\n const handle = usePlayback(source);\n const tracksAreaRef = useRef<HTMLDivElement>(null);\n const [containerWidth, setContainerWidth] = useState(0);\n const [currentTime, setCurrentTime] = useState(0);\n\n // Callback ref: measures the container immediately on attach. Works\n // regardless of mount order — unlike useEffect, a callback ref fires when\n // React attaches the DOM element, even if the component re-renders later\n // (e.g. when the playback handle becomes available).\n // Also stores the element in containerElRef so we can call .focus() later.\n const observerRef = useRef<ResizeObserver | null>(null);\n const containerElRef = useRef<HTMLDivElement | null>(null);\n const containerRef = useCallback((el: HTMLDivElement | null) => {\n containerElRef.current = el;\n observerRef.current?.disconnect();\n observerRef.current = null;\n if (!el) return;\n setContainerWidth(el.getBoundingClientRect().width);\n if (typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver((entries) => {\n for (const entry of entries) {\n setContainerWidth(entry.contentRect.width);\n }\n });\n observer.observe(el);\n observerRef.current = observer;\n }, []);\n // Cleanup on unmount\n useEffect(() => {\n return () => {\n observerRef.current?.disconnect();\n observerRef.current = null;\n };\n }, []);\n\n // Zoom & pan state\n const [zoomLevel, setZoomLevel] = useState(1);\n const [viewportStart, setViewportStart] = useState(0);\n const [helpOpen, setHelpOpen] = useState(false);\n\n // Track whether the playhead changes are \"natural playback\" (RAF tick)\n // versus \"external seek\" (someone called handle.seekTo() out of band).\n // Auto-scroll uses the former; snap-on-seek uses the latter.\n const lastPlayheadRef = useRef(0);\n const lastTickWasPlayingRef = useRef(false);\n\n // Selection state via reducer. Lazy initializer hydrates from saved state\n // when present so participants who reload mid-stage see their existing\n // selections (validated to drop malformed items).\n const [state, dispatch] = useReducer(selectionsReducer, undefined, () => {\n const base = initialSelectionState();\n if (initialSelections === undefined) return base;\n return {\n ...base,\n selections: validateSavedSelections(initialSelections, selectionType),\n };\n });\n\n // Drag transaction state — set true between BEGIN_DRAG (first pointermove\n // past the dead zone) and pointerup/leave. While true, the save effect\n // skips so we don't spam the server with one save per pixel of motion.\n const [isDragging, setIsDragging] = useState(false);\n\n // Save selections whenever they change (after the initial mount). Mouse-\n // driven changes save immediately on commit (drag end / click); keyboard\n // adjustments are debounced ~500ms so holding an arrow key collapses to\n // one save; mid-drag pointermove dispatches are deferred until the drag\n // ends to avoid server spam.\n const lastSavedRef = useRef<string | null>(null);\n const saveTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n // Set to true by the keyboard handler before dispatching, so the save\n // effect can debounce this particular state change. Reset after the save.\n const debounceNextSaveRef = useRef(false);\n useEffect(() => {\n const serialized = JSON.stringify(state.selections);\n if (lastSavedRef.current === null) {\n lastSavedRef.current = serialized;\n return;\n }\n if (serialized === lastSavedRef.current) return;\n // While a pointer drag is in progress, defer — the save will fire when\n // isDragging transitions back to false (this same effect re-runs).\n if (isDragging) return;\n\n if (saveTimerRef.current !== null) clearTimeout(saveTimerRef.current);\n\n if (debounceNextSaveRef.current) {\n debounceNextSaveRef.current = false;\n saveTimerRef.current = setTimeout(() => {\n lastSavedRef.current = serialized;\n save(`timeline_${name}`, state.selections);\n saveTimerRef.current = null;\n }, 500);\n } else {\n lastSavedRef.current = serialized;\n save(`timeline_${name}`, state.selections);\n }\n\n return () => {\n if (saveTimerRef.current !== null) {\n clearTimeout(saveTimerRef.current);\n saveTimerRef.current = null;\n }\n };\n }, [state.selections, isDragging, name, save]);\n\n // Measure container width. Read from getBoundingClientRect on every render\n // via a callback ref, and observe with ResizeObserver for ongoing updates.\n // The callback ref fires synchronously when the element is attached, which\n // gives a usable width on first paint even in test environments where the\n // ResizeObserver callback is delayed.\n\n // Keep a ref to the handle so other effects can read the current handle\n // without re-running when its identity changes.\n const handleRef = useRef(handle);\n handleRef.current = handle;\n\n // Request waveform capture once the handle becomes available. We depend\n // on `handle` (not just on `showWaveform`) so the effect re-runs when the\n // handle transitions from undefined to defined — important when MediaPlayer\n // and Timeline mount in the same render but the handle is registered in a\n // post-render effect from MockPlayer / MediaPlayer.\n useEffect(() => {\n if (!handle) return;\n if (showWaveform) {\n handle.requestWaveformCapture();\n }\n setCurrentTime(handle.getCurrentTime());\n }, [handle, showWaveform]);\n\n // Poll currentTime + peaksVersion + isPaused via RAF. peaksVersion is the\n // render token for the waveform — peaks are mutated in place by the\n // capture loop, so React never sees the array reference change. Polling\n // the version and storing it in state lets the WaveformRenderer effect\n // re-run when new data arrives.\n const [isPaused, setIsPaused] = useState(true);\n const [peaksVersion, setPeaksVersion] = useState(0);\n useEffect(() => {\n let cancelled = false;\n let lastValue = -1;\n let lastPaused: boolean | null = null;\n let lastPeaksVersion = -1;\n let rafId = 0;\n\n function tick() {\n if (cancelled) return;\n const h = handleRef.current;\n if (h) {\n const t = h.getCurrentTime();\n if (t !== lastValue) {\n lastValue = t;\n setCurrentTime(t);\n }\n const paused = h.isPaused();\n if (paused !== lastPaused) {\n lastPaused = paused;\n setIsPaused(paused);\n }\n const v = h.peaksVersion;\n if (v !== lastPeaksVersion) {\n lastPeaksVersion = v;\n setPeaksVersion(v);\n }\n }\n rafId = requestAnimationFrame(tick);\n }\n\n rafId = requestAnimationFrame(tick);\n return () => {\n cancelled = true;\n cancelAnimationFrame(rafId);\n };\n }, []);\n\n // Viewport scrolling effect: keeps the playhead within view as it moves.\n // - During playback: when playhead crosses 90%, scroll smoothly\n // - On seek/scrub (large playhead delta): snap so playhead is at ~25%\n //\n // Only triggered by playhead motion, not by viewport changes — otherwise\n // a manual pan via the minimap would immediately get undone (the playhead\n // would suddenly look \"off-screen\" relative to the new viewport).\n useEffect(() => {\n if (zoomLevel <= 1) return;\n const duration = handleRef.current?.getDuration() ?? 0;\n if (duration <= 0) return;\n\n const visibleDuration = duration / zoomLevel;\n const lastT = lastPlayheadRef.current;\n lastPlayheadRef.current = currentTime;\n lastTickWasPlayingRef.current = !isPaused;\n\n // No motion → nothing to do\n if (currentTime === lastT) return;\n\n // Detect \"jump\" — large delta or transition to/from playing means\n // the user seeked rather than naturally played through\n const delta = currentTime - lastT;\n const isJump = Math.abs(delta) > SEEK_JUMP_THRESHOLD;\n\n if (isJump) {\n // Snap viewport so the playhead is ~25% from the left\n const newStart = computeViewportAfterSeek(\n currentTime,\n visibleDuration,\n duration,\n );\n setViewportStart(newStart);\n return;\n }\n\n // Continuous playback: auto-scroll when playhead crosses 90%\n if (\n isPlayheadPastThreshold(\n currentTime,\n viewportStart,\n visibleDuration,\n AUTO_SCROLL_THRESHOLD,\n )\n ) {\n const newStart = computeViewportAfterScroll(\n currentTime,\n visibleDuration,\n duration,\n );\n if (newStart !== viewportStart) setViewportStart(newStart);\n }\n }, [currentTime, isPaused, zoomLevel, viewportStart]);\n\n // Zoom handlers\n const onZoomIn = useCallback(() => {\n const duration = handleRef.current?.getDuration() ?? 0;\n if (duration <= 0) return;\n const newZoom = nextZoomIn(zoomLevel);\n if (newZoom === zoomLevel) return;\n setZoomLevel(newZoom);\n setViewportStart(\n computeViewportAfterZoom({\n currentZoom: zoomLevel,\n newZoom,\n duration,\n currentViewportStart: viewportStart,\n playheadTime: currentTime,\n }),\n );\n }, [zoomLevel, viewportStart, currentTime]);\n\n const onZoomOut = useCallback(() => {\n const duration = handleRef.current?.getDuration() ?? 0;\n if (duration <= 0) return;\n const newZoom = nextZoomOut(zoomLevel);\n if (newZoom === zoomLevel) return;\n setZoomLevel(newZoom);\n setViewportStart(\n computeViewportAfterZoom({\n currentZoom: zoomLevel,\n newZoom,\n duration,\n currentViewportStart: viewportStart,\n playheadTime: currentTime,\n }),\n );\n }, [zoomLevel, viewportStart, currentTime]);\n\n const onMinimapPan = useCallback(\n (newStart: number) => {\n const duration = handleRef.current?.getDuration() ?? 0;\n setViewportStart(clampViewportStart(newStart, duration, zoomLevel));\n },\n [zoomLevel],\n );\n\n // Keyboard handler — delegates to keyboardActions.ts for the key-to-action\n // mapping. Returns null when the key should fall through to MediaPlayer.\n const onKeyDown = (e: React.KeyboardEvent) => {\n const currentRange =\n selectionType === \"range\" && state.activeIndex !== null\n ? ((state.selections as RangeSelection[])[state.activeIndex] ?? null)\n : null;\n const currentPoint =\n selectionType === \"point\" && state.activeIndex !== null\n ? ((state.selections as PointSelection[])[state.activeIndex] ?? null)\n : null;\n\n const action = keyToAction(\n {\n key: e.key,\n ctrlKey: e.ctrlKey,\n metaKey: e.metaKey,\n shiftKey: e.shiftKey,\n },\n {\n selectionType,\n activeIndex: state.activeIndex,\n activeHandle: state.activeHandle,\n currentRange,\n currentPoint,\n },\n );\n if (!action) return; // Fall through to MediaPlayer\n\n e.preventDefault();\n e.stopPropagation();\n\n // Clamp time to [0, duration] before dispatch + seek so a keyboard\n // adjustment can never push a selection past the media bounds.\n const dur = handleRef.current?.getDuration() ?? 0;\n const clampToMedia = (t: number): number => {\n if (Number.isFinite(dur) && dur > 0) {\n return Math.max(0, Math.min(t, dur));\n }\n return Math.max(0, t);\n };\n\n switch (action.type) {\n case \"adjustHandle\": {\n const t = clampToMedia(action.time);\n debounceNextSaveRef.current = true;\n dispatch({\n type: \"ADJUST_HANDLE\",\n index: action.index,\n handle: action.handle,\n time: t,\n });\n // Sync video to the new handle position so the user sees the frame\n handleRef.current?.seekTo(t);\n break;\n }\n case \"repositionPoint\": {\n const t = clampToMedia(action.time);\n debounceNextSaveRef.current = true;\n dispatch({\n type: \"REPOSITION_POINT\",\n index: action.index,\n time: t,\n });\n handleRef.current?.seekTo(t);\n break;\n }\n case \"switchHandle\":\n dispatch({ type: \"SET_ACTIVE_HANDLE\", handle: action.handle });\n break;\n case \"delete\":\n dispatch({ type: \"DELETE\" });\n break;\n case \"deselect\":\n dispatch({ type: \"DESELECT\" });\n break;\n case \"undo\":\n dispatch({ type: \"UNDO\" });\n break;\n }\n };\n\n if (!handle) {\n return (\n <p\n data-testid=\"timeline-error\"\n style={{\n color: \"var(--stagebook-danger, #dc2626)\",\n fontSize: \"0.875rem\",\n }}\n >\n Timeline: no media player found with name "{source}"\n </p>\n );\n }\n\n const duration = handle.getDuration();\n const channelCount = handle.channelCount || 1;\n const peaks = handle.peaks;\n const waveformWidth = Math.max(containerWidth - GUTTER_WIDTH, 0);\n const totalBuckets = computeBucketCount(duration, BUCKETS_PER_SECOND);\n\n // Compute visible bucket range from zoom/viewport\n const visibleDuration = duration > 0 ? duration / zoomLevel : 0;\n const startBucket = Math.floor(viewportStart * BUCKETS_PER_SECOND);\n const endBucket = Math.min(\n Math.ceil((viewportStart + visibleDuration) * BUCKETS_PER_SECOND),\n totalBuckets,\n );\n\n // Build track labels\n const labels: string[] = [];\n for (let i = 0; i < channelCount; i++) {\n labels.push(trackLabels?.[i] ?? `Position ${String(i)}`);\n }\n\n const tracksHeight = channelCount * TRACK_HEIGHT;\n\n return (\n <div\n ref={containerRef}\n data-testid=\"timeline\"\n data-source={source}\n data-name={name}\n data-selection-type={selectionType}\n data-selection-scope={selectionScope}\n data-multi-select={multiSelect}\n data-show-waveform={showWaveform}\n data-zoom-level={zoomLevel}\n role=\"region\"\n aria-label={`Timeline: ${name}`}\n tabIndex={0}\n onKeyDown={onKeyDown}\n style={{\n border: \"1px solid var(--stagebook-border, #e5e7eb)\",\n borderRadius: \"0.5rem\",\n overflow: \"hidden\",\n outline: \"none\",\n position: \"relative\",\n }}\n >\n {/* Minimap — only when zoomed in */}\n {zoomLevel > 1 && (\n <div style={{ marginLeft: `${String(GUTTER_WIDTH)}px` }}>\n <Minimap\n duration={duration}\n width={waveformWidth}\n zoomLevel={zoomLevel}\n viewportStart={viewportStart}\n currentTime={currentTime}\n selections={state.selections}\n onViewportChange={onMinimapPan}\n />\n </div>\n )}\n\n {/* Time ruler — offset by gutter width */}\n <div style={{ marginLeft: `${String(GUTTER_WIDTH)}px` }}>\n <TimeRuler\n duration={duration}\n width={waveformWidth}\n zoomLevel={zoomLevel}\n viewportStart={viewportStart}\n />\n </div>\n\n {/* Tracks + selection overlay + playhead */}\n <div ref={tracksAreaRef} style={{ position: \"relative\" }}>\n {labels.map((label, i) => (\n <TimelineTrack\n key={i}\n label={label}\n peaks={peaks[i] ?? null}\n peaksVersion={peaksVersion}\n waveformWidth={waveformWidth}\n height={TRACK_HEIGHT}\n startBucket={startBucket}\n endBucket={endBucket}\n />\n ))}\n\n {/* Selection overlay — positioned over the waveform area, offset by gutter */}\n <div\n style={{\n position: \"absolute\",\n top: 0,\n left: `${String(GUTTER_WIDTH)}px`,\n width: `${String(waveformWidth)}px`,\n height: `${String(tracksHeight)}px`,\n }}\n >\n <SelectionOverlay\n width={waveformWidth}\n height={tracksHeight}\n duration={duration}\n zoomLevel={zoomLevel}\n viewportStart={viewportStart}\n selectionType={selectionType}\n selectionScope={selectionScope}\n channelCount={channelCount}\n multiSelect={multiSelect}\n selections={state.selections}\n activeIndex={state.activeIndex}\n activeHandle={state.activeHandle}\n onSeek={(t) => handle.seekTo(t)}\n onCreateRange={(start, end, track) =>\n dispatch({\n type: \"CREATE_RANGE\",\n start,\n end,\n track,\n multiSelect,\n })\n }\n onCreatePoint={(time, track) =>\n dispatch({\n type: \"CREATE_POINT\",\n time,\n track,\n multiSelect,\n })\n }\n onAdjustHandle={(index, h, time, noSnapshot) =>\n dispatch({\n type: \"ADJUST_HANDLE\",\n index,\n handle: h,\n time,\n noSnapshot,\n })\n }\n onRepositionPoint={(index, time, noSnapshot) =>\n dispatch({\n type: \"REPOSITION_POINT\",\n index,\n time,\n noSnapshot,\n })\n }\n onSelect={(index) => dispatch({ type: \"SELECT\", index })}\n onDeselect={() => dispatch({ type: \"DESELECT\" })}\n onSetActiveHandle={(h) =>\n dispatch({ type: \"SET_ACTIVE_HANDLE\", handle: h })\n }\n onBeginDrag={() => {\n dispatch({ type: \"BEGIN_DRAG\" });\n setIsDragging(true);\n }}\n onEndDrag={() => setIsDragging(false)}\n onRequestFocus={() =>\n containerElRef.current?.focus({ preventScroll: true })\n }\n />\n\n {/* Playhead — over selection overlay */}\n <Playhead\n currentTime={currentTime}\n duration={duration}\n width={waveformWidth}\n height={tracksHeight}\n zoomLevel={zoomLevel}\n viewportStart={viewportStart}\n />\n </div>\n </div>\n\n {/* Footer */}\n <TimelineFooter\n selectionType={selectionType}\n selections={state.selections}\n activeIndex={state.activeIndex}\n zoomLevel={zoomLevel}\n onZoomIn={onZoomIn}\n onZoomOut={onZoomOut}\n onHelpToggle={() => setHelpOpen((v) => !v)}\n helpOpen={helpOpen}\n />\n\n {/* Help popover */}\n {helpOpen && (\n <HelpPopover\n selectionType={selectionType}\n onClose={() => setHelpOpen(false)}\n />\n )}\n </div>\n );\n}\n","// Pure layout helpers for the timeline visual components.\n// No React/DOM dependencies.\n\n/**\n * Convert a time (seconds) to a pixel position within the container.\n *\n * @param time - Time in seconds\n * @param duration - Total media duration in seconds\n * @param containerWidth - Width of the waveform area in pixels\n * @param zoomLevel - 1 = full duration visible, 2 = half visible, etc.\n * @param viewportStart - Left edge of the visible region in seconds\n */\nexport function timeToPixel(\n time: number,\n duration: number,\n containerWidth: number,\n zoomLevel: number,\n viewportStart: number,\n): number {\n const visibleDuration = duration / zoomLevel;\n const pixelsPerSecond = containerWidth / visibleDuration;\n return (time - viewportStart) * pixelsPerSecond;\n}\n\n/**\n * Convert a pixel position to a time (seconds). Inverse of timeToPixel.\n */\nexport function pixelToTime(\n pixel: number,\n duration: number,\n containerWidth: number,\n zoomLevel: number,\n viewportStart: number,\n): number {\n const visibleDuration = duration / zoomLevel;\n const pixelsPerSecond = containerWidth / visibleDuration;\n return pixel / pixelsPerSecond + viewportStart;\n}\n\n/**\n * Choose an appropriate tick interval (seconds) based on pixels-per-second.\n * Higher pixel density (more zoomed in) → finer ticks.\n * Returns the smallest interval whose pixel spacing is still >= 60px.\n */\nexport function computeTickInterval(pixelsPerSecond: number): number {\n const candidates = [0.1, 0.5, 1, 5, 10, 30, 60];\n // Walk from finest to coarsest, return first with adequate spacing\n for (let i = 0; i < candidates.length; i++) {\n const interval = candidates[i];\n const tickSpacing = interval * pixelsPerSecond;\n if (tickSpacing >= 60) return interval;\n }\n return 60;\n}\n\n/**\n * Generate tick positions (in seconds) within a visible time range.\n *\n * @param visibleStart - Start of visible range in seconds\n * @param visibleEnd - End of visible range in seconds\n * @param interval - Tick spacing in seconds\n */\nexport function generateTicks(\n visibleStart: number,\n visibleEnd: number,\n interval: number,\n): number[] {\n const ticks: number[] = [];\n // Align first tick to interval boundary\n const firstTick = Math.ceil(visibleStart / interval) * interval;\n for (let t = firstTick; t <= visibleEnd + interval * 0.001; t += interval) {\n ticks.push(Math.round(t * 1000) / 1000); // avoid floating point drift\n }\n return ticks;\n}\n","import React from \"react\";\nimport { formatTime } from \"../../../utils/formatTime.js\";\nimport {\n timeToPixel,\n computeTickInterval,\n generateTicks,\n} from \"./timelineLayout.js\";\n\nexport interface TimeRulerProps {\n /** Total media duration in seconds. */\n duration: number;\n /** Width of the ruler area in pixels. */\n width: number;\n /** Current zoom level (1 = full duration visible). */\n zoomLevel: number;\n /** Left edge of the visible region in seconds. */\n viewportStart: number;\n}\n\nconst RULER_HEIGHT = 24;\n\n/**\n * Time labels and tick marks along the top of the waveform area.\n * Tick density adapts to zoom level.\n */\nexport function TimeRuler({\n duration,\n width,\n zoomLevel,\n viewportStart,\n}: TimeRulerProps) {\n if (!Number.isFinite(duration) || duration <= 0 || width <= 0) {\n return (\n <div\n data-testid=\"time-ruler\"\n style={{ height: `${String(RULER_HEIGHT)}px` }}\n />\n );\n }\n\n const visibleDuration = duration / zoomLevel;\n const visibleEnd = viewportStart + visibleDuration;\n const pixelsPerSecond = width / visibleDuration;\n const interval = computeTickInterval(pixelsPerSecond);\n const ticks = generateTicks(viewportStart, visibleEnd, interval);\n\n return (\n <div\n data-testid=\"time-ruler\"\n style={{\n position: \"relative\",\n height: `${String(RULER_HEIGHT)}px`,\n width: `${String(width)}px`,\n overflow: \"hidden\",\n fontSize: \"0.625rem\",\n color: \"var(--stagebook-muted, #9ca3af)\",\n userSelect: \"none\",\n }}\n >\n {ticks.map((t) => {\n const x = timeToPixel(t, duration, width, zoomLevel, viewportStart);\n if (x < -50 || x > width + 50) return null;\n return (\n <div\n key={t}\n style={{\n position: \"absolute\",\n left: `${String(x)}px`,\n top: 0,\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n }}\n >\n <span\n style={{\n whiteSpace: \"nowrap\",\n transform: \"translateX(-50%)\",\n display: \"block\",\n }}\n >\n {formatTime(t)}\n </span>\n <div\n style={{\n width: \"1px\",\n height: \"6px\",\n background: \"currentColor\",\n opacity: 0.5,\n }}\n />\n </div>\n );\n })}\n </div>\n );\n}\n","import React, { useRef, useEffect } from \"react\";\n\nexport interface WaveformRendererProps {\n /** Interleaved min/max pairs per time bucket. */\n peaks: Float32Array | null;\n /**\n * Render token. The peaks array is mutated in place during capture, so\n * its reference doesn't change. Bumping this counter (in MediaPlayer's\n * RAF accumulation loop, polled by Timeline) tells this component to\n * redraw with the new data.\n */\n peaksVersion: number;\n /** Width of the canvas in CSS pixels. */\n width: number;\n /** Height of the canvas in CSS pixels. */\n height: number;\n /** Index of the first visible bucket. */\n startBucket: number;\n /** Index of the last visible bucket (exclusive). */\n endBucket: number;\n}\n\n/**\n * Pure canvas waveform renderer. Draws min/max amplitude bars centered\n * on the horizontal midline. Handles empty/partial peaks gracefully.\n */\nexport function WaveformRenderer({\n peaks,\n peaksVersion,\n width,\n height,\n startBucket,\n endBucket,\n}: WaveformRendererProps) {\n const canvasRef = useRef<HTMLCanvasElement>(null);\n\n useEffect(() => {\n const canvas = canvasRef.current;\n if (!canvas) return;\n\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) return;\n\n const dpr = window.devicePixelRatio || 1;\n canvas.width = width * dpr;\n canvas.height = height * dpr;\n // Use setTransform (not scale) so repeated draws don't compound the\n // DPR scaling on top of itself.\n ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n\n ctx.clearRect(0, 0, width, height);\n\n if (!peaks || endBucket <= startBucket) return;\n\n const visibleBuckets = endBucket - startBucket;\n const barWidth = width / visibleBuckets;\n const midY = height / 2;\n\n ctx.fillStyle =\n getComputedStyle(canvas).getPropertyValue(\"--stagebook-waveform-color\") ||\n \"#6b7280\";\n\n for (let i = 0; i < visibleBuckets; i++) {\n const bucketIdx = startBucket + i;\n const minIdx = bucketIdx * 2;\n const maxIdx = bucketIdx * 2 + 1;\n\n const minVal = peaks[minIdx];\n const maxVal = peaks[maxIdx];\n\n // Skip unfilled buckets (sentinel: min=1, max=-1)\n if (minVal === undefined || maxVal === undefined || minVal > maxVal)\n continue;\n\n // Map [-1, 1] amplitude to pixel offset from midline\n const topOffset = maxVal * midY;\n const bottomOffset = minVal * midY;\n\n const x = i * barWidth;\n const barTop = midY - topOffset;\n const barHeight = topOffset - bottomOffset;\n\n ctx.fillRect(\n x,\n barTop,\n Math.max(barWidth - 0.5, 1),\n Math.max(barHeight, 1),\n );\n }\n }, [peaks, peaksVersion, width, height, startBucket, endBucket]);\n\n return (\n <canvas\n ref={canvasRef}\n data-testid=\"waveform-canvas\"\n style={{\n width: `${String(width)}px`,\n height: `${String(height)}px`,\n display: \"block\",\n }}\n />\n );\n}\n","import React from \"react\";\nimport { WaveformRenderer } from \"./WaveformRenderer.js\";\n\nexport interface TimelineTrackProps {\n /** Label shown in the gutter (from trackLabels or \"Position N\"). */\n label: string;\n /** Interleaved min/max peaks for this channel. */\n peaks: Float32Array | null;\n /**\n * Render token: bumps when peaks are mutated in place. Forces the\n * WaveformRenderer to redraw despite a stable array reference.\n */\n peaksVersion: number;\n /** Width of the waveform area in pixels (excludes gutter). */\n waveformWidth: number;\n /** Height of this track in pixels. */\n height: number;\n /** First visible bucket index. */\n startBucket: number;\n /** Last visible bucket index (exclusive). */\n endBucket: number;\n}\n\nconst GUTTER_WIDTH = 72;\n\n/**\n * One row in the timeline: a fixed-width gutter label + a WaveformRenderer.\n */\nexport function TimelineTrack({\n label,\n peaks,\n peaksVersion,\n waveformWidth,\n height,\n startBucket,\n endBucket,\n}: TimelineTrackProps) {\n return (\n <div\n data-testid=\"timeline-track\"\n style={{\n display: \"flex\",\n alignItems: \"stretch\",\n height: `${String(height)}px`,\n }}\n >\n <div\n data-testid=\"track-label\"\n style={{\n width: `${String(GUTTER_WIDTH)}px`,\n minWidth: `${String(GUTTER_WIDTH)}px`,\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"flex-end\",\n paddingRight: \"0.5rem\",\n fontSize: \"0.6875rem\",\n color: \"var(--stagebook-muted, #9ca3af)\",\n userSelect: \"none\",\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n whiteSpace: \"nowrap\",\n borderRight: \"1px solid var(--stagebook-border, #e5e7eb)\",\n }}\n >\n {label}\n </div>\n <WaveformRenderer\n peaks={peaks}\n peaksVersion={peaksVersion}\n width={waveformWidth}\n height={height}\n startBucket={startBucket}\n endBucket={endBucket}\n />\n </div>\n );\n}\n\nexport { GUTTER_WIDTH };\n","import React from \"react\";\nimport { timeToPixel } from \"./timelineLayout.js\";\n\nexport interface PlayheadProps {\n /** Current playback time in seconds. */\n currentTime: number;\n /** Total media duration in seconds. */\n duration: number;\n /** Width of the waveform area in pixels. */\n width: number;\n /** Height to span (all tracks). */\n height: number;\n /** Current zoom level (1 = full duration visible). */\n zoomLevel: number;\n /** Left edge of the visible region in seconds. */\n viewportStart: number;\n}\n\n/**\n * Thin vertical line tracking the current playback position.\n * Absolutely positioned over the waveform area.\n */\nexport function Playhead({\n currentTime,\n duration,\n width,\n height,\n zoomLevel,\n viewportStart,\n}: PlayheadProps) {\n if (!Number.isFinite(duration) || duration <= 0) return null;\n\n const x = timeToPixel(currentTime, duration, width, zoomLevel, viewportStart);\n\n // Don't render if off-screen\n if (x < -1 || x > width + 1) return null;\n\n return (\n <div\n data-testid=\"playhead\"\n style={{\n position: \"absolute\",\n left: `${String(x)}px`,\n top: 0,\n width: \"2px\",\n height: `${String(height)}px`,\n background: \"var(--stagebook-primary, #3b82f6)\",\n pointerEvents: \"none\",\n zIndex: 10,\n transform: \"translateX(-1px)\",\n }}\n />\n );\n}\n","import React, { useCallback, useRef, useState } from \"react\";\nimport { pixelToTime, timeToPixel } from \"./timelineLayout.js\";\nimport { clampToFreeGap } from \"./selections.js\";\nimport type { RangeSelection, TimelineValue } from \"./selections.js\";\n\nexport interface SelectionOverlayProps {\n /** Width of the waveform area in pixels (excludes gutter). */\n width: number;\n /** Height of the overlay in pixels (covers all tracks). */\n height: number;\n /** Total media duration in seconds. */\n duration: number;\n /** Current zoom level (1 = full duration visible). */\n zoomLevel: number;\n /** Left edge of the visible region in seconds. */\n viewportStart: number;\n /** Selection type. */\n selectionType: \"range\" | \"point\";\n /** Selection scope. */\n selectionScope: \"track\" | \"all\";\n /** Number of tracks (for track-mode hit testing). */\n channelCount: number;\n /** Whether multiple selections are allowed. When false, new selections\n * replace existing ones — the drag preview should not clamp against\n * ranges that are about to be replaced. */\n multiSelect: boolean;\n /** Current selections from reducer state. */\n selections: TimelineValue;\n /** Index of the active/focused selection. */\n activeIndex: number | null;\n /** Active handle in range mode. */\n activeHandle: \"start\" | \"end\" | null;\n\n // ── Callbacks (Timeline.tsx wires these to dispatch + seek) ──\n onSeek: (time: number) => void;\n onCreateRange: (\n start: number,\n end: number,\n track: number | undefined,\n ) => void;\n onCreatePoint: (time: number, track: number | undefined) => void;\n /**\n * `noSnapshot=true` skips the undo snapshot — used for live drag\n * pointermove events so the entire drag collapses into one undo step.\n */\n onAdjustHandle: (\n index: number,\n handle: \"start\" | \"end\",\n time: number,\n noSnapshot?: boolean,\n ) => void;\n onRepositionPoint: (\n index: number,\n time: number,\n noSnapshot?: boolean,\n ) => void;\n onSelect: (index: number) => void;\n onDeselect: () => void;\n onSetActiveHandle: (handle: \"start\" | \"end\" | null) => void;\n /** Begin a drag transaction — pushes one undo snapshot, defers saves. */\n onBeginDrag: () => void;\n /** End a drag transaction — releases the save defer. */\n onEndDrag: () => void;\n /** Request the parent to focus its keyboard-event container. Called after\n * selection actions so keyboard shortcuts (arrows, Tab, Delete, Escape)\n * work immediately without the user manually clicking the timeline. */\n onRequestFocus: () => void;\n}\n\nconst DRAG_DEAD_ZONE_PX = 4;\n\ninterface DragState {\n startX: number;\n startTime: number;\n /** Did the mouse move beyond the dead zone? */\n isDragging: boolean;\n /** What kind of drag is in progress. */\n mode: \"create-range\" | \"adjust-handle\" | \"reposition-point\" | \"click\";\n /** For adjust-handle: which selection and which handle. */\n index?: number;\n handle?: \"start\" | \"end\";\n /** For all drags in track mode: which track. */\n track?: number;\n /**\n * Has this drag already pushed its undo snapshot via onBeginDrag?\n * Used to ensure each drag collapses to a single undo step.\n */\n beganDrag?: boolean;\n}\n\nfunction isRangeArray(s: TimelineValue): s is RangeSelection[] {\n return s.length === 0 || \"start\" in (s[0] as object);\n}\n\n/**\n * Renders all selections (ranges or points) and handles mouse/touch events\n * for creating, selecting, and editing them. Absolutely positioned over the\n * waveform area.\n */\nexport function SelectionOverlay({\n width,\n height,\n duration,\n zoomLevel,\n viewportStart,\n selectionType,\n selectionScope,\n channelCount,\n multiSelect,\n selections,\n activeIndex,\n activeHandle,\n onSeek,\n onCreateRange,\n onCreatePoint,\n onAdjustHandle,\n onRepositionPoint,\n onSelect,\n onDeselect,\n onSetActiveHandle,\n onBeginDrag,\n onEndDrag,\n onRequestFocus,\n}: SelectionOverlayProps) {\n const containerRef = useRef<HTMLDivElement>(null);\n const dragRef = useRef<DragState | null>(null);\n // Live drag preview for \"create-range\" before mouseup commits the range\n const [dragPreview, setDragPreview] = useState<{\n startTime: number;\n endTime: number;\n track: number | undefined;\n } | null>(null);\n\n const trackHeight = channelCount > 0 ? height / channelCount : height;\n\n const eventToTime = useCallback(\n (clientX: number) => {\n const el = containerRef.current;\n if (!el) return 0;\n const rect = el.getBoundingClientRect();\n const localX = clientX - rect.left;\n return pixelToTime(localX, duration, width, zoomLevel, viewportStart);\n },\n [duration, width, zoomLevel, viewportStart],\n );\n\n const eventToTrack = useCallback(\n (clientY: number): number | undefined => {\n if (selectionScope !== \"track\") return undefined;\n const el = containerRef.current;\n if (!el) return undefined;\n const rect = el.getBoundingClientRect();\n const localY = clientY - rect.top;\n const trackIdx = Math.floor(localY / trackHeight);\n return Math.max(0, Math.min(channelCount - 1, trackIdx));\n },\n [selectionScope, trackHeight, channelCount],\n );\n\n // ── Pointer handlers (mouse + touch unified) ──\n\n /** Capture the pointer so drag gestures continue even if the pointer\n * leaves the overlay. Silently ignore failures in test environments\n * where the pointerId may not be a real OS pointer. */\n const capturePointer = useCallback((e: React.PointerEvent) => {\n try {\n e.currentTarget.setPointerCapture(e.pointerId);\n } catch {\n // ignore\n }\n }, []);\n\n const releasePointer = useCallback((e: React.PointerEvent) => {\n try {\n e.currentTarget.releasePointerCapture(e.pointerId);\n } catch {\n // ignore\n }\n }, []);\n\n const handlePointerDown = useCallback(\n (e: React.PointerEvent) => {\n if (e.button !== 0) return;\n capturePointer(e);\n const time = eventToTime(e.clientX);\n const track = eventToTrack(e.clientY);\n dragRef.current = {\n startX: e.clientX,\n startTime: time,\n isDragging: false,\n mode: \"click\",\n track,\n };\n },\n [eventToTime, eventToTrack, capturePointer],\n );\n\n const handlePointerMove = useCallback(\n (e: React.PointerEvent) => {\n const drag = dragRef.current;\n if (!drag) return;\n\n const dx = Math.abs(e.clientX - drag.startX);\n if (!drag.isDragging && dx < DRAG_DEAD_ZONE_PX) return;\n\n if (!drag.isDragging) {\n drag.isDragging = true;\n if (drag.mode === \"click\") {\n drag.mode =\n selectionType === \"range\" ? \"create-range\" : \"reposition-point\";\n }\n }\n\n const currentTime = eventToTime(e.clientX);\n\n if (drag.mode === \"create-range\") {\n setDragPreview({\n startTime: drag.startTime,\n endTime: currentTime,\n track: drag.track,\n });\n } else if (\n drag.mode === \"adjust-handle\" &&\n drag.index !== undefined &&\n drag.handle\n ) {\n // First move of an adjust-handle drag: snapshot once, then defer\n // saves until pointerup. Subsequent moves use noSnapshot=true so\n // the entire drag collapses to one undo step.\n if (!drag.beganDrag) {\n drag.beganDrag = true;\n onBeginDrag();\n }\n onAdjustHandle(drag.index, drag.handle, currentTime, true);\n } else if (drag.mode === \"reposition-point\" && drag.index !== undefined) {\n if (!drag.beganDrag) {\n drag.beganDrag = true;\n onBeginDrag();\n }\n onRepositionPoint(drag.index, currentTime, true);\n }\n },\n [\n eventToTime,\n onAdjustHandle,\n onRepositionPoint,\n onBeginDrag,\n selectionType,\n ],\n );\n\n const handlePointerUp = useCallback(\n (e: React.PointerEvent) => {\n releasePointer(e);\n const drag = dragRef.current;\n if (!drag) return;\n\n const time = eventToTime(e.clientX);\n const track = drag.track;\n\n if (!drag.isDragging) {\n if (selectionType === \"point\") {\n onCreatePoint(time, track);\n onSeek(time);\n onRequestFocus();\n } else {\n if (activeIndex !== null) {\n onDeselect();\n }\n onSeek(time);\n }\n } else if (drag.mode === \"create-range\") {\n const start = Math.min(drag.startTime, time);\n const end = Math.max(drag.startTime, time);\n if (end - start > 0) {\n onCreateRange(start, end, track);\n onRequestFocus();\n }\n setDragPreview(null);\n }\n\n // Release the save defer for adjust-handle / reposition-point drags\n if (drag.beganDrag) {\n onEndDrag();\n onRequestFocus();\n }\n dragRef.current = null;\n },\n [\n eventToTime,\n selectionType,\n activeIndex,\n onCreatePoint,\n onSeek,\n onDeselect,\n onCreateRange,\n onEndDrag,\n onRequestFocus,\n releasePointer,\n ],\n );\n\n const handleRangeBodyPointerDown = useCallback(\n (e: React.PointerEvent, index: number) => {\n e.stopPropagation();\n onSelect(index);\n onRequestFocus();\n dragRef.current = null;\n },\n [onSelect, onRequestFocus],\n );\n\n const handleHandlePointerDown = useCallback(\n (e: React.PointerEvent, index: number, handle: \"start\" | \"end\") => {\n e.stopPropagation();\n if (e.button !== 0) return;\n // Capture on the overlay container (parent), not the handle itself,\n // so pointermove/pointerup keep flowing to the overlay during drag.\n const overlay = containerRef.current;\n if (overlay) {\n try {\n overlay.setPointerCapture(e.pointerId);\n } catch {\n // ignore in test environments\n }\n }\n onSelect(index);\n onSetActiveHandle(handle);\n const time = eventToTime(e.clientX);\n dragRef.current = {\n startX: e.clientX,\n startTime: time,\n isDragging: false,\n mode: \"adjust-handle\",\n index,\n handle,\n };\n },\n [eventToTime, onSelect, onSetActiveHandle],\n );\n\n const handlePointPointerDown = useCallback(\n (e: React.PointerEvent, index: number) => {\n e.stopPropagation();\n if (e.button !== 0) return;\n const overlay = containerRef.current;\n if (overlay) {\n try {\n overlay.setPointerCapture(e.pointerId);\n } catch {\n // ignore\n }\n }\n onSelect(index);\n const time = eventToTime(e.clientX);\n dragRef.current = {\n startX: e.clientX,\n startTime: time,\n isDragging: false,\n mode: \"reposition-point\",\n index,\n };\n },\n [eventToTime, onSelect],\n );\n\n const handlePointerCancel = useCallback(\n (e: React.PointerEvent) => {\n releasePointer(e);\n if (dragRef.current?.beganDrag) onEndDrag();\n dragRef.current = null;\n setDragPreview(null);\n },\n [onEndDrag, releasePointer],\n );\n\n // ── Render ──\n\n const renderRanges = () => {\n if (!isRangeArray(selections)) return null;\n return selections.map((range, i) => {\n const isActive = i === activeIndex;\n const x1 = timeToPixel(\n range.start,\n duration,\n width,\n zoomLevel,\n viewportStart,\n );\n const x2 = timeToPixel(\n range.end,\n duration,\n width,\n zoomLevel,\n viewportStart,\n );\n const left = Math.min(x1, x2);\n const rangeWidth = Math.abs(x2 - x1);\n\n // Per-track positioning in track scope\n const top =\n selectionScope === \"track\" && range.track !== undefined\n ? range.track * trackHeight\n : 0;\n const rangeHeight = selectionScope === \"track\" ? trackHeight : height;\n\n return (\n <div\n key={`range-${String(i)}`}\n data-testid={`range-${String(i)}`}\n data-active={isActive}\n onPointerDown={(e) => handleRangeBodyPointerDown(e, i)}\n style={{\n position: \"absolute\",\n left: `${String(left)}px`,\n top: `${String(top)}px`,\n width: `${String(rangeWidth)}px`,\n height: `${String(rangeHeight)}px`,\n background: isActive\n ? \"rgba(59, 130, 246, 0.35)\"\n : \"rgba(59, 130, 246, 0.18)\",\n border: isActive\n ? \"1px solid rgba(59, 130, 246, 0.9)\"\n : \"1px solid rgba(59, 130, 246, 0.4)\",\n boxSizing: \"border-box\",\n cursor: \"pointer\",\n pointerEvents: \"auto\",\n }}\n >\n {/* Start handle */}\n <div\n data-testid={`range-${String(i)}-handle-start`}\n data-active={isActive && activeHandle === \"start\"}\n onPointerDown={(e) => handleHandlePointerDown(e, i, \"start\")}\n style={{\n position: \"absolute\",\n left: -3,\n top: 0,\n width: 6,\n height: \"100%\",\n background:\n isActive && activeHandle === \"start\"\n ? \"rgba(37, 99, 235, 1)\"\n : \"rgba(59, 130, 246, 0.7)\",\n cursor: \"ew-resize\",\n }}\n />\n {/* End handle */}\n <div\n data-testid={`range-${String(i)}-handle-end`}\n data-active={isActive && activeHandle === \"end\"}\n onPointerDown={(e) => handleHandlePointerDown(e, i, \"end\")}\n style={{\n position: \"absolute\",\n right: -3,\n top: 0,\n width: 6,\n height: \"100%\",\n background:\n isActive && activeHandle === \"end\"\n ? \"rgba(37, 99, 235, 1)\"\n : \"rgba(59, 130, 246, 0.7)\",\n cursor: \"ew-resize\",\n }}\n />\n </div>\n );\n });\n };\n\n const renderPoints = () => {\n if (isRangeArray(selections)) return null;\n return selections.map((point, i) => {\n const isActive = i === activeIndex;\n const x = timeToPixel(\n point.time,\n duration,\n width,\n zoomLevel,\n viewportStart,\n );\n const top =\n selectionScope === \"track\" && point.track !== undefined\n ? point.track * trackHeight\n : 0;\n const pointHeight = selectionScope === \"track\" ? trackHeight : height;\n return (\n <div\n key={`point-${String(i)}`}\n data-testid={`point-${String(i)}`}\n data-active={isActive}\n onPointerDown={(e) => handlePointPointerDown(e, i)}\n style={{\n position: \"absolute\",\n left: `${String(x - 5)}px`,\n top: `${String(top)}px`,\n width: 10,\n height: `${String(pointHeight)}px`,\n cursor: \"pointer\",\n pointerEvents: \"auto\",\n }}\n >\n <div\n style={{\n position: \"absolute\",\n left: 4,\n top: 0,\n width: 2,\n height: \"100%\",\n background: isActive\n ? \"rgba(37, 99, 235, 1)\"\n : \"rgba(59, 130, 246, 0.8)\",\n }}\n />\n <div\n style={{\n position: \"absolute\",\n left: 0,\n top: -2,\n width: 10,\n height: 10,\n borderRadius: \"50%\",\n background: isActive\n ? \"rgba(37, 99, 235, 1)\"\n : \"rgba(59, 130, 246, 0.9)\",\n }}\n />\n </div>\n );\n });\n };\n\n const renderDragPreview = () => {\n if (!dragPreview) return null;\n\n // Clamp the preview to free space so it doesn't visually overlap existing\n // ranges — matching the clamping that will happen on commit (pointerup).\n // When multiSelect is false the new range replaces all existing ones, so\n // there's nothing to clamp against.\n const existing = multiSelect && isRangeArray(selections) ? selections : [];\n const clamped = clampToFreeGap(\n dragPreview.startTime,\n dragPreview.endTime,\n dragPreview.track,\n existing,\n );\n if (!clamped) return null; // no free space\n\n const x1 = timeToPixel(\n clamped.start,\n duration,\n width,\n zoomLevel,\n viewportStart,\n );\n const x2 = timeToPixel(\n clamped.end,\n duration,\n width,\n zoomLevel,\n viewportStart,\n );\n const left = Math.min(x1, x2);\n const previewWidth = Math.abs(x2 - x1);\n const top =\n selectionScope === \"track\" && dragPreview.track !== undefined\n ? dragPreview.track * trackHeight\n : 0;\n const previewHeight = selectionScope === \"track\" ? trackHeight : height;\n return (\n <div\n data-testid=\"range-drag-preview\"\n style={{\n position: \"absolute\",\n left: `${String(left)}px`,\n top: `${String(top)}px`,\n width: `${String(previewWidth)}px`,\n height: `${String(previewHeight)}px`,\n background: \"rgba(59, 130, 246, 0.25)\",\n border: \"1px dashed rgba(59, 130, 246, 0.6)\",\n boxSizing: \"border-box\",\n pointerEvents: \"none\",\n }}\n />\n );\n };\n\n return (\n <div\n ref={containerRef}\n data-testid=\"selection-overlay\"\n onPointerDown={handlePointerDown}\n onPointerMove={handlePointerMove}\n onPointerUp={handlePointerUp}\n onPointerCancel={handlePointerCancel}\n onPointerLeave={() => {\n // With pointer capture active, this only fires for uncaptured\n // interactions (e.g., hover without mousedown). For captured drags,\n // pointerup/pointercancel handle cleanup instead.\n if (dragRef.current) {\n if (dragRef.current.beganDrag) onEndDrag();\n dragRef.current = null;\n setDragPreview(null);\n }\n }}\n style={{\n position: \"absolute\",\n inset: 0,\n cursor: \"crosshair\",\n pointerEvents: \"auto\",\n }}\n >\n {selectionType === \"range\" ? renderRanges() : renderPoints()}\n {renderDragPreview()}\n </div>\n );\n}\n","// Selection data model for Timeline component.\n// Pure TypeScript — no React/DOM dependencies.\n\nexport interface RangeSelection {\n track?: number;\n start: number;\n end: number;\n}\n\nexport interface PointSelection {\n track?: number;\n time: number;\n}\n\nexport type TimelineValue = RangeSelection[] | PointSelection[];\n\nexport interface SelectionSnapshot {\n selections: TimelineValue;\n}\n\nconst MAX_UNDO_DEPTH = 50;\n\n// ---------------------------------------------------------------------------\n// Sorting\n// ---------------------------------------------------------------------------\n\nexport function sortRanges(selections: RangeSelection[]): RangeSelection[] {\n return [...selections].sort((a, b) => a.start - b.start);\n}\n\nexport function sortPoints(selections: PointSelection[]): PointSelection[] {\n return [...selections].sort((a, b) => a.time - b.time);\n}\n\n// ---------------------------------------------------------------------------\n// Free-gap clamping (ranges only)\n// ---------------------------------------------------------------------------\n\n/** Filter to ranges on the same track (or all if track is undefined). */\nfunction sameScope(\n existing: RangeSelection[],\n track: number | undefined,\n): RangeSelection[] {\n if (track === undefined) return existing;\n return existing.filter((r) => r.track === track);\n}\n\n/**\n * Clamp a proposed [start, end] to the free gap in the given scope.\n * Returns { start, end } or null if no free space exists.\n */\nexport function clampToFreeGap(\n start: number,\n end: number,\n track: number | undefined,\n existing: RangeSelection[],\n): { start: number; end: number } | null {\n const scoped = sortRanges(sameScope(existing, track));\n\n // Normalize so start <= end\n const lo = Math.min(start, end);\n const hi = Math.max(start, end);\n\n // Find the tightest bounds: the latest-ending range that starts before our\n // midpoint (left neighbor) and the earliest-starting range that ends after\n // our midpoint (right neighbor).\n let leftBound = -Infinity;\n let rightBound = Infinity;\n\n for (const r of scoped) {\n // Range ends at or before our start — it's a left neighbor candidate\n if (r.end <= lo) {\n leftBound = Math.max(leftBound, r.end);\n }\n // Range starts at or after our end — it's a right neighbor candidate\n else if (r.start >= hi) {\n rightBound = Math.min(rightBound, r.start);\n break; // sorted, so first one is the closest\n }\n // Range overlaps our proposed interval — clamp to its edges\n else {\n if (r.start > lo) {\n rightBound = Math.min(rightBound, r.start);\n }\n if (r.end < hi) {\n leftBound = Math.max(leftBound, r.end);\n }\n if (r.start <= lo && r.end >= hi) {\n // Fully enclosed by an existing range — no space\n return null;\n }\n }\n }\n\n const clampedStart = Math.max(lo, leftBound);\n const clampedEnd = Math.min(hi, rightBound);\n\n if (clampedStart >= clampedEnd) return null;\n\n return { start: clampedStart, end: clampedEnd };\n}\n\n// ---------------------------------------------------------------------------\n// Range creation\n// ---------------------------------------------------------------------------\n\nexport function createRange(\n start: number,\n end: number,\n track: number | undefined,\n existing: RangeSelection[],\n): RangeSelection | null {\n const clamped = clampToFreeGap(start, end, track, existing);\n if (!clamped) return null;\n\n const range: RangeSelection = {\n start: clamped.start,\n end: clamped.end,\n };\n if (track !== undefined) range.track = track;\n return range;\n}\n\n// ---------------------------------------------------------------------------\n// Handle adjustment\n// ---------------------------------------------------------------------------\n\nexport function adjustHandle(\n selections: RangeSelection[],\n index: number,\n handle: \"start\" | \"end\",\n newTime: number,\n): RangeSelection[] {\n const result = selections.map((s) => ({ ...s }));\n const target = result[index];\n if (!target) return result;\n\n const scoped = sameScope(\n result.filter((_, i) => i !== index),\n target.track,\n );\n const sorted = sortRanges(scoped);\n\n if (handle === \"start\") {\n let clampedTime = newTime;\n\n // Can't go past end handle\n clampedTime = Math.min(clampedTime, target.end);\n\n // Can't go into previous neighbor\n for (let i = sorted.length - 1; i >= 0; i--) {\n const neighbor = sorted[i];\n if (\n neighbor &&\n (neighbor.end <= target.start || neighbor.end <= clampedTime)\n ) {\n clampedTime = Math.max(clampedTime, neighbor.end);\n break;\n }\n }\n\n target.start = clampedTime;\n } else {\n let clampedTime = newTime;\n\n // Can't go past start handle\n clampedTime = Math.max(clampedTime, target.start);\n\n // Can't go into next neighbor\n for (const r of sorted) {\n if (r.start >= target.end || r.start >= clampedTime) {\n clampedTime = Math.min(clampedTime, r.start);\n break;\n }\n }\n\n target.end = clampedTime;\n }\n\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Point creation / repositioning\n// ---------------------------------------------------------------------------\n\nexport function createPoint(\n time: number,\n track: number | undefined,\n): PointSelection {\n const point: PointSelection = { time };\n if (track !== undefined) point.track = track;\n return point;\n}\n\nexport function repositionPoint(\n selections: PointSelection[],\n index: number,\n newTime: number,\n): PointSelection[] {\n return selections.map((s, i) => (i === index ? { ...s, time: newTime } : s));\n}\n\n// ---------------------------------------------------------------------------\n// Deletion\n// ---------------------------------------------------------------------------\n\nexport function deleteSelection<T extends RangeSelection | PointSelection>(\n selections: T[],\n index: number,\n): T[] {\n return selections.filter((_, i) => i !== index);\n}\n\n// ---------------------------------------------------------------------------\n// multiSelect enforcement\n// ---------------------------------------------------------------------------\n\nexport function enforceMultiSelect<T extends RangeSelection | PointSelection>(\n selections: T[],\n multiSelect: boolean,\n): T[] {\n if (multiSelect || selections.length <= 1) return selections;\n // Keep only the last (newest) selection\n const last = selections[selections.length - 1];\n return last ? [last] : [];\n}\n\n// ---------------------------------------------------------------------------\n// Undo stack\n// ---------------------------------------------------------------------------\n\nexport function pushUndo(\n stack: SelectionSnapshot[],\n current: TimelineValue,\n): SelectionSnapshot[] {\n const next = [...stack, { selections: [...current] }];\n if (next.length > MAX_UNDO_DEPTH) {\n return next.slice(next.length - MAX_UNDO_DEPTH);\n }\n return next;\n}\n\nexport function popUndo(\n stack: SelectionSnapshot[],\n): { restored: TimelineValue; newStack: SelectionSnapshot[] } | null {\n const last = stack[stack.length - 1];\n if (!last) return null;\n const newStack = stack.slice(0, -1);\n return { restored: last.selections, newStack };\n}\n","// Pure viewport math for the Timeline. Zoom level + viewport start handling,\n// auto-scroll threshold, seek snap, etc. No React/DOM deps.\n\nexport const MIN_ZOOM = 1;\nexport const MAX_ZOOM = 32;\n\n/** Default fraction of viewport width at which auto-scroll begins. */\nexport const AUTO_SCROLL_THRESHOLD = 0.9;\n\n/** Where the playhead lands within the viewport after a seek snap (0..1). */\nexport const SEEK_SNAP_POSITION = 0.25;\n\n/**\n * Minimum delta (seconds) between RAF ticks that counts as a \"seek jump\"\n * rather than continuous playback. When the playhead moves by more than\n * this in a single tick, the viewport snaps rather than scrolling smoothly.\n *\n * At normal 1x playback with 60fps RAF, each tick is ~0.017s. At 2x it's\n * ~0.033s. A threshold of 1.5s provides a wide margin above both while\n * still catching all scrub-bar and keyboard seeks (which move 1s+ at once).\n */\nexport const SEEK_JUMP_THRESHOLD = 1.5;\n\n/**\n * Clamp viewport start so the visible region stays inside [0, duration].\n */\nexport function clampViewportStart(\n start: number,\n duration: number,\n zoomLevel: number,\n): number {\n const visibleDuration = duration / zoomLevel;\n const max = Math.max(0, duration - visibleDuration);\n if (start < 0) return 0;\n if (start > max) return max;\n return start;\n}\n\n/**\n * Double the zoom level, capped at MAX_ZOOM.\n */\nexport function zoomIn(currentZoom: number): number {\n return Math.min(currentZoom * 2, MAX_ZOOM);\n}\n\n/**\n * Halve the zoom level, capped at MIN_ZOOM.\n */\nexport function zoomOut(currentZoom: number): number {\n return Math.max(currentZoom / 2, MIN_ZOOM);\n}\n\ninterface ZoomViewportArgs {\n currentZoom: number;\n newZoom: number;\n duration: number;\n currentViewportStart: number;\n playheadTime: number;\n}\n\n/**\n * Compute the new viewport start after zooming. Centers on the playhead\n * if it's within the current viewport, otherwise centers on the viewport\n * midpoint. Clamps to valid range.\n */\nexport function computeViewportAfterZoom(args: ZoomViewportArgs): number {\n const { currentZoom, newZoom, duration, currentViewportStart, playheadTime } =\n args;\n\n if (newZoom <= 1) return 0;\n\n const currentVisible = duration / currentZoom;\n const newVisible = duration / newZoom;\n const viewportEnd = currentViewportStart + currentVisible;\n\n // Center on playhead if it's in the current viewport, else viewport center\n const playheadInView =\n playheadTime >= currentViewportStart && playheadTime <= viewportEnd;\n const center = playheadInView\n ? playheadTime\n : currentViewportStart + currentVisible / 2;\n\n const newStart = center - newVisible / 2;\n return clampViewportStart(newStart, duration, newZoom);\n}\n\n/**\n * Returns true if the playhead is at or past the auto-scroll threshold\n * (default 90% of viewport width).\n */\nexport function isPlayheadPastThreshold(\n playheadTime: number,\n viewportStart: number,\n visibleDuration: number,\n threshold = AUTO_SCROLL_THRESHOLD,\n): boolean {\n return playheadTime >= viewportStart + visibleDuration * threshold;\n}\n\n/**\n * Compute the new viewport start to keep the playhead pinned at the\n * threshold position (e.g., 90%) during continuous playback.\n */\nexport function computeViewportAfterScroll(\n playheadTime: number,\n visibleDuration: number,\n duration: number,\n threshold = AUTO_SCROLL_THRESHOLD,\n): number {\n const newStart = playheadTime - visibleDuration * threshold;\n // Note: using zoomLevel = duration / visibleDuration so clamp can compute max\n const zoomLevel = duration / visibleDuration;\n return clampViewportStart(newStart, duration, zoomLevel);\n}\n\n/**\n * Compute the new viewport start to snap the playhead to a target position\n * (e.g., 25% from the left edge) after a seek/scrub.\n */\nexport function computeViewportAfterSeek(\n playheadTime: number,\n visibleDuration: number,\n duration: number,\n snapPosition = SEEK_SNAP_POSITION,\n): number {\n const newStart = playheadTime - visibleDuration * snapPosition;\n const zoomLevel = duration / visibleDuration;\n return clampViewportStart(newStart, duration, zoomLevel);\n}\n","import React from \"react\";\nimport { formatTime } from \"../../../utils/formatTime.js\";\nimport type { TimelineValue } from \"./selections.js\";\nimport { MIN_ZOOM, MAX_ZOOM } from \"./viewport.js\";\n\nexport interface TimelineFooterProps {\n selectionType: \"range\" | \"point\";\n selections: TimelineValue;\n activeIndex: number | null;\n zoomLevel: number;\n onZoomIn: () => void;\n onZoomOut: () => void;\n onHelpToggle: () => void;\n helpOpen: boolean;\n}\n\nfunction isRangeArray(\n s: TimelineValue,\n): s is { start: number; end: number; track?: number }[] {\n return s.length === 0 || \"start\" in (s[0] as object);\n}\n\nfunction summary(\n selectionType: \"range\" | \"point\",\n selections: TimelineValue,\n activeIndex: number | null,\n): string {\n const count = selections.length;\n // Active selection time readout takes precedence\n if (activeIndex !== null) {\n const item = selections[activeIndex];\n if (item) {\n if (isRangeArray(selections)) {\n const r = selections[activeIndex];\n if (r) return `${formatTime(r.start)} – ${formatTime(r.end)}`;\n } else {\n const p = (selections as { time: number }[])[activeIndex];\n if (p) return formatTime(p.time);\n }\n }\n }\n if (selectionType === \"range\") {\n if (count === 0) return \"0 ranges selected\";\n if (count === 1) return \"1 range selected\";\n return `${String(count)} ranges selected`;\n }\n if (count === 0) return \"0 points marked\";\n if (count === 1) return \"1 point marked\";\n return `${String(count)} points marked`;\n}\n\nconst buttonStyle: React.CSSProperties = {\n width: \"24px\",\n height: \"24px\",\n display: \"inline-flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n border: \"1px solid var(--stagebook-border, #e5e7eb)\",\n borderRadius: \"0.25rem\",\n background: \"var(--stagebook-bg, #ffffff)\",\n cursor: \"pointer\",\n fontSize: \"0.875rem\",\n lineHeight: 1,\n padding: 0,\n color: \"inherit\",\n};\n\nconst disabledStyle: React.CSSProperties = {\n ...buttonStyle,\n cursor: \"not-allowed\",\n opacity: 0.4,\n};\n\nexport function TimelineFooter({\n selectionType,\n selections,\n activeIndex,\n zoomLevel,\n onZoomIn,\n onZoomOut,\n onHelpToggle,\n helpOpen,\n}: TimelineFooterProps) {\n return (\n <div\n data-testid=\"timeline-footer\"\n style={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"space-between\",\n padding: \"0.25rem 0.5rem\",\n borderTop: \"1px solid var(--stagebook-border, #e5e7eb)\",\n fontSize: \"0.75rem\",\n color: \"var(--stagebook-muted, #6b7280)\",\n userSelect: \"none\",\n }}\n >\n {/* Left: zoom controls */}\n <div style={{ display: \"flex\", alignItems: \"center\", gap: \"0.25rem\" }}>\n <button\n type=\"button\"\n data-testid=\"timeline-zoom-out\"\n onClick={onZoomOut}\n disabled={zoomLevel <= MIN_ZOOM}\n aria-label=\"Zoom out\"\n style={zoomLevel <= MIN_ZOOM ? disabledStyle : buttonStyle}\n >\n −\n </button>\n <button\n type=\"button\"\n data-testid=\"timeline-zoom-in\"\n onClick={onZoomIn}\n disabled={zoomLevel >= MAX_ZOOM}\n aria-label=\"Zoom in\"\n style={zoomLevel >= MAX_ZOOM ? disabledStyle : buttonStyle}\n >\n +\n </button>\n </div>\n\n {/* Center: selection summary */}\n <div data-testid=\"timeline-selection-summary\">\n {summary(selectionType, selections, activeIndex)}\n </div>\n\n {/* Right: help */}\n <div style={{ display: \"flex\", alignItems: \"center\" }}>\n <button\n type=\"button\"\n data-testid=\"timeline-help-button\"\n onClick={onHelpToggle}\n aria-label=\"Show keyboard shortcuts\"\n aria-pressed={helpOpen}\n style={buttonStyle}\n >\n ?\n </button>\n </div>\n </div>\n );\n}\n","import React, { useCallback, useRef } from \"react\";\nimport type { TimelineValue } from \"./selections.js\";\nimport { clampViewportStart } from \"./viewport.js\";\n\nexport interface MinimapProps {\n /** Total media duration in seconds. */\n duration: number;\n /** Width of the minimap area in pixels. */\n width: number;\n /** Current zoom level (1 = full visible). */\n zoomLevel: number;\n /** Current viewport start in seconds. */\n viewportStart: number;\n /** Current playhead position in seconds. */\n currentTime: number;\n /** All current selections — drawn as small marks on the minimap. */\n selections: TimelineValue;\n /** Called with new viewport start (seconds) when the user pans. */\n onViewportChange: (newStart: number) => void;\n}\n\nconst HEIGHT = 32;\nconst VIEWPORT_RECT_BORDER = \"1.5px solid rgba(59, 130, 246, 0.9)\";\n\nfunction isRangeArray(\n s: TimelineValue,\n): s is { start: number; end: number; track?: number }[] {\n return s.length === 0 || \"start\" in (s[0] as object);\n}\n\ninterface DragState {\n pointerId: number;\n /** Offset in seconds between pointerdown time and viewportStart. */\n offset: number;\n}\n\nexport function Minimap({\n duration,\n width,\n zoomLevel,\n viewportStart,\n currentTime,\n selections,\n onViewportChange,\n}: MinimapProps) {\n const containerRef = useRef<HTMLDivElement>(null);\n const dragRef = useRef<DragState | null>(null);\n\n const visibleDuration = duration > 0 ? duration / zoomLevel : 0;\n\n const eventToTime = useCallback(\n (clientX: number) => {\n const el = containerRef.current;\n if (!el || duration <= 0) return 0;\n const rect = el.getBoundingClientRect();\n const localX = clientX - rect.left;\n const t = (localX / rect.width) * duration;\n return Math.max(0, Math.min(duration, t));\n },\n [duration],\n );\n\n const handlePointerDown = useCallback(\n (e: React.PointerEvent) => {\n if (e.button !== 0) return;\n const time = eventToTime(e.clientX);\n const viewportEnd = viewportStart + visibleDuration;\n\n // If clicking inside the viewport rectangle, drag-pan it (preserve offset)\n // Otherwise, click to center the viewport on that point\n let offset: number;\n if (time >= viewportStart && time <= viewportEnd) {\n offset = time - viewportStart;\n } else {\n // Click to center viewport — apply immediately\n const newStart = clampViewportStart(\n time - visibleDuration / 2,\n duration,\n zoomLevel,\n );\n onViewportChange(newStart);\n offset = visibleDuration / 2;\n }\n\n dragRef.current = { pointerId: e.pointerId, offset };\n // Pointer capture lets us track drag outside the element. In tests\n // (Playwright dispatchEvent), the pointerId may not be a real OS\n // pointer, so we silently ignore failures.\n try {\n e.currentTarget.setPointerCapture(e.pointerId);\n } catch {\n // ignore\n }\n },\n [\n eventToTime,\n viewportStart,\n visibleDuration,\n duration,\n zoomLevel,\n onViewportChange,\n ],\n );\n\n const handlePointerMove = useCallback(\n (e: React.PointerEvent) => {\n const drag = dragRef.current;\n if (!drag) return;\n const time = eventToTime(e.clientX);\n const newStart = clampViewportStart(\n time - drag.offset,\n duration,\n zoomLevel,\n );\n onViewportChange(newStart);\n },\n [eventToTime, duration, zoomLevel, onViewportChange],\n );\n\n const handlePointerUp = useCallback((e: React.PointerEvent) => {\n if (dragRef.current?.pointerId === e.pointerId) {\n dragRef.current = null;\n try {\n e.currentTarget.releasePointerCapture(e.pointerId);\n } catch {\n // ignore\n }\n }\n }, []);\n\n const timeToX = useCallback(\n (t: number) => (duration > 0 ? (t / duration) * width : 0),\n [duration, width],\n );\n\n // Selection marks\n const selectionMarks: React.ReactElement[] = [];\n if (isRangeArray(selections)) {\n selections.forEach((r, i) => {\n const x1 = timeToX(r.start);\n const x2 = timeToX(r.end);\n selectionMarks.push(\n <div\n key={`r-${String(i)}`}\n style={{\n position: \"absolute\",\n left: `${String(x1)}px`,\n top: 4,\n width: `${String(Math.max(x2 - x1, 1))}px`,\n height: HEIGHT - 8,\n background: \"rgba(59, 130, 246, 0.4)\",\n borderRadius: \"1px\",\n pointerEvents: \"none\",\n }}\n />,\n );\n });\n } else {\n (selections as { time: number }[]).forEach((p, i) => {\n const x = timeToX(p.time);\n selectionMarks.push(\n <div\n key={`p-${String(i)}`}\n style={{\n position: \"absolute\",\n left: `${String(x - 1)}px`,\n top: 4,\n width: 2,\n height: HEIGHT - 8,\n background: \"rgba(59, 130, 246, 0.7)\",\n pointerEvents: \"none\",\n }}\n />,\n );\n });\n }\n\n // Viewport rectangle position\n const viewportLeft = timeToX(viewportStart);\n const viewportWidth = Math.max(timeToX(visibleDuration), 8);\n\n // Playhead\n const playheadX = timeToX(currentTime);\n\n return (\n <div\n ref={containerRef}\n data-testid=\"timeline-minimap\"\n onPointerDown={handlePointerDown}\n onPointerMove={handlePointerMove}\n onPointerUp={handlePointerUp}\n onPointerCancel={handlePointerUp}\n style={{\n position: \"relative\",\n height: `${String(HEIGHT)}px`,\n width: `${String(width)}px`,\n background: \"var(--stagebook-bg-muted, #f9fafb)\",\n borderBottom: \"1px solid var(--stagebook-border, #e5e7eb)\",\n cursor: \"pointer\",\n userSelect: \"none\",\n touchAction: \"none\",\n }}\n >\n {selectionMarks}\n {/* Playhead line */}\n {currentTime >= 0 && currentTime <= duration && (\n <div\n data-testid=\"minimap-playhead\"\n style={{\n position: \"absolute\",\n left: `${String(playheadX - 0.5)}px`,\n top: 0,\n width: \"1px\",\n height: \"100%\",\n background: \"rgba(37, 99, 235, 0.8)\",\n pointerEvents: \"none\",\n }}\n />\n )}\n {/* Viewport rectangle */}\n <div\n data-testid=\"minimap-viewport\"\n style={{\n position: \"absolute\",\n left: `${String(viewportLeft)}px`,\n top: 0,\n width: `${String(viewportWidth)}px`,\n height: \"100%\",\n border: VIEWPORT_RECT_BORDER,\n boxSizing: \"border-box\",\n background: \"rgba(59, 130, 246, 0.06)\",\n pointerEvents: \"none\",\n }}\n />\n </div>\n );\n}\n","import React, { useEffect, useRef } from \"react\";\n\nexport interface HelpPopoverProps {\n selectionType: \"range\" | \"point\";\n onClose: () => void;\n}\n\nconst rangeShortcuts: { keys: string; description: string }[] = [\n { keys: \"Click empty space\", description: \"Seek playhead\" },\n { keys: \"Click and drag\", description: \"Create range\" },\n { keys: \"Click range\", description: \"Select it\" },\n { keys: \"Drag handle\", description: \"Adjust boundary\" },\n { keys: \"← →\", description: \"Adjust handle ±1s\" },\n { keys: \", .\", description: \"Adjust ±1 frame\" },\n { keys: \"Tab\", description: \"Switch handle\" },\n { keys: \"Delete\", description: \"Remove range\" },\n { keys: \"Ctrl+Z / Cmd+Z\", description: \"Undo\" },\n { keys: \"Escape\", description: \"Deselect\" },\n];\n\nconst pointShortcuts: { keys: string; description: string }[] = [\n { keys: \"Click empty space\", description: \"Place point\" },\n { keys: \"Click point\", description: \"Select it\" },\n { keys: \"Drag point\", description: \"Reposition\" },\n { keys: \"← →\", description: \"Reposition ±1s\" },\n { keys: \", .\", description: \"Reposition ±1 frame\" },\n { keys: \"Delete\", description: \"Remove point\" },\n { keys: \"Ctrl+Z / Cmd+Z\", description: \"Undo\" },\n { keys: \"Escape\", description: \"Deselect\" },\n];\n\nexport function HelpPopover({ selectionType, onClose }: HelpPopoverProps) {\n const popoverRef = useRef<HTMLDivElement>(null);\n const shortcuts = selectionType === \"range\" ? rangeShortcuts : pointShortcuts;\n\n // Close on Escape and click-outside\n useEffect(() => {\n function onKey(e: KeyboardEvent) {\n if (e.key === \"Escape\") {\n e.stopPropagation();\n onClose();\n }\n }\n function onClick(e: MouseEvent) {\n if (\n popoverRef.current &&\n !popoverRef.current.contains(e.target as Node)\n ) {\n onClose();\n }\n }\n document.addEventListener(\"keydown\", onKey, true);\n // Use capture so we run before other listeners; mousedown so we close\n // before any click handler on outside elements fires.\n document.addEventListener(\"mousedown\", onClick, true);\n return () => {\n document.removeEventListener(\"keydown\", onKey, true);\n document.removeEventListener(\"mousedown\", onClick, true);\n };\n }, [onClose]);\n\n return (\n <div\n ref={popoverRef}\n data-testid=\"timeline-help-popover\"\n role=\"dialog\"\n aria-label=\"Timeline keyboard shortcuts\"\n style={{\n position: \"absolute\",\n right: \"0.5rem\",\n bottom: \"2rem\",\n zIndex: 100,\n background: \"var(--stagebook-bg, #ffffff)\",\n border: \"1px solid var(--stagebook-border, #e5e7eb)\",\n borderRadius: \"0.375rem\",\n padding: \"0.5rem 0.75rem\",\n fontSize: \"0.75rem\",\n boxShadow: \"0 4px 12px rgba(0, 0, 0, 0.12)\",\n minWidth: \"220px\",\n }}\n >\n <div\n style={{\n fontWeight: 600,\n marginBottom: \"0.375rem\",\n color: \"var(--stagebook-text, #111827)\",\n }}\n >\n Keyboard shortcuts\n </div>\n <table\n style={{\n borderCollapse: \"collapse\",\n width: \"100%\",\n }}\n >\n <tbody>\n {shortcuts.map((s) => (\n <tr key={s.keys}>\n <td\n style={{\n paddingRight: \"0.75rem\",\n fontFamily: \"monospace\",\n color: \"var(--stagebook-text, #111827)\",\n whiteSpace: \"nowrap\",\n verticalAlign: \"top\",\n }}\n >\n {s.keys}\n </td>\n <td\n style={{\n color: \"var(--stagebook-muted, #6b7280)\",\n verticalAlign: \"top\",\n }}\n >\n {s.description}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n );\n}\n","// State management for Timeline selections.\n// Pure reducer wrapping selections.ts functions, with undo support.\n//\n// Pure TypeScript — no React/DOM deps. Used by Timeline.tsx via useReducer.\n\nimport {\n type RangeSelection,\n type TimelineValue,\n type SelectionSnapshot,\n createRange,\n createPoint,\n adjustHandle,\n repositionPoint,\n deleteSelection,\n sortRanges,\n sortPoints,\n pushUndo,\n popUndo,\n} from \"./selections.js\";\n\nexport interface SelectionState {\n selections: TimelineValue;\n activeIndex: number | null;\n activeHandle: \"start\" | \"end\" | null;\n undoStack: SelectionSnapshot[];\n}\n\nexport function initialSelectionState(): SelectionState {\n return {\n selections: [],\n activeIndex: null,\n activeHandle: null,\n undoStack: [],\n };\n}\n\nexport type SelectionAction =\n | {\n type: \"CREATE_RANGE\";\n start: number;\n end: number;\n track: number | undefined;\n multiSelect: boolean;\n }\n | {\n type: \"CREATE_POINT\";\n time: number;\n track: number | undefined;\n multiSelect: boolean;\n }\n | {\n type: \"ADJUST_HANDLE\";\n index: number;\n handle: \"start\" | \"end\";\n time: number;\n /**\n * If true, the reducer updates selections without pushing an undo\n * snapshot. SelectionOverlay uses this for live drag pointermove\n * events so that an entire drag collapses into a single undo step.\n */\n noSnapshot?: boolean;\n }\n | {\n type: \"REPOSITION_POINT\";\n index: number;\n time: number;\n noSnapshot?: boolean;\n }\n /**\n * Pushes a snapshot of the current selections to the undo stack without\n * changing them. Dispatched at the start of a drag (after the dead zone)\n * so undo restores the pre-drag state in one step.\n */\n | { type: \"BEGIN_DRAG\" }\n | { type: \"DELETE\" }\n | { type: \"SELECT\"; index: number }\n | { type: \"DESELECT\" }\n | { type: \"SET_ACTIVE_HANDLE\"; handle: \"start\" | \"end\" | null }\n | { type: \"UNDO\" }\n | { type: \"REPLACE_ALL\"; selections: TimelineValue };\n\nfunction isRangeArray(s: TimelineValue): s is RangeSelection[] {\n return s.length === 0 || \"start\" in s[0];\n}\n\nfunction snapshot(state: SelectionState): SelectionState {\n return {\n ...state,\n undoStack: pushUndo(state.undoStack, state.selections),\n };\n}\n\nexport function selectionsReducer(\n state: SelectionState,\n action: SelectionAction,\n): SelectionState {\n switch (action.type) {\n case \"CREATE_RANGE\": {\n // multiSelect: false → the new range REPLACES any existing one. Don't\n // clamp against ranges that are about to be discarded, and don't try\n // to \"keep the last in time order\" (that would keep the wrong one).\n if (!action.multiSelect) {\n const lo = Math.min(action.start, action.end);\n const hi = Math.max(action.start, action.end);\n if (hi <= lo) return state;\n const range: RangeSelection = { start: lo, end: hi };\n if (action.track !== undefined) range.track = action.track;\n return {\n ...snapshot(state),\n selections: [range],\n activeIndex: 0,\n activeHandle: null,\n };\n }\n const existing = isRangeArray(state.selections) ? state.selections : [];\n const range = createRange(\n action.start,\n action.end,\n action.track,\n existing,\n );\n if (!range) return state;\n const next = sortRanges([...existing, range]);\n return {\n ...snapshot(state),\n selections: next,\n activeIndex: next.indexOf(range),\n activeHandle: null,\n };\n }\n\n case \"CREATE_POINT\": {\n // multiSelect: false → the new point REPLACES any existing one.\n if (!action.multiSelect) {\n const point = createPoint(action.time, action.track);\n return {\n ...snapshot(state),\n selections: [point],\n activeIndex: 0,\n activeHandle: null,\n };\n }\n const existing = !isRangeArray(state.selections) ? state.selections : [];\n const point = createPoint(action.time, action.track);\n const next = sortPoints([...existing, point]);\n return {\n ...snapshot(state),\n selections: next,\n activeIndex: next.indexOf(point),\n activeHandle: null,\n };\n }\n\n case \"ADJUST_HANDLE\": {\n if (!isRangeArray(state.selections)) return state;\n const next = adjustHandle(\n state.selections,\n action.index,\n action.handle,\n action.time,\n );\n const base = action.noSnapshot ? state : snapshot(state);\n return {\n ...base,\n selections: sortRanges(next),\n activeHandle: action.handle,\n };\n }\n\n case \"REPOSITION_POINT\": {\n if (isRangeArray(state.selections)) return state;\n const next = repositionPoint(state.selections, action.index, action.time);\n const base = action.noSnapshot ? state : snapshot(state);\n return {\n ...base,\n selections: sortPoints(next),\n };\n }\n\n case \"BEGIN_DRAG\": {\n return snapshot(state);\n }\n\n case \"DELETE\": {\n if (state.activeIndex === null) return state;\n const next = deleteSelection(\n state.selections as RangeSelection[],\n state.activeIndex,\n );\n return {\n ...snapshot(state),\n selections: next,\n activeIndex: null,\n activeHandle: null,\n };\n }\n\n case \"SELECT\": {\n return {\n ...state,\n activeIndex: action.index,\n activeHandle: null,\n };\n }\n\n case \"DESELECT\": {\n return {\n ...state,\n activeIndex: null,\n activeHandle: null,\n };\n }\n\n case \"SET_ACTIVE_HANDLE\": {\n return {\n ...state,\n activeHandle: action.handle,\n };\n }\n\n case \"UNDO\": {\n const result = popUndo(state.undoStack);\n if (!result) return state;\n return {\n ...state,\n selections: result.restored,\n undoStack: result.newStack,\n activeIndex: null,\n activeHandle: null,\n };\n }\n\n case \"REPLACE_ALL\": {\n // Guard against malformed input from future consumers (e.g., saved\n // state rehydration) — better to ignore than crash downstream in\n // clampToFreeGap, adjustHandle, etc. which assume numeric fields.\n if (!Array.isArray(action.selections)) return state;\n return {\n ...snapshot(state),\n selections: action.selections,\n };\n }\n }\n}\n","// Pure key-to-action mapping for Timeline keyboard editing.\n// No React/DOM deps — used by Timeline.tsx's keydown handler.\n//\n// IMPORTANT: This is the arbitration point between Timeline and MediaPlayer.\n// Returning `null` means \"fall through to MediaPlayer\". Returning an action\n// means \"the timeline handles this; preventDefault + stopPropagation\".\n\n/** Frame step for comma/period: 1/30s ≈ one video frame at 30fps. */\nexport const FRAME_STEP = 1 / 30;\n\n/** Adjustment in seconds for arrow keys. */\nconst ARROW_STEP = 1;\n\nexport interface KeyContext {\n selectionType: \"range\" | \"point\";\n activeIndex: number | null;\n /** Range mode only: which handle is currently active. */\n activeHandle: \"start\" | \"end\" | null;\n /** Range mode only: the currently active range, if any. */\n currentRange: { start: number; end: number } | null;\n /** Point mode only: the currently active point, if any. */\n currentPoint: { time: number } | null;\n}\n\nexport type KeyAction =\n | {\n type: \"adjustHandle\";\n index: number;\n handle: \"start\" | \"end\";\n time: number;\n }\n | { type: \"repositionPoint\"; index: number; time: number }\n | { type: \"switchHandle\"; handle: \"start\" | \"end\" }\n | { type: \"delete\" }\n | { type: \"deselect\" }\n | { type: \"undo\" };\n\n/** Subset of KeyboardEvent that we care about — easier to test. */\nexport interface KeyEventLike {\n key: string;\n ctrlKey: boolean;\n metaKey: boolean;\n shiftKey: boolean;\n}\n\n/**\n * Map a keyboard event + selection context to an action, or null if the\n * event should fall through to the MediaPlayer.\n *\n * Rules:\n * - Space, K, J, L are NEVER intercepted (always belong to MediaPlayer)\n * - Ctrl+Z / Cmd+Z (without Shift) is undo, even when no selection is active\n * - All other actions require an active selection (`activeIndex !== null`)\n */\nexport function keyToAction(\n e: KeyEventLike,\n ctx: KeyContext,\n): KeyAction | null {\n // Never intercept playback shortcuts — always fall through.\n if (e.key === \" \" || e.key === \"k\" || e.key === \"K\") return null;\n if (e.key === \"j\" || e.key === \"J\" || e.key === \"l\" || e.key === \"L\")\n return null;\n\n // Undo works regardless of active selection.\n if (\n (e.ctrlKey || e.metaKey) &&\n !e.shiftKey &&\n (e.key === \"z\" || e.key === \"Z\")\n ) {\n return { type: \"undo\" };\n }\n\n // Everything else requires an active selection.\n if (ctx.activeIndex === null) return null;\n\n if (e.key === \"Delete\" || e.key === \"Backspace\") {\n return { type: \"delete\" };\n }\n\n if (e.key === \"Escape\") {\n return { type: \"deselect\" };\n }\n\n // Range mode: arrows + comma/period adjust the active handle.\n if (ctx.selectionType === \"range\" && ctx.currentRange) {\n if (e.key === \"Tab\") {\n const next: \"start\" | \"end\" =\n ctx.activeHandle === \"start\" ? \"end\" : \"start\";\n // No active handle: default to end (the most common adjustment target)\n const handle = ctx.activeHandle === null ? \"end\" : next;\n return { type: \"switchHandle\", handle };\n }\n\n if (ctx.activeHandle === null) return null;\n\n let delta = 0;\n if (e.key === \"ArrowLeft\") delta = -ARROW_STEP;\n else if (e.key === \"ArrowRight\") delta = ARROW_STEP;\n else if (e.key === \",\") delta = -FRAME_STEP;\n else if (e.key === \".\") delta = FRAME_STEP;\n else return null;\n\n const currentTime =\n ctx.activeHandle === \"start\"\n ? ctx.currentRange.start\n : ctx.currentRange.end;\n return {\n type: \"adjustHandle\",\n index: ctx.activeIndex,\n handle: ctx.activeHandle,\n time: currentTime + delta,\n };\n }\n\n // Point mode: arrows + comma/period reposition the active point.\n if (ctx.selectionType === \"point\" && ctx.currentPoint) {\n let delta = 0;\n if (e.key === \"ArrowLeft\") delta = -ARROW_STEP;\n else if (e.key === \"ArrowRight\") delta = ARROW_STEP;\n else if (e.key === \",\") delta = -FRAME_STEP;\n else if (e.key === \".\") delta = FRAME_STEP;\n else return null;\n\n return {\n type: \"repositionPoint\",\n index: ctx.activeIndex,\n time: ctx.currentPoint.time + delta,\n };\n }\n\n return null;\n}\n","import React, { useState, useCallback, useRef } from \"react\";\nimport { Markdown } from \"../form/Markdown.js\";\nimport { RadioGroup } from \"../form/RadioGroup.js\";\nimport { CheckboxGroup } from \"../form/CheckboxGroup.js\";\nimport { TextArea, type DebugMessage } from \"../form/TextArea.js\";\nimport { Slider } from \"../form/Slider.js\";\nimport { ListSorter } from \"../form/ListSorter.js\";\nimport type { MetadataType } from \"../../schemas/promptFile.js\";\n\nfunction setEquality(a: Set<string>, b: Set<string>): boolean {\n if (a.size !== b.size) return false;\n return Array.from(a).every((item) => b.has(item));\n}\n\nexport interface PromptProps {\n metadata: MetadataType;\n body: string;\n responseItems: string[];\n name: string;\n file?: string;\n shared?: boolean;\n value: unknown;\n save: (key: string, value: unknown, scope?: \"player\" | \"shared\") => void;\n resolveURL?: (path: string) => string;\n renderSharedNotepad?: (config: {\n padName: string;\n defaultText?: string;\n rows?: number;\n }) => React.ReactNode;\n}\n\nexport function Prompt({\n metadata,\n body,\n responseItems,\n name,\n file,\n shared = false,\n value,\n save,\n resolveURL,\n renderSharedNotepad,\n}: PromptProps) {\n const [responses, setResponses] = useState<string[]>([]);\n const [debugMessages, setDebugMessages] = useState<DebugMessage[]>([]);\n const debounceTextRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const debounceInteractiveRef = useRef<ReturnType<typeof setTimeout> | null>(\n null,\n );\n\n const promptType = metadata.type;\n const rows = metadata.rows ?? 5;\n const minLength = metadata.minLength ?? undefined;\n const maxLength = metadata.maxLength ?? undefined;\n\n // Initialize responses from responseItems (with optional shuffle)\n if (\n promptType !== \"noResponse\" &&\n responseItems.length > 0 &&\n (!responses.length ||\n !setEquality(new Set(responseItems), new Set(responses)))\n ) {\n if (metadata.shuffleOptions) {\n setResponses([...responseItems].sort(() => 0.5 - Math.random()));\n } else {\n setResponses(responseItems);\n }\n }\n\n const record = {\n ...metadata,\n name,\n file,\n shared,\n prompt: body,\n responses,\n debugMessages,\n };\n\n const saveData = useCallback(\n (newValue: unknown, recordData: typeof record) => {\n const updatedRecord = {\n ...recordData,\n value: newValue,\n };\n const scope = shared ? \"shared\" : \"player\";\n save(`prompt_${recordData.name}`, updatedRecord, scope);\n },\n [shared, save],\n );\n\n const debouncedSaveText = useCallback(\n (newValue: unknown, recordData: typeof record) => {\n if (debounceTextRef.current) clearTimeout(debounceTextRef.current);\n debounceTextRef.current = setTimeout(\n () => saveData(newValue, recordData),\n 2000,\n );\n },\n [saveData],\n );\n\n const debouncedSaveInteractive = useCallback(\n (newValue: unknown, recordData: typeof record) => {\n if (debounceInteractiveRef.current)\n clearTimeout(debounceInteractiveRef.current);\n debounceInteractiveRef.current = setTimeout(\n () => saveData(newValue, recordData),\n 50,\n );\n },\n [saveData],\n );\n\n return (\n <>\n <Markdown text={body} resolveURL={resolveURL} />\n\n {promptType === \"multipleChoice\" &&\n (metadata.select === \"single\" || metadata.select === undefined) && (\n <RadioGroup\n options={responses.map((choice) => ({\n key: choice,\n value: choice,\n }))}\n value={value as string | undefined}\n layout={metadata.layout}\n onChange={(e) => debouncedSaveInteractive(e.target.value, record)}\n />\n )}\n\n {promptType === \"multipleChoice\" && metadata.select === \"multiple\" && (\n <CheckboxGroup\n options={responses.map((choice) => ({\n key: choice,\n value: choice,\n }))}\n value={(value as string[]) ?? []}\n layout={metadata.layout}\n onChange={(newSelection) =>\n debouncedSaveInteractive(newSelection, record)\n }\n />\n )}\n\n {promptType === \"openResponse\" && !shared && (\n <TextArea\n defaultText={responses.join(\"\\n\")}\n onChange={(val) => debouncedSaveText(val, record)}\n onDebugMessage={(message) =>\n setDebugMessages((prev) => [...prev, message])\n }\n value={value as string | undefined}\n rows={rows}\n showCharacterCount={!!(minLength || maxLength)}\n minLength={minLength}\n maxLength={maxLength}\n />\n )}\n\n {promptType === \"openResponse\" &&\n shared &&\n renderSharedNotepad?.({\n padName: name,\n defaultText: responses.join(\"\\n\"),\n rows,\n })}\n\n {promptType === \"listSorter\" && (\n <ListSorter\n items={(value as string[]) ?? responses}\n onChange={(newOrder) => debouncedSaveInteractive(newOrder, record)}\n />\n )}\n\n {promptType === \"slider\" && (\n <Slider\n min={metadata.min}\n max={metadata.max}\n interval={metadata.interval}\n labelPts={metadata.labelPts}\n labels={responses}\n value={value as number | undefined}\n onChange={(val) => debouncedSaveInteractive(val, record)}\n />\n )}\n </>\n );\n}\n","import React from \"react\";\nimport ReactMarkdown from \"react-markdown\";\nimport remarkGfm from \"remark-gfm\";\n\nexport interface MarkdownProps {\n text: string;\n resolveURL?: (path: string) => string;\n}\n\n// ---------------------------------------------------------------------------\n// Inline styles for markdown elements\n// ---------------------------------------------------------------------------\n//\n// Why inline styles instead of a stylesheet?\n//\n// Stagebook is consumed as a library. The same Stagebook study should render\n// consistently across every host platform — that's the whole point of the\n// portable treatment file. But hosts ship wildly different CSS environments:\n// one ships Tailwind preflight, another ships Bootstrap reboot, another\n// ships normalize.css, another ships nothing. CSS resets routinely collapse\n// every heading level to body text size, so a researcher's `## Watch the\n// clip` renders as a paragraph that happens to start with capital letters.\n//\n// Author CSS shipped from node_modules loses specificity battles against\n// host CSS. Inline styles win against everything except !important, so\n// prompt content renders with the intended hierarchy regardless of what\n// the host's reset does. This is the same logic that makes Stagebook own\n// button shapes, slider thumbs, and the media player controls — visual\n// behavior is part of the contract, not a property of the host.\n//\n// These styles are tunable, but not every value is exposed as a CSS\n// custom property. Key typography and color values are variable-backed\n// (heading sizes/weights, link color, body line-height, blockquote\n// border/background, code background/font, prompt max-width). Spacing\n// and structural values (margins, padding, list bullet style, em\n// italics, strong weight) are hard-coded inline to keep the visual\n// consistent across hosts. If a researcher needs to tune one of those,\n// add a new variable in styles.css :root and reference it here.\n//\n// To override the exposed variables, set them on a parent element or\n// :root — no selector-based CSS needed:\n//\n// :root {\n// --stagebook-prompt-h1-size: 1.5rem;\n// --stagebook-prompt-line-height: 1.6;\n// --stagebook-link: #1e40af;\n// }\n//\n// See issue #33 for the full discussion.\n\nconst headingBase: React.CSSProperties = {\n lineHeight: 1.2,\n marginBlock: \"0.75em 0.5em\",\n};\n\nconst h1Style: React.CSSProperties = {\n ...headingBase,\n fontSize: \"var(--stagebook-prompt-h1-size, 1.875rem)\",\n fontWeight: \"var(--stagebook-prompt-h1-weight, 700)\",\n};\n\nconst h2Style: React.CSSProperties = {\n ...headingBase,\n fontSize: \"var(--stagebook-prompt-h2-size, 1.5rem)\",\n fontWeight: \"var(--stagebook-prompt-h2-weight, 600)\",\n};\n\nconst h3Style: React.CSSProperties = {\n ...headingBase,\n fontSize: \"var(--stagebook-prompt-h3-size, 1.25rem)\",\n fontWeight: \"var(--stagebook-prompt-h3-weight, 600)\",\n marginBlock: \"0.5em 0.25em\",\n};\n\nconst h4Style: React.CSSProperties = {\n ...headingBase,\n fontSize: \"var(--stagebook-prompt-h4-size, 1.125rem)\",\n fontWeight: \"var(--stagebook-prompt-h4-weight, 600)\",\n marginBlock: \"0.5em 0.25em\",\n};\n\nconst pStyle: React.CSSProperties = {\n marginBlock: \"0.5em\",\n};\n\nconst ulStyle: React.CSSProperties = {\n marginBlock: \"0.5em\",\n paddingInlineStart: \"1.5em\",\n listStyle: \"disc\",\n};\n\nconst olStyle: React.CSSProperties = {\n marginBlock: \"0.5em\",\n paddingInlineStart: \"1.5em\",\n listStyle: \"decimal\",\n};\n\nconst liStyle: React.CSSProperties = {\n marginBlock: \"0.125em\",\n};\n\nconst strongStyle: React.CSSProperties = {\n // Match the browser-default <strong> weight so **bold** looks bold even\n // on hosts that strip the UA stylesheet.\n fontWeight: 700,\n};\n\nconst emStyle: React.CSSProperties = {\n fontStyle: \"italic\",\n};\n\n// Inline code only — `like this`. Fenced code blocks (```...```) get\n// className=\"language-*\" from react-markdown and are passed through\n// untouched (out of scope for issue #33).\nconst inlineCodeStyle: React.CSSProperties = {\n fontFamily:\n \"var(--stagebook-code-font, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace)\",\n fontSize: \"0.9em\",\n background: \"var(--stagebook-code-bg, rgba(0,0,0,0.06))\",\n padding: \"0.1em 0.3em\",\n borderRadius: \"0.25rem\",\n};\n\nconst aStyle: React.CSSProperties = {\n color: \"var(--stagebook-link, #2563eb)\",\n textDecoration: \"underline\",\n};\n\n// Shared with Display.tsx (intentional inline duplication, see issue #33).\n// Both render <blockquote> and should look identical.\nconst blockquoteStyle: React.CSSProperties = {\n maxWidth: \"36rem\",\n wordBreak: \"break-word\",\n padding: \"1rem\",\n margin: \"1rem 0\",\n borderLeftWidth: \"0.25rem\",\n borderLeftStyle: \"solid\",\n borderLeftColor: \"var(--stagebook-blockquote-border, #d1d5db)\",\n background: \"var(--stagebook-blockquote-bg, #f9fafb)\",\n};\n\nexport function Markdown({ text, resolveURL }: MarkdownProps) {\n let displayText = text;\n\n // Rewrite relative image paths if a resolver is provided\n if (resolveURL) {\n displayText = text?.replace(\n /!\\[(.*?)\\]\\((.*?)\\)/g,\n (_match, alt: string, path: string) => {\n // Skip absolute URLs\n if (path.startsWith(\"http://\") || path.startsWith(\"https://\")) {\n return ``;\n }\n const resolved = resolveURL(path);\n // Reject non-http protocols (e.g., javascript:)\n if (\n !resolved.startsWith(\"http://\") &&\n !resolved.startsWith(\"https://\") &&\n !resolved.startsWith(\"data:\")\n ) {\n return ``; // fall back to original path\n }\n const url = encodeURI(resolved);\n return ``;\n },\n );\n }\n\n return (\n <div\n id=\"markdown\"\n style={{\n maxWidth: \"var(--stagebook-prompt-max-width, 36rem)\",\n fontSize: \"var(--stagebook-prompt-text-size, 1rem)\",\n lineHeight: \"var(--stagebook-prompt-line-height, 1.5)\",\n color: \"var(--stagebook-text, #1f2937)\",\n }}\n >\n <ReactMarkdown\n remarkPlugins={[remarkGfm]}\n components={{\n h1: ({ node: _node, ...props }) => <h1 style={h1Style} {...props} />,\n h2: ({ node: _node, ...props }) => <h2 style={h2Style} {...props} />,\n h3: ({ node: _node, ...props }) => <h3 style={h3Style} {...props} />,\n h4: ({ node: _node, ...props }) => <h4 style={h4Style} {...props} />,\n p: ({ node: _node, ...props }) => <p style={pStyle} {...props} />,\n ul: ({ node: _node, ...props }) => <ul style={ulStyle} {...props} />,\n ol: ({ node: _node, ...props }) => <ol style={olStyle} {...props} />,\n li: ({ node: _node, ...props }) => <li style={liStyle} {...props} />,\n strong: ({ node: _node, ...props }) => (\n <strong style={strongStyle} {...props} />\n ),\n em: ({ node: _node, ...props }) => <em style={emStyle} {...props} />,\n code: ({ node: _node, className, ...props }) => {\n // react-markdown v10 dropped the `inline` prop. Fenced code\n // blocks get className=\"language-*\"; inline code has no\n // className. Style only inline code; pass fenced blocks\n // through unchanged (out of scope for issue #33).\n const isFenced =\n typeof className === \"string\" &&\n className.startsWith(\"language-\");\n return isFenced ? (\n <code className={className} {...props} />\n ) : (\n <code style={inlineCodeStyle} {...props} />\n );\n },\n a: ({ node: _node, ...props }) => <a style={aStyle} {...props} />,\n blockquote: ({ node: _node, ...props }) => (\n <blockquote style={blockquoteStyle} {...props} />\n ),\n }}\n >\n {displayText}\n </ReactMarkdown>\n </div>\n );\n}\n","import React from \"react\";\n\nexport interface RadioOption {\n key: string;\n value: string;\n}\n\nexport type RadioLayout = \"vertical\" | \"horizontal\";\n\nexport interface RadioGroupProps {\n options: RadioOption[];\n value?: string;\n onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;\n label?: string;\n layout?: RadioLayout;\n id?: string;\n \"data-testid\"?: string;\n}\n\nexport function RadioGroup({\n options,\n value,\n onChange,\n label = \"\",\n layout = \"vertical\",\n id = \"radioGroup\",\n \"data-testid\": dataTestId,\n}: RadioGroupProps) {\n return (\n <div data-testid={dataTestId ?? id} style={{ marginTop: \"1rem\" }}>\n {label && (\n <label\n htmlFor={id}\n style={{\n display: \"block\",\n fontSize: \"1rem\",\n fontWeight: 500,\n color: \"var(--stagebook-text, #1f2937)\",\n marginBottom: \"0.5rem\",\n }}\n >\n {label}\n </label>\n )}\n <div\n style={{\n marginLeft: \"1.25rem\",\n display: layout === \"horizontal\" ? \"flex\" : \"grid\",\n gap: layout === \"horizontal\" ? \"1rem\" : \"0.375rem\",\n flexWrap: layout === \"horizontal\" ? \"wrap\" : undefined,\n }}\n >\n {options.map(({ key, value: optionValue }) => (\n <label\n key={`${id}_${key}`}\n data-testid=\"option\"\n style={{\n fontWeight: 400,\n fontSize: \"0.875rem\",\n color: \"var(--stagebook-text-muted, #6b7280)\",\n cursor: \"pointer\",\n display: \"flex\",\n alignItems: \"center\",\n gap: \"0.5rem\",\n }}\n >\n <input\n type=\"radio\"\n value={key}\n checked={value === key}\n onChange={onChange}\n />\n {optionValue}\n </label>\n ))}\n </div>\n </div>\n );\n}\n","import React from \"react\";\n\nexport interface CheckboxOption {\n key: string;\n value: string;\n}\n\nexport type CheckboxLayout = \"vertical\" | \"horizontal\";\n\nexport interface CheckboxGroupProps {\n options: CheckboxOption[];\n value: string[];\n onChange: (selected: string[]) => void;\n label?: string;\n layout?: CheckboxLayout;\n id?: string;\n \"data-testid\"?: string;\n}\n\nexport function CheckboxGroup({\n options,\n value = [],\n onChange,\n label = \"\",\n layout = \"vertical\",\n id = \"checkboxGroup\",\n \"data-testid\": dataTestId,\n}: CheckboxGroupProps) {\n const handleToggle = (key: string) => {\n const selectedSet = new Set(value);\n if (selectedSet.has(key)) {\n selectedSet.delete(key);\n } else {\n selectedSet.add(key);\n }\n onChange(Array.from(selectedSet));\n };\n\n return (\n <div data-testid={dataTestId ?? id} style={{ marginTop: \"1rem\" }}>\n {label && (\n <label\n htmlFor={id}\n style={{\n display: \"block\",\n fontSize: \"1rem\",\n fontWeight: 500,\n color: \"var(--stagebook-text, #1f2937)\",\n marginBottom: \"0.5rem\",\n }}\n >\n {label}\n </label>\n )}\n <div\n style={{\n marginLeft: \"1.25rem\",\n display: layout === \"horizontal\" ? \"flex\" : \"grid\",\n gap: layout === \"horizontal\" ? \"1rem\" : \"0.375rem\",\n flexWrap: layout === \"horizontal\" ? \"wrap\" : undefined,\n }}\n >\n {options.map(({ key, value: optionValue }) => (\n <label\n key={`${id}_${key}`}\n data-testid=\"option\"\n style={{\n fontWeight: 400,\n fontSize: \"0.875rem\",\n color: \"var(--stagebook-text-muted, #6b7280)\",\n cursor: \"pointer\",\n display: \"flex\",\n alignItems: \"center\",\n gap: \"0.5rem\",\n }}\n >\n <input\n type=\"checkbox\"\n name={key}\n value={key}\n id={`${id}_${key}`}\n checked={value.includes(key)}\n onChange={() => handleToggle(key)}\n />\n {optionValue}\n </label>\n ))}\n </div>\n </div>\n );\n}\n","import React, { useEffect, useState, useRef, useId } from \"react\";\n\nexport interface TypingStats {\n type: \"typingStats\";\n totalKeystrokes: number;\n avgInterval: number;\n stdDev: number;\n}\n\nexport interface PasteAttempt {\n type: \"pasteAttempt\";\n length: number;\n timestamp: number;\n}\n\nexport type DebugMessage = TypingStats | PasteAttempt;\n\nexport interface TextAreaProps {\n defaultText?: string;\n onChange?: (value: string) => void;\n onDebugMessage?: (message: DebugMessage) => void;\n value?: string;\n rows?: number;\n showCharacterCount?: boolean;\n minLength?: number;\n maxLength?: number;\n debounceDelay?: number;\n id?: string;\n}\n\nexport function TextArea({\n defaultText,\n onChange,\n onDebugMessage,\n value,\n rows = 5,\n showCharacterCount,\n minLength,\n maxLength,\n debounceDelay = 500,\n id,\n}: TextAreaProps) {\n const generatedId = useId();\n const textAreaId = id || generatedId;\n const [localValue, setLocalValue] = useState(value || \"\");\n const debounceTimeout = useRef<ReturnType<typeof setTimeout> | null>(null);\n const keystrokeTimestamps = useRef<number[]>([]);\n const isDebouncing = useRef(false);\n\n // Sync with external value only when not actively debouncing\n useEffect(() => {\n if (!isDebouncing.current) {\n setLocalValue(value || \"\");\n }\n }, [value]);\n\n const submitChange = (val: string) => {\n if (onChange && typeof onChange === \"function\") {\n onChange(val);\n }\n };\n\n const debouncedSubmit = (val: string) => {\n if (debounceTimeout.current) {\n clearTimeout(debounceTimeout.current);\n }\n isDebouncing.current = true;\n debounceTimeout.current = setTimeout(() => {\n isDebouncing.current = false;\n submitChange(val);\n }, debounceDelay);\n };\n\n const handlePaste = (e: React.ClipboardEvent<HTMLTextAreaElement>) => {\n e.preventDefault();\n const pastedText = e.clipboardData.getData(\"text\");\n if (onDebugMessage) {\n onDebugMessage({\n type: \"pasteAttempt\",\n length: pastedText.length,\n timestamp: Date.now(),\n });\n }\n };\n\n const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {\n const newValue = e.target.value;\n if (maxLength && newValue.length > maxLength) return;\n setLocalValue(newValue);\n debouncedSubmit(newValue);\n };\n\n const computeTypingStats = (): TypingStats | null => {\n const timestamps = keystrokeTimestamps.current;\n if (timestamps.length < 2) return null;\n\n const intervals = timestamps.slice(1).map((t, i) => t - timestamps[i]);\n const avgInterval = intervals.reduce((a, b) => a + b, 0) / intervals.length;\n const stdDev = Math.sqrt(\n intervals.map((x) => (x - avgInterval) ** 2).reduce((a, b) => a + b, 0) /\n intervals.length,\n );\n\n return {\n type: \"typingStats\",\n totalKeystrokes: timestamps.length,\n avgInterval,\n stdDev,\n };\n };\n\n const handleBlur = () => {\n if (debounceTimeout.current) {\n clearTimeout(debounceTimeout.current);\n isDebouncing.current = false;\n }\n submitChange(localValue);\n const typingStats = computeTypingStats();\n if (typingStats && onDebugMessage) {\n onDebugMessage(typingStats);\n }\n };\n\n const handleKeyDown = () => {\n keystrokeTimestamps.current.push(Date.now());\n };\n\n const renderCharacterCount = () => {\n if (!showCharacterCount) return null;\n\n let countText = \"\";\n let countColor = \"var(--stagebook-text-muted, #6b7280)\";\n let countState = \"default\";\n const currentLength = localValue.length;\n\n if (minLength && maxLength) {\n countText = `(${currentLength} / ${minLength}-${maxLength} chars)`;\n if (currentLength >= minLength && currentLength < maxLength) {\n countColor = \"var(--stagebook-success, #16a34a)\";\n countState = \"valid\";\n } else if (currentLength === maxLength) {\n countColor = \"var(--stagebook-warning, #dc2626)\";\n countState = \"error\";\n }\n } else if (minLength) {\n countText = `(${currentLength} / ${minLength}+ characters required)`;\n if (currentLength >= minLength) {\n countColor = \"var(--stagebook-success, #16a34a)\";\n countState = \"valid\";\n }\n } else if (maxLength) {\n countText = `(${currentLength} / ${maxLength} chars max)`;\n if (currentLength === maxLength) {\n countColor = \"var(--stagebook-warning, #dc2626)\";\n countState = \"error\";\n }\n } else {\n countText = `(${currentLength} characters)`;\n }\n\n return (\n <div\n data-testid=\"char-counter\"\n data-state={countState}\n style={{\n textAlign: \"right\",\n fontSize: \"0.75rem\",\n marginTop: \"0.25rem\",\n paddingRight: \"0.75rem\",\n color: countColor,\n boxSizing: \"border-box\",\n width: \"100%\",\n }}\n >\n {countText}\n </div>\n );\n };\n\n return (\n <div\n style={{ position: \"relative\", width: \"100%\", boxSizing: \"border-box\" }}\n >\n <textarea\n id={textAreaId}\n autoComplete=\"off\"\n rows={rows}\n placeholder={defaultText}\n value={localValue}\n onChange={handleChange}\n onBlur={handleBlur}\n onPaste={handlePaste}\n onKeyDown={handleKeyDown}\n style={{\n display: \"block\",\n width: \"100%\",\n boxSizing: \"border-box\",\n padding: \"0.5rem 0.75rem\",\n border: \"1px solid var(--stagebook-border, #d1d5db)\",\n borderRadius: \"0.375rem\",\n boxShadow: \"0 1px 2px 0 rgba(0, 0, 0, 0.05)\",\n fontSize: \"0.875rem\",\n lineHeight: \"1.25rem\",\n color: \"var(--stagebook-text, #1f2937)\",\n resize: \"vertical\",\n }}\n />\n {renderCharacterCount()}\n </div>\n );\n}\n","import React, { useState, useEffect } from \"react\";\n\nexport interface SliderProps {\n min?: number;\n max?: number;\n interval?: number;\n labelPts?: number[];\n labels?: string[];\n value?: number;\n onChange?: (value: number) => void;\n}\n\nexport function Slider({\n min = 0,\n max = 100,\n interval = 1,\n labelPts = [],\n labels = [],\n value,\n onChange,\n}: SliderProps) {\n const [localValue, setLocalValue] = useState<number | undefined>(value);\n\n useEffect(() => {\n setLocalValue(value);\n }, [value]);\n\n const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n const newValue = parseFloat(e.target.value);\n setLocalValue(newValue);\n onChange?.(newValue);\n };\n\n const handleClick = (e: React.MouseEvent<HTMLDivElement>) => {\n // Only set value on click if no value is set yet (avoids anchoring)\n if (localValue !== undefined && localValue !== null) return;\n\n const rect = e.currentTarget.getBoundingClientRect();\n const x = e.clientX - rect.left;\n const percentage = x / rect.width;\n const rawValue = min + percentage * (max - min);\n const newValue = Math.round(rawValue / interval) * interval;\n const clampedValue = Math.max(min, Math.min(max, newValue));\n setLocalValue(clampedValue);\n onChange?.(clampedValue);\n };\n\n const getPosition = (pt: number) => ((pt - min) / (max - min)) * 100;\n\n const hasValue = localValue !== undefined && localValue !== null;\n\n return (\n <div\n data-testid=\"slider\"\n data-state={hasValue ? \"anchored\" : \"unanchored\"}\n style={{ marginTop: \"1rem\", width: \"100%\" }}\n >\n <div\n style={{\n position: \"relative\",\n width: \"100%\",\n paddingTop: \"0.5rem\",\n paddingBottom: \"2.5rem\",\n paddingLeft: \"0.5rem\",\n paddingRight: \"0.5rem\",\n }}\n >\n {/* Clickable track — no thumb until first interaction */}\n <div\n onClick={handleClick}\n role=\"presentation\"\n data-testid=\"slider-track\"\n style={{\n position: \"relative\",\n width: \"100%\",\n height: \"8px\",\n backgroundColor: \"var(--stagebook-bg-track, #e5e7eb)\",\n borderRadius: \"4px\",\n cursor: \"pointer\",\n }}\n >\n {/* Ticks */}\n {labelPts.map((pt) => (\n <div\n key={`tick-${pt}`}\n style={{\n position: \"absolute\",\n left: `${getPosition(pt)}%`,\n top: 0,\n width: \"2px\",\n height: \"12px\",\n backgroundColor: \"var(--stagebook-text-faint, #9ca3af)\",\n }}\n />\n ))}\n </div>\n\n {/* Instruction when no value set — avoids anchoring */}\n {!hasValue && (\n <div\n style={{\n position: \"absolute\",\n left: \"50%\",\n transform: \"translateX(-50%)\",\n top: \"-0.75rem\",\n fontSize: \"0.75rem\",\n color: \"var(--stagebook-text-muted, #6b7280)\",\n textAlign: \"center\",\n whiteSpace: \"nowrap\",\n }}\n >\n Click the bar to select a value, then drag to adjust.\n </div>\n )}\n\n {/* Range input — only rendered after first interaction */}\n {hasValue && (\n <input\n type=\"range\"\n min={min}\n max={max}\n step={interval}\n value={localValue}\n onChange={handleChange}\n style={{\n position: \"absolute\",\n top: \"8px\",\n left: \"0.5rem\",\n width: \"calc(100% - 1rem)\",\n height: \"8px\",\n background: \"transparent\",\n cursor: \"pointer\",\n WebkitAppearance: \"none\",\n MozAppearance: \"none\",\n }}\n aria-label=\"Slider\"\n aria-valuemin={min}\n aria-valuemax={max}\n aria-valuenow={localValue}\n />\n )}\n\n {/* Labels — positioned below ticks */}\n <div style={{ position: \"relative\", width: \"100%\", marginTop: \"6px\" }}>\n {labelPts.map((pt, idx) => {\n const pos = getPosition(pt);\n // Prevent edge labels from clipping off-screen\n let transform = \"translateX(-50%)\";\n let textAlign: React.CSSProperties[\"textAlign\"] = \"center\";\n if (pos <= 5) {\n transform = \"translateX(0)\";\n textAlign = \"left\";\n } else if (pos >= 95) {\n transform = \"translateX(-100%)\";\n textAlign = \"right\";\n }\n return (\n <div\n key={`label-${pt}`}\n style={{\n position: \"absolute\",\n left: `${pos}%`,\n transform,\n textAlign,\n maxWidth: \"80px\",\n fontSize: \"0.75rem\",\n color: \"var(--stagebook-text-muted, #6b7280)\",\n }}\n >\n {labels[idx]}\n </div>\n );\n })}\n </div>\n </div>\n\n <style>{`\n input[type=\"range\"]::-webkit-slider-thumb {\n -webkit-appearance: none;\n appearance: none;\n width: 20px;\n height: 24px;\n background: var(--stagebook-primary, #3b82f6);\n border-radius: 50%;\n cursor: pointer;\n border: 2px solid white;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);\n margin-top: -8px;\n }\n input[type=\"range\"]::-moz-range-thumb {\n width: 20px;\n height: 24px;\n background: var(--stagebook-primary, #3b82f6);\n border-radius: 50%;\n cursor: pointer;\n border: 2px solid white;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);\n }\n input[type=\"range\"]::-webkit-slider-runnable-track {\n width: 100%;\n height: 0;\n background: transparent;\n }\n input[type=\"range\"]::-moz-range-track {\n width: 100%;\n height: 0;\n background: transparent;\n }\n input[type=\"range\"]:focus {\n outline: none;\n }\n input[type=\"range\"]:focus::-webkit-slider-thumb {\n box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.3);\n }\n input[type=\"range\"]:focus::-moz-range-thumb {\n box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.3);\n }\n `}</style>\n </div>\n );\n}\n","import React from \"react\";\nimport {\n DragDropContext,\n Droppable,\n Draggable,\n type DropResult,\n} from \"@hello-pangea/dnd\";\n\nconst reorder = (\n list: string[],\n startIndex: number,\n endIndex: number,\n): string[] => {\n const result = Array.from(list);\n const [removed] = result.splice(startIndex, 1);\n result.splice(endIndex, 0, removed);\n return result;\n};\n\nfunction ListItem({\n id,\n index,\n text,\n}: {\n id: string;\n index: number;\n text: string;\n}) {\n return (\n <Draggable key={id} draggableId={id} index={index}>\n {(provided, snapshot) => (\n <div\n ref={provided.innerRef}\n {...provided.draggableProps}\n {...provided.dragHandleProps}\n data-testid={`draggable-${index}`}\n style={{\n ...provided.draggableProps.style,\n padding: \"0.5rem 0.75rem\",\n border: `1px solid ${snapshot.isDragging ? \"var(--stagebook-text-secondary, #374151)\" : \"var(--stagebook-border, #d1d5db)\"}`,\n backgroundColor: \"var(--stagebook-bg-muted, #f9fafb)\",\n borderRadius: \"0.375rem\",\n boxShadow: \"0 1px 2px 0 rgba(0, 0, 0, 0.05)\",\n fontSize: \"0.875rem\",\n color: \"var(--stagebook-text, #1f2937)\",\n cursor: \"grab\",\n }}\n >\n ⇅ {text}\n </div>\n )}\n </Draggable>\n );\n}\n\nfunction List({ items }: { items: string[] }) {\n const displayIndex = [...Array(items.length + 1).keys()].slice(1);\n return (\n <div\n style={{\n display: \"flex\",\n border: \"1px solid var(--stagebook-border, #d1d5db)\",\n borderRadius: \"0.375rem\",\n boxShadow: \"0 1px 2px 0 rgba(0, 0, 0, 0.05)\",\n }}\n >\n <div\n style={{\n display: \"grid\",\n padding: \"0.5rem\",\n alignContent: \"start\",\n gap: \"0.5rem\",\n color: \"var(--stagebook-text-muted, #6b7280)\",\n fontSize: \"0.875rem\",\n }}\n >\n {displayIndex.map((i) => (\n <p\n key={i}\n style={{\n margin: 0,\n padding: \"0.5rem 0\",\n lineHeight: \"1.25rem\",\n }}\n >\n {i}.{\" \"}\n </p>\n ))}\n </div>\n <Droppable droppableId=\"droppable\">\n {(provided) => (\n <div\n {...provided.droppableProps}\n ref={provided.innerRef}\n style={{\n display: \"grid\",\n gap: \"0.5rem\",\n padding: \"0.5rem\",\n flex: 1,\n }}\n >\n {items.map((item, index) => (\n <ListItem key={item} id={item} index={index} text={item} />\n ))}\n {provided.placeholder}\n </div>\n )}\n </Droppable>\n </div>\n );\n}\n\nexport interface ListSorterProps {\n items: string[];\n onChange: (reordered: string[]) => void;\n}\n\nexport function ListSorter({ items, onChange }: ListSorterProps) {\n const onDragEnd = (result: DropResult) => {\n if (!result.destination) return;\n const reordered = reorder(\n items,\n result.source.index,\n result.destination.index,\n );\n onChange(reordered);\n };\n\n return (\n <DragDropContext onDragEnd={onDragEnd}>\n <List items={items} />\n </DragDropContext>\n );\n}\n","import React, { useEffect, useMemo } from \"react\";\n\nexport interface ResolvedParam {\n key: string;\n value: string;\n}\n\nexport interface QualtricsProps {\n url: string;\n resolvedParams?: ResolvedParam[];\n participantId?: string;\n groupId?: string;\n save: (key: string, value: unknown) => void;\n onComplete: () => void;\n}\n\nexport function Qualtrics({\n url,\n resolvedParams = [],\n participantId = \"\",\n groupId = \"\",\n save,\n onComplete,\n}: QualtricsProps) {\n // Listen for Qualtrics end-of-survey message.\n // Validates origin to prevent spoofed messages from non-Qualtrics sources.\n // Checks *.qualtrics.com to handle datacenter redirects.\n useEffect(() => {\n const onMessage = (event: MessageEvent) => {\n // Validate origin — only accept messages from Qualtrics domains\n try {\n const originHost = new URL(event.origin).hostname;\n if (!originHost.endsWith(\"qualtrics.com\")) return;\n } catch {\n return;\n }\n\n const data: unknown = event.data;\n if (typeof data === \"string\" && data.startsWith(\"QualtricsEOS\")) {\n const [, surveyId, sessionId] = data.split(\"|\");\n save(\"qualtricsDataReady\", {\n surveyURL: url,\n surveyId,\n sessionId,\n });\n onComplete();\n }\n };\n\n window.addEventListener(\"message\", onMessage);\n return () => window.removeEventListener(\"message\", onMessage);\n }, [url, save, onComplete]);\n\n // Build the full URL with resolved params + standard identifiers\n const fullURL = useMemo(() => {\n const urlObj = new URL(url);\n resolvedParams.forEach(({ key, value }) =>\n urlObj.searchParams.append(key, value),\n );\n if (participantId) {\n urlObj.searchParams.append(\"deliberationId\", participantId);\n }\n if (groupId) {\n urlObj.searchParams.append(\"sampleId\", groupId);\n }\n return urlObj.toString();\n }, [url, resolvedParams, participantId, groupId]);\n\n return (\n <div\n style={{\n height: \"100%\",\n minWidth: \"800px\",\n maxWidth: \"56rem\",\n overflowX: \"auto\",\n }}\n >\n <iframe\n title={`qualtrics_${url}`}\n src={fullURL}\n style={{\n position: \"relative\",\n height: \"100%\",\n minHeight: \"100vh\",\n width: \"100%\",\n border: \"none\",\n }}\n />\n </div>\n );\n}\n","import React from \"react\";\n\nexport interface LoadingProps {\n size?: \"sm\" | \"md\" | \"lg\";\n}\n\nconst sizeMap = {\n sm: 16,\n md: 20,\n lg: 32,\n};\n\nexport function Loading({ size = \"md\" }: LoadingProps) {\n const px = sizeMap[size];\n\n return (\n <div\n style={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n padding: \"1rem\",\n }}\n >\n <svg\n width={px}\n height={px}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n aria-label=\"Loading\"\n style={{\n animation: \"stagebook-spin 0.75s linear infinite\",\n }}\n >\n {/* Track — full circle, light gray */}\n <circle\n cx=\"12\"\n cy=\"12\"\n r=\"10\"\n style={{ stroke: \"var(--stagebook-spinner-track, #e5e7eb)\" }}\n strokeWidth=\"3\"\n fill=\"none\"\n />\n {/* Spinning arc — quarter circle on the same path */}\n <circle\n cx=\"12\"\n cy=\"12\"\n r=\"10\"\n style={{ stroke: \"var(--stagebook-spinner-arc, #9ca3af)\" }}\n strokeWidth=\"3\"\n fill=\"none\"\n strokeLinecap=\"round\"\n strokeDasharray=\"15.7 47.1\"\n />\n </svg>\n <style>{`\n @keyframes stagebook-spin {\n from { transform: rotate(0deg); }\n to { transform: rotate(360deg); }\n }\n `}</style>\n </div>\n );\n}\n","import React, { useState, useEffect } from \"react\";\n\nexport interface TimeConditionalRenderProps {\n displayTime?: number;\n hideTime?: number;\n getElapsedTime: () => number;\n children: React.ReactNode;\n}\n\nexport function TimeConditionalRender({\n displayTime,\n hideTime,\n getElapsedTime,\n children,\n}: TimeConditionalRenderProps) {\n const [, setTick] = useState(false);\n\n useEffect(() => {\n if (!displayTime && !hideTime) return () => undefined;\n\n const interval = setInterval(() => setTick((prev) => !prev), 1000);\n return () => clearInterval(interval);\n }, [displayTime, hideTime]);\n\n const elapsed = getElapsedTime();\n\n if (displayTime && elapsed < displayTime) return null;\n if (hideTime && elapsed > hideTime) return null;\n\n return <>{children}</>;\n}\n","import React from \"react\";\n\nexport interface PositionConditionalRenderProps {\n showToPositions?: number[];\n hideFromPositions?: number[];\n position: number | undefined;\n children: React.ReactNode;\n}\n\nexport function PositionConditionalRender({\n showToPositions,\n hideFromPositions,\n position,\n children,\n}: PositionConditionalRenderProps) {\n // Position is undefined in intro steps — render everything\n if (position === undefined || position === null) return <>{children}</>;\n\n // Defensive coercion — host platforms may pass position as a string\n const numPosition =\n typeof position === \"number\" ? position : Number(position);\n if (Number.isNaN(numPosition)) return <>{children}</>;\n\n if (showToPositions && !showToPositions.includes(numPosition)) return null;\n if (hideFromPositions && hideFromPositions.includes(numPosition)) return null;\n\n return <>{children}</>;\n}\n","import React from \"react\";\nimport {\n evaluateCondition,\n type Condition,\n} from \"../../utils/evaluateConditions.js\";\n\nexport type { Condition };\n\nexport interface ConditionsConditionalRenderProps {\n conditions: Condition[];\n resolve: (reference: string, position?: string) => unknown[];\n children: React.ReactNode;\n fallback?: React.ReactNode;\n}\n\nexport function ConditionsConditionalRender({\n conditions,\n resolve,\n children,\n fallback = null,\n}: ConditionsConditionalRenderProps) {\n if (!conditions || !conditions.length) return <>{children}</>;\n\n return (\n <RecursiveConditionalRender\n conditions={conditions}\n resolve={resolve}\n fallback={fallback}\n >\n {children}\n </RecursiveConditionalRender>\n );\n}\n\nfunction RecursiveConditionalRender({\n conditions,\n resolve,\n children,\n fallback = null,\n}: ConditionsConditionalRenderProps) {\n const condition = conditions[0];\n const referenceValues = resolve(condition.reference, condition.position);\n const conditionMet = evaluateCondition(condition, referenceValues);\n\n if (!conditionMet) return <>{fallback}</>;\n if (conditions.length === 1) return <>{children}</>;\n\n return (\n <RecursiveConditionalRender\n conditions={conditions.slice(1)}\n resolve={resolve}\n fallback={fallback}\n >\n {children}\n </RecursiveConditionalRender>\n );\n}\n","import React from \"react\";\nimport { Loading } from \"../form/Loading.js\";\n\nexport interface SubmissionConditionalRenderProps {\n isSubmitted: boolean;\n playerCount: number | undefined;\n children: React.ReactNode;\n}\n\nexport function SubmissionConditionalRender({\n isSubmitted,\n playerCount,\n children,\n}: SubmissionConditionalRenderProps) {\n if (isSubmitted) {\n if (!playerCount || playerCount <= 1) {\n return (\n <div\n data-testid=\"submission-state\"\n data-state=\"loading\"\n style={{ textAlign: \"center\" }}\n >\n <Loading />\n </div>\n );\n }\n return (\n <div\n data-testid=\"submission-state\"\n data-state=\"waiting\"\n style={{ textAlign: \"center\", color: \"#9ca3af\", pointerEvents: \"none\" }}\n >\n Please wait for other participant(s) to finish this stage.\n </div>\n );\n }\n\n return <>{children}</>;\n}\n","import React from \"react\";\n\nexport interface ScrollIndicatorProps {\n visible: boolean;\n}\n\nexport function ScrollIndicator({ visible }: ScrollIndicatorProps) {\n if (!visible) return null;\n\n return (\n <>\n <style>{`\n @keyframes scrollIndicatorFadeIn {\n from { opacity: 0; transform: translateY(10px); }\n to { opacity: 1; transform: translateY(0); }\n }\n @keyframes scrollIndicatorPulse {\n 0%, 100% { transform: scale(1); opacity: 1; }\n 50% { transform: scale(1.05); opacity: 0.85; }\n }\n `}</style>\n <div\n style={{\n position: \"sticky\",\n bottom: 0,\n left: 0,\n right: 0,\n display: \"flex\",\n justifyContent: \"center\",\n padding: \"0.75rem\",\n pointerEvents: \"none\",\n zIndex: 50,\n animation: \"scrollIndicatorFadeIn 0.3s ease-out\",\n }}\n aria-hidden=\"true\"\n data-testid=\"scroll-indicator\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"40\"\n height=\"40\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n style={{\n backgroundColor: \"rgba(229, 231, 235, 0.8)\",\n color: \"#4b5563\",\n borderRadius: \"9999px\",\n padding: \"0.5rem\",\n boxShadow: \"0 4px 6px -1px rgba(0, 0, 0, 0.1)\",\n backdropFilter: \"blur(4px)\",\n animation: \"scrollIndicatorPulse 2s ease-in-out infinite\",\n }}\n >\n <polyline points=\"6 9 12 15 18 9\" />\n </svg>\n </div>\n </>\n );\n}\n","import { useState, useEffect, useCallback, useRef } from \"react\";\nimport { isAtBottom } from \"./scrollUtils.js\";\n\n/**\n * Detects when new content appears below the viewport and either:\n * - Auto-scrolls to \"peek\" the content if user is near bottom\n * - Shows an indicator if user is not near bottom\n */\nexport function useScrollAwareness(\n containerRef: React.RefObject<HTMLElement | null>,\n options: { threshold?: number } = {},\n): { showIndicator: boolean; dismissIndicator: () => void } {\n const { threshold = 120 } = options;\n const [showIndicator, setShowIndicator] = useState(false);\n const prevScrollHeightRef = useRef(0);\n const wasAtBottomRef = useRef(true);\n const isInitializedRef = useRef(false);\n\n const checkAtBottom = useCallback(() => {\n const container = containerRef.current;\n if (!container) return true;\n return isAtBottom(\n container.scrollHeight,\n container.scrollTop,\n container.clientHeight,\n threshold,\n );\n }, [containerRef, threshold]);\n\n // Handle scroll events — dismiss indicator when user scrolls down\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return undefined;\n\n const handleScroll = () => {\n wasAtBottomRef.current = checkAtBottom();\n if (showIndicator && wasAtBottomRef.current) {\n setShowIndicator(false);\n }\n };\n\n container.addEventListener(\"scroll\", handleScroll, { passive: true });\n wasAtBottomRef.current = checkAtBottom();\n prevScrollHeightRef.current = container.scrollHeight;\n\n return () => container.removeEventListener(\"scroll\", handleScroll);\n }, [containerRef, checkAtBottom, showIndicator]);\n\n // MutationObserver detects when React renders new content\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return undefined;\n\n const checkForNewContent = () => {\n const currentScrollHeight = container.scrollHeight;\n const scrollTop = container.scrollTop;\n const prevScrollHeight = prevScrollHeightRef.current;\n\n if (currentScrollHeight > prevScrollHeight && prevScrollHeight > 0) {\n if (!isInitializedRef.current) {\n isInitializedRef.current = true;\n prevScrollHeightRef.current = currentScrollHeight;\n return;\n }\n\n const wasAtBottomNow =\n wasAtBottomRef.current ||\n isAtBottom(\n prevScrollHeight,\n scrollTop,\n container.clientHeight,\n threshold,\n );\n const heightDelta = currentScrollHeight - prevScrollHeight;\n\n if (wasAtBottomNow) {\n // User was at bottom — \"peek\" scroll to show top of new content\n const peekAmount = Math.min(heightDelta, 150);\n const startScrollTop = scrollTop;\n const duration = 900;\n const startTime = performance.now();\n\n const animateScroll = (currentTime: number) => {\n const elapsed = currentTime - startTime;\n const progress = Math.min(elapsed / duration, 1);\n const easeInOut =\n progress < 0.5\n ? 4 * progress * progress * progress\n : 1 - Math.pow(-2 * progress + 2, 3) / 2;\n container.scrollTop = startScrollTop + peekAmount * easeInOut;\n if (progress < 1) {\n requestAnimationFrame(animateScroll);\n }\n };\n\n setTimeout(() => requestAnimationFrame(animateScroll), 50);\n } else {\n // User was not at bottom — show indicator\n setShowIndicator(true);\n }\n }\n\n prevScrollHeightRef.current = currentScrollHeight;\n };\n\n const mutationObserver = new MutationObserver(() => {\n requestAnimationFrame(() => checkForNewContent());\n });\n\n mutationObserver.observe(container, {\n childList: true,\n subtree: true,\n characterData: true,\n });\n\n prevScrollHeightRef.current = container.scrollHeight;\n\n return () => mutationObserver.disconnect();\n }, [containerRef, threshold]);\n\n const dismissIndicator = useCallback(() => {\n setShowIndicator(false);\n }, []);\n\n return { showIndicator, dismissIndicator };\n}\n","/**\n * Determines if a scroll container is at or near the bottom.\n */\nexport function isAtBottom(\n scrollHeight: number,\n scrollTop: number,\n clientHeight: number,\n threshold = 80,\n): boolean {\n if (scrollHeight <= 0 || clientHeight <= 0) {\n return true; // No scrollable content — consider \"at bottom\"\n }\n const distanceFromBottom = scrollHeight - scrollTop - clientHeight;\n return distanceFromBottom < threshold;\n}\n"],"mappings":"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","names":["useRef","React","jsx","jsx","jsx","jsx","useEffect","jsx","useState","useEffect","jsx","jsxs","useEffect","jsx","jsxs","useRef","useCallback","useEffect","useMemo","useState","useEffect","useRef","jsx","jsx","jsxs","jsx","jsxs","createContext","useCallback","useContext","useEffect","useMemo","useRef","useState","jsx","jsx","jsxs","useRef","useState","useEffect","BUCKETS_PER_SECOND","useCallback","useMemo","min","max","useCallback","useEffect","useRef","useState","jsx","jsxs","useRef","useEffect","jsx","jsx","jsxs","jsx","useCallback","useRef","useState","jsx","jsxs","useRef","useState","useCallback","jsx","jsxs","isRangeArray","useCallback","useRef","jsx","jsxs","isRangeArray","useRef","useCallback","useEffect","useRef","jsx","jsxs","isRangeArray","range","point","jsx","jsxs","valid","useRef","useState","useCallback","useEffect","duration","visibleDuration","useState","useCallback","useRef","jsx","blockquoteStyle","jsx","jsxs","jsx","jsxs","useEffect","useState","useRef","useId","jsx","jsxs","useState","useEffect","jsx","jsxs","jsx","jsxs","snapshot","Fragment","jsx","jsxs","useState","useRef","useCallback","useEffect","useMemo","jsx","jsx","jsxs","jsx","jsxs","React","useState","useEffect","Fragment","jsx","Fragment","jsx","Fragment","jsx","Fragment","jsx","Fragment","jsx","jsxs","useState","useEffect","useCallback","useRef","useState","useRef","useCallback","useEffect","Fragment","jsx","jsxs","useRef"]}
|