{"id":"e2c18cb5-0ac9-4f61-9894-e4cc0bd08aee","entityType":"agent","slug":"clawhub-whitetowerai-video-add-captions","name":"video-add-captions","canonicalUrl":"https://www.xpersona.co/agent/clawhub-whitetowerai-video-add-captions","canonicalPath":"/agent/clawhub-whitetowerai-video-add-captions","generatedAt":"2026-10-11T14:15:40.457Z","source":"CLAWHUB","claimStatus":"UNCLAIMED","verificationTier":"NONE","summary":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-11T09:51:19.684Z","emptyReason":null},"description":"Add word-timed captions to an Open Recut program. Use this skill to map the canonical transcript through timeline.json, review a maintained style on source-backed pixels, render a local transparent HyperFrames PNG sequence, and register it as an overlay contribution for the shared delivery render.","descriptionLabel":"Source description","evidenceSummary":"Capability contract not published. No trust telemetry is available yet. 1.1K downloads reported by the source. Last updated 10/11/2026.","installCommand":"clawhub skill install s178pg569r7j3dkw6ekc9bn7258b0eme:video-add-captions","sourceUrl":"https://clawhub.ai/whitetowerai/video-add-captions","homepage":"https://clawhub.ai/whitetowerai/skills/video-add-captions","primaryLinks":[{"label":"View on ClawHub","url":"https://clawhub.ai/whitetowerai/video-add-captions","kind":"source"},{"label":"Homepage","url":"https://clawhub.ai/whitetowerai/skills/video-add-captions","kind":"homepage"}],"safetyScore":84,"overallRank":62,"popularityScore":61,"trustScore":null,"claimedByName":null,"isOwner":false,"seoDescription":"video-add-captions technical dossier on Xpersona with agent coverage, OPENCLEW support, and live trust metadata."},"coverage":{"evidence":{"source":"public-profile","verified":false,"confidence":"medium","updatedAt":"2026-10-11T09:51:19.684Z","emptyReason":null},"protocols":[{"protocol":"OPENCLEW","label":"OpenClaw","status":"self-declared","notes":"Declared in the public agent profile."}],"capabilities":[],"verifiedCount":0,"selfDeclaredCount":1,"capabilityMatrix":{"rows":[{"key":"OPENCLEW","type":"protocol","support":"unknown","confidenceSource":"profile","notes":"Listed on profile"}],"flattenedTokens":"protocol:OPENCLEW|unknown|profile"}},"adoption":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-11T09:51:19.684Z","emptyReason":null},"stars":null,"forks":null,"downloads":1095,"packageName":null,"latestVersion":"1.0.8","tractionLabel":"1.1K downloads"},"release":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-11T09:51:19.620Z","emptyReason":null},"lastUpdatedAt":"2026-10-11T09:51:19.684Z","lastCrawledAt":"2026-10-11T09:51:19.620Z","lastIndexedAt":null,"nextCrawlAt":"2026-10-12T09:51:19.620Z","lastVerifiedAt":null,"highlights":[{"version":"1.0.8","createdAt":"2026-10-07T04:13:18.173Z","changelog":"video-add-captions 1.0.8 - Documentation updated in SKILL.md: clarified motion graphics ordering, improved several explanations, and corrected minor issues. - Removed outdated skill-card.md file. - No functional or interface changes; update focuses on documentation consistency and accuracy.","fileCount":53,"zipByteSize":202652},{"version":"1.0.7","createdAt":"2026-08-19T06:37:36.937Z","changelog":"Version 1.0.7 - Adds support for spatial context planning in caption overlays, including new generation of optional caption-spatial-context.json for eligible B-roll composites. - Updates the review and decision workflow, clarifying gallery selection and review requirements. - Enables phrase-level hero_line support for Expressive cues, with maintained word-level scale policies. - Refines documentation for placement schemas, overflow policy, and structured composite handling. - Adds and updates scripts for spatial context handling; removes obsolete skill-card.md. - Improves clarity and accuracy throughout references and skill documentation.","fileCount":53,"zipByteSize":202880},{"version":"1.0.6","createdAt":"2026-08-10T05:53:09.162Z","changelog":"video-add-captions 1.0.6 - Updated documentation to clarify run order: captions must be created before cards and graphics, reserving a subtitle region for later elements. - Removed obsolete skill-card.md file. - No changes to underlying functionality or output.","fileCount":51,"zipByteSize":157957},{"version":"1.0.5","createdAt":"2026-07-31T04:51:17.802Z","changelog":"### Adds expressive presentation mode and enhances caption plan - Introduced support for expressive caption presentation mode with `bottom-standard` and `center-emphasis`, including stable cue IDs and draft planning shell. - Updated canonical workflow: prompt user to select presentation mode (Standard or Expressive) before building the caption plan. - Expanded `captions-plan.json` structure to support expressive features and clarified handling of semantic roles and karaoke options. - Strengthened documentation in SKILL.md and reference files for new expressive behaviors, evidence, and workflow requirements. - Removed obsolete `skill-card.md`.","fileCount":51,"zipByteSize":157546},{"version":"1.0.4","createdAt":"2026-07-27T10:48:26.593Z","changelog":"- Updated the documentation in SKILL.md for clarity and conciseness. - Removed skill-card.md; skill no longer includes this file.","fileCount":51,"zipByteSize":136152},{"version":"1.0.3","createdAt":"2026-07-23T08:08:35.352Z","changelog":"- Adds a check to ensure the `/video-understand` prerequisite is installed before running; now warns and aborts if missing. - Documentation clarifies that missing prerequisites will halt processing with a warning. - Removes outdated skill-card.md file.","fileCount":51,"zipByteSize":136092},{"version":"1.0.2","createdAt":"2026-07-23T07:19:20.100Z","changelog":"video-add-captions 1.0.2 - Documentation update: SKILL.md clarified that /video-understand must be run first as a prerequisite. - Minor rewording and reorganization for workflow details in SKILL.md. - Removed deprecated skill-card.md file; SKILL.md is now the sole documentation.","fileCount":51,"zipByteSize":135964},{"version":"1.0.1","createdAt":"2026-07-22T12:18:06.827Z","changelog":"### Minor update to caption style management and documentation - Updated SKILL.md for clarity, with minor corrections and streamlined instructions for style review and workflow. - Adjusted description of inputs, workflows, and outputs to match latest practices. - Improved guidance on launch and usage of review galleries. - Modified and added new caption style configuration files in the assets/style-previews directory. - Removed obsolete documentation file skill-card.md.","fileCount":51,"zipByteSize":136041}]},"execution":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No published capability contract is available yet."},"installCommand":"clawhub skill install s178pg569r7j3dkw6ekc9bn7258b0eme:video-add-captions","setupComplexity":"low","setupSteps":["Install using `clawhub skill install s178pg569r7j3dkw6ekc9bn7258b0eme:video-add-captions` in an isolated environment before connecting it to live workloads.","No published capability contract is available yet, so validate auth and request/response behavior manually.","Review the upstream CLAWHUB listing at https://clawhub.ai/whitetowerai/video-add-captions before using production credentials."],"contract":{"contractStatus":"missing","authModes":[],"requires":[],"forbidden":[],"supportsMcp":false,"supportsA2a":false,"supportsStreaming":false,"inputSchemaRef":null,"outputSchemaRef":null,"dataRegion":null,"contractUpdatedAt":null,"sourceUpdatedAt":null,"freshnessSeconds":null},"invocationGuide":{"preferredApi":{"snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-whitetowerai-video-add-captions/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-whitetowerai-video-add-captions/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-whitetowerai-video-add-captions/trust"},"curlExamples":["curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-whitetowerai-video-add-captions/snapshot\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-whitetowerai-video-add-captions/contract\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-whitetowerai-video-add-captions/trust\""],"jsonRequestTemplate":{"query":"summarize this repo","constraints":{"maxLatencyMs":2000,"protocolPreference":["OPENCLEW"]}},"jsonResponseTemplate":{"ok":true,"result":{"summary":"...","confidence":0.9},"meta":{"source":"CLAWHUB","generatedAt":"2026-10-11T14:15:40.453Z"}},"retryPolicy":{"maxAttempts":3,"backoffMs":[500,1500,3500],"retryableConditions":["HTTP_429","HTTP_503","NETWORK_TIMEOUT"]}},"endpoints":{"dossierUrl":"https://www.xpersona.co/api/v1/agents/clawhub-whitetowerai-video-add-captions/dossier","snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-whitetowerai-video-add-captions/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-whitetowerai-video-add-captions/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-whitetowerai-video-add-captions/trust"}},"reliability":{"evidence":{"source":"runtime-metrics","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No trust, reliability, or runtime telemetry is available."},"trust":{"status":"unavailable","handshakeStatus":"UNKNOWN","verificationFreshnessHours":null,"reputationScore":null,"p95LatencyMs":null,"successRate30d":null,"fallbackRate":null,"attempts30d":null,"trustUpdatedAt":null,"trustConfidence":"unknown","sourceUpdatedAt":null,"freshnessSeconds":null},"decisionGuardrails":{"doNotUseIf":["Contract metadata is missing or unavailable for deterministic execution."],"safeUseWhen":[],"riskFlags":["missing_or_unavailable_contract","trust_data_unavailable","schema_references_missing"],"operationalConfidence":"low"},"executionMetrics":{"observedLatencyMsP50":null,"observedLatencyMsP95":null,"estimatedCostUsd":null,"uptime30d":null,"rateLimitRpm":null,"rateLimitBurst":null,"lastVerifiedAt":null,"verificationSource":null},"runtimeMetrics":{"successRate":null,"avgLatencyMs":null,"avgCostUsd":null,"hallucinationRate":null,"retryRate":null,"disputeRate":null,"p50Latency":null,"p95Latency":null,"lastUpdated":null}},"benchmarks":{"evidence":{"source":"no-benchmark-data","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No benchmark suites or observed failure patterns are available."},"suites":[],"failurePatterns":[]},"artifacts":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-11T09:51:19.684Z","emptyReason":null},"readme":"Skill: video-add-captions\n\nOwner: whitetowerai\n\nSummary: Add word-timed captions to an Open Recut program. Use this skill to map the canonical transcript through timeline.json, review a maintained style on source-backed pixels, render a local transparent HyperFrames PNG sequence, and register it as an overlay contribution for the shared delivery render.\n\nTags: latest:1.0.8\n\nVersion history:\n\nv1.0.8 | 2026-10-07T04:13:18.173Z | auto\n\nvideo-add-captions 1.0.8\n\n- Documentation updated in SKILL.md: clarified motion graphics ordering, improved several explanations, and corrected minor issues.\n- Removed outdated skill-card.md file.\n- No functional or interface changes; update focuses on documentation consistency and accuracy.\n\nv1.0.7 | 2026-08-19T06:37:36.937Z | auto\n\nVersion 1.0.7\n\n- Adds support for spatial context planning in caption overlays, including new generation of optional caption-spatial-context.json for eligible B-roll composites.\n- Updates the review and decision workflow, clarifying gallery selection and review requirements.\n- Enables phrase-level hero_line support for Expressive cues, with maintained word-level scale policies.\n- Refines documentation for placement schemas, overflow policy, and structured composite handling.\n- Adds and updates scripts for spatial context handling; removes obsolete skill-card.md.\n- Improves clarity and accuracy throughout references and skill documentation.\n\nv1.0.6 | 2026-08-10T05:53:09.162Z | auto\n\nvideo-add-captions 1.0.6\n\n- Updated documentation to clarify run order: captions must be created before cards and graphics, reserving a subtitle region for later elements.\n- Removed obsolete skill-card.md file.\n- No changes to underlying functionality or output.\n\nv1.0.5 | 2026-07-31T04:51:17.802Z | auto\n\n### Adds expressive presentation mode and enhances caption plan\n\n- Introduced support for expressive caption presentation mode with `bottom-standard` and `center-emphasis`, including stable cue IDs and draft planning shell.\n- Updated canonical workflow: prompt user to select presentation mode (Standard or Expressive) before building the caption plan.\n- Expanded `captions-plan.json` structure to support expressive features and clarified handling of semantic roles and karaoke options.\n- Strengthened documentation in SKILL.md and reference files for new expressive behaviors, evidence, and workflow requirements.\n- Removed obsolete `skill-card.md`.\n\nv1.0.4 | 2026-07-27T10:48:26.593Z | auto\n\n- Updated the documentation in SKILL.md for clarity and conciseness.\n- Removed skill-card.md; skill no longer includes this file.\n\nv1.0.3 | 2026-07-23T08:08:35.352Z | auto\n\n- Adds a check to ensure the `/video-understand` prerequisite is installed before running; now warns and aborts if missing.\n- Documentation clarifies that missing prerequisites will halt processing with a warning.\n- Removes outdated skill-card.md file.\n\nv1.0.2 | 2026-07-23T07:19:20.100Z | auto\n\nvideo-add-captions 1.0.2\n\n- Documentation update: SKILL.md clarified that /video-understand must be run first as a prerequisite.\n- Minor rewording and reorganization for workflow details in SKILL.md.\n- Removed deprecated skill-card.md file; SKILL.md is now the sole documentation.\n\nv1.0.1 | 2026-07-22T12:18:06.827Z | auto\n\n### Minor update to caption style management and documentation\n\n- Updated SKILL.md for clarity, with minor corrections and streamlined instructions for style review and workflow.\n- Adjusted description of inputs, workflows, and outputs to match latest practices.\n- Improved guidance on launch and usage of review galleries.\n- Modified and added new caption style configuration files in the assets/style-previews directory.\n- Removed obsolete documentation file skill-card.md.\n\nv1.0.0 | 2026-07-22T09:52:58.950Z | auto\n\n- Initial release of the video-add-captions skill for generating word-timed caption overlays in Open Recut programs.\n- Maps canonical source transcripts through program timelines, grouping and styling captions with reviewable, source-backed evidence.\n- Outputs transparent PNG caption frames, program-timed SRTs, human/agent review pages, durable caption plans, and style decisions.\n- Requires ffmpeg/ffprobe, Python with Pillow, and Node.js 22+; manages Chrome for HTML rendering.\n- Enforces protocol around timeline mapping, style review (with strict UUID binding), and approval for reliable, reproducible caption tracks.\n\nArchive index:\n\nArchive v1.0.8: 53 files, 202652 bytes\n\nFiles: assets/captions-review.html (19535b), assets/style-previews/index.html (50456b), assets/style-previews/preview-manifest.json (22431b), assets/style-previews/props-boxed-blue.json (4292b), assets/style-previews/props-boxed-gray.json (4292b), assets/style-previews/props-boxed-green.json (4295b), assets/style-previews/props-boxed-pink.json (4292b), assets/style-previews/props-boxed-yellow.json (4298b), assets/style-previews/props-clean.json (4293b), assets/style-previews/props-minimal.json (4279b), assets/style-previews/props-pill-blue.json (4296b), assets/style-previews/props-pill-gray.json (4296b), assets/style-previews/props-pill-green.json (4299b), assets/style-previews/props-pill-pink.json (4296b), assets/style-previews/props-pill-yellow.json (4302b), assets/style-previews/props-shorts-blue.json (4319b), assets/style-previews/props-shorts-green.json (4321b), assets/style-previews/props-shorts-orange.json (4323b), assets/style-previews/props-shorts-pink.json (4319b), assets/style-previews/props-shorts-purple.json (4323b), assets/style-previews/props-shorts-yellow.json (4323b), assets/style-previews/props-social-bold-karaoke.json (4324b), assets/style-previews/props-social-bold.json (4317b), assets/style-previews/props-stroked-black.json (4299b), assets/style-previews/props-stroked-blue.json (4297b), assets/style-previews/props-stroked-green.json (4300b), assets/style-previews/props-stroked-pink.json (4297b), assets/style-previews/props-stroked-yellow.json (4303b), examples/index.html (4642b), public/gsap.min.js (72212b), reference/caption-feedback-mapping.md (6174b), reference/caption-rules.md (11879b), reference/caption-style-themes.md (2437b), scripts/build_caption_review.py (46537b), scripts/build_captions.py (23815b), scripts/build_style_preview_gallery.mjs (18396b), scripts/caption_interaction_state.mjs (34656b), scripts/caption_interaction.mjs (38165b), scripts/caption_spatial_context.py (36736b), scripts/caption_style_config.mjs (3840b), scripts/caption-styles.json (14381b), scripts/check_caption_interaction.mjs (72875b), scripts/check_caption_review.py (42733b), scripts/check_caption_spatial_context.py (23035b), scripts/check_caption_style_config.mjs (4550b), scripts/check_project_protocol.py (43931b), scripts/check_structure.ps1 (13020b), scripts/composite_caption_overlay.ps1 (2244b), scripts/generate_caption_project.mjs (48255b), scripts/render_style_previews.ps1 (18045b), skill-card.md (2164b), SKILL.md (31304b), _meta.json (137b)\n\nFile v1.0.8:SKILL.md\n\n---\nname: video-add-captions\ndescription: >\n  Add word-timed captions to an Open Recut program. Use this skill to map the\n  canonical transcript through timeline.json, review a maintained style on\n  source-backed pixels, render a local transparent HyperFrames PNG sequence,\n  and register it as an overlay contribution for the shared delivery render.\n---\n\n# Video Add Captions\n\n## Dependencies\n\n`/video-understand` is a prerequisite. Run it first so captions use the validated\nword-level transcript and canonical timeline.\nBefore starting, verify that it is installed. If it is not, warn the user that\nthis prerequisite is missing and stop before processing media.\n\nRequire `ffmpeg`/`ffprobe` on PATH, Python with `Pillow`, and Node.js >= 22 (for the `.mjs` scripts and `npx hyperframes`, fetched on demand). `hyperframes render`/`snapshot` drives a headless Chrome — it manages its own `chrome-headless-shell`, and falls back to a system Chrome (set `CHROME` to override) when the cached one is unusable. Check these before processing media.\n\n## Scope\n\nThis skill owns caption grouping, style selection, review, and the transparent\ncaption track. It does not transcribe, cut, retime, grade, reframe, or choose the\ndelivery audio policy.\n\nRun captions before content cards and motion graphics whenever either is selected.\nThe approved caption layout establishes a reserved subtitle region that both later\noperations must keep clear. This relative order also applies when only one pair is active.\n\nUse `video-understand` first. If a cut exists, caption the active program\ntimeline; do not treat source transcript seconds as program seconds.\n\n## Protocol Inputs\n\nRequired:\n\n- `work/project.json`\n- `work/understand/transcript.json`, with word-level source timestamps\n- `work/understand/media.json`\n- `work/timeline.json`\n- the source video named by `work/project.json`\n\nThe timeline is the only source-to-program mapping. All ranges are half-open\n`[start_s, end_s)`. `scripts/build_captions.py` uses the shared\n`projectlib.map_transcript_to_timeline`; words in dropped source ranges disappear,\nretained words move to program time, and cues always break at clip boundaries.\n\n## Durable Outputs\n\n```text\nwork/captions/\n|-- captions-plan.json\n|-- caption-spatial-context.json (only for eligible B-roll composites)\n`-- caption-interaction.json\nwork/cache/captions/\n|-- preview-project/\n|-- preview-snapshots/\n|-- overlay-project/\n`-- overlay-frames/frame_000001.png ...\nreview/05-captions/\n|-- captions.srt\n|-- captions-style-review-<UUID>.html\n|-- captions-style-review.html\n|-- captions-review.html\n|-- preview-early.png\n|-- preview-middle.png\n|-- preview-late.png\n|-- preview-no-caption.png\n|-- captions-evidence.json\n`-- captions-summary.md\n```\n\nGenerated HTML, copied runtime files, extracted source frames, and overlay frames\nare cache. The plan, optional spatial context, SRT, decision receipt, and review\nevidence are durable.\n\n## Caption Plan\n\n`work/captions/captions-plan.json` is the canonical caption plan, targets `overlay`, and uses\nprogram time. It records:\n\n- `timeline_id`, `program_duration_s`, and the source transcript path;\n- cue grouping settings;\n- approved style, decision mode, rationale, gallery choice, and complete resolved style;\n- source-backed review evidence and approval actor;\n- cues with compatibility fields `start`, `end`, `text`, `lines`, and `words`;\n- `program_range` and `source_ranges` on every cue;\n- program/source ranges and `clip_id` on every mapped word;\n- a local renderer recipe with exact rational FPS and hashed runtime assets.\n\nThe generator also accepts the old top-level cue array. That is a standalone\ncompatibility adapter only; it cannot express timeline provenance or regenerate an\napproved protocol contribution.\n\nStandard is the default presentation mode. Standard plans do not need a\n`presentation` field and must not be migrated merely to add one. Expressive is an\nexplicit presentation mode, not a preset: request it with\n`--presentation-mode expressive`. The build step then adds stable cue IDs, default\nword `semantic_role` values, and a draft `presentation` planning shell; it does not\nguess layout variants.\n\nExpressive supports only `bottom-standard` and `center-emphasis`. Read\n`reference/caption-rules.md` before filling the shell. The\nexisting renderer consumes only completed `presentation.layout_beats`; it does not\ninfer positions or change layout inside a beat.\n\nExpressive cues may also define one contiguous phrase-level `hero_line`.\nUse canonical `level: hero`; it renders the phrase gold on its own line at\n`1.5x`. Legacy `level: strong` remains accepted as an input alias and renders\nidentically, but new Agent-authored plans must not emit it. The maintained word roles `keyword`, `number`, and\n`contrast` each use `1.22x`; `normal` remains `1.0x`. These treatments are\northogonal to presets and do not create new style IDs. Read\n`reference/caption-rules.md` for the schema, overflow policy, and Karaoke rules.\n\nWhen an active, review-approved B-roll operation contains approved or verified\nspeaker-inset composites, captions may derive `caption-spatial-context.json` from\nthe frozen upstream plan, evidence, normalized composites, and explicit subshot\n`display_mode`. This does not modify or reselect the B-roll layout. It aligns cue\nboundaries before Expressive planning and resolves only the maintained\n`preset-bottom`, `frame-center`, `panel-center`, and boundary-only `panel-bottom`\nplacements. `panel-bottom` is allowed only for an `unsplittable_word_boundary`\nadjacent to a lower-center focused panel; it centers the stable cue inside the\nreserved strip below the structured speaker rect. Such a cue is checked against\nevery visual context it intersects, not only the context containing the word\nmidpoint. Without this context, all existing Standard and Expressive placement\nbehavior remains unchanged.\n\n## Styles\n\nMaintained presets are `clean`, `minimal`, `social-bold`, `pill`, `boxed`,\n`stroked`, and `shorts`. Gallery combination IDs add maintained themes, for\nexample `pill-yellow`, `stroked-blue`, and `shorts-green`. Karaoke is an option,\nnot a separate preset.\n\nDefault delegated choice: `clean`, Karaoke off. Choose a more expressive preset\nonly when the destination or footage justifies it. Store job-specific changes in\nan overrides JSON; do not edit the generator for ordinary style feedback.\n\nExpressive supports both `semantic-only` (Karaoke off) and\n`semantic-plus-karaoke` (Karaoke on). Semantic emphasis persists for the full cue\nin both configurations. In coexistence mode, semantic emphasis and the active\nKaraoke word use `style.wordHighlight.activeColor` as their foreground color in\nevery highlight mode; a background highlight remains an additional effect. The\neffective scale is `max(semantic scale, karaoke active scale)`, never their\nproduct. Expressive never renders an underline, including for the compatible\n`contrast` semantic role.\n\nThe existing review contract continues to use semantic-only as the primary\nExpressive evidence and binds semantic-plus-karaoke separately. The compatibility\npayload field remains named `experimental_comparison`; do not rename it without an\napproval-protocol migration.\n\nRead:\n\n- `reference/caption-rules.md` before changing grouping limits;\n- `reference/caption-style-themes.md` for style vocabulary;\n- `reference/caption-feedback-mapping.md` for override mapping.\n\n## Decision Modes\n\nTwo honest modes share the same hash-bound state machine:\n\n- `human`: pass the exact copied gallery summary to `select`, show all real\n  preview evidence, and pass the exact copied approval or revision summary to\n  `confirm` or `adjust`. Review-page-bound commands do not accept bare approval\n  phrases.\n- `agent`: use only when the user explicitly delegates caption decisions. Start\n  with a non-empty delegation note, then use `agent-select` and `agent-confirm`\n  with non-empty rationales.\n\nHuman commands fail in agent mode and agent commands fail in human mode. Standard\nwithout spatial context requires the four maintained source-backed images;\nExpressive and composite-aware reviews use their dynamic evidence contracts. A\nchange to the source, plan, timeline, style, override, project metadata,\nreview page, or evidence invalidates approval; spatial context, upstream B-roll revision,\nor bound-composite changes do the same. Rebuild the affected artifacts and review\nthem again.\n\n## Canonical Workflow\n\nRun from the repository root. Set project paths:\n\n```powershell\n$RepoRoot = (Resolve-Path \".\").Path\n$SkillRoot = Join-Path $RepoRoot \"skills\\video-add-captions\"\n$ProjectRoot = (Resolve-Path \"<project-root>\").Path\n$Work = Join-Path $ProjectRoot \"work\"\n$SourceVideo = \"<absolute path from work/project.json>\"\n$Plan = Join-Path $Work \"captions\\captions-plan.json\"\n$SpatialContext = Join-Path $Work \"captions\\caption-spatial-context.json\"\n$SpatialArgs = @()\n$SpatialReviewArgs = @()\n$Receipt = Join-Path $Work \"captions\\caption-interaction.json\"\n$Review = Join-Path $ProjectRoot \"review\\05-captions\"\n$Cache = Join-Path $Work \"cache\\captions\"\n$PreviewProject = Join-Path $Cache \"preview-project\"\n$ComparisonProject = Join-Path $Cache \"preview-project-expressive-karaoke\"\n$OverlayProject = Join-Path $Cache \"overlay-project\"\n$OverlayFrames = Join-Path $Cache \"overlay-frames\"\nNew-Item -ItemType Directory -Force -Path (Split-Path $Plan),$Review,$Cache | Out-Null\n```\n\nBefore building the caption plan, ask the user to choose the presentation mode\nunless the current request already explicitly selects one. Render the question in\nthe user's current conversation language while keeping the `Standard` and\n`Expressive` identifiers untranslated. Keep the English source prompt below in\nthis file, present the localized choice in the current session, then\n**Present + STOP**:\n\n```text\nChoose the caption presentation mode:\n\n1. Standard: Uses stable conventional placement and suits most content.\n2. Expressive: Switches between bottom and center placement based on meaning and emphasizes keywords and numbers.\n\nReply with Standard, Expressive, or \"Use the default (Standard).\"\n```\n\nDo not run `build_captions.py` until the user responds. If the user explicitly\ndelegates the choice in any wording, select Standard. State that Standard was\nselected as the compatibility-preserving default before building the plan.\n\nBuild program-time cues and the review SRT:\n\n```powershell\npython \"$SkillRoot\\scripts\\build_captions.py\" `\n  \"$Work\\understand\\transcript.json\" `\n  $Plan `\n  \"$Review\\captions.srt\" `\n  --timeline \"$Work\\timeline.json\" `\n  --source-transcript \"understand/transcript.json\" `\n  --max-chars 42 --max-lines 2 --max-dur 6 --gap 0.6\n```\n\nAfter the user selects Standard, the command above produces the unchanged Standard\nplan. After the user selects Expressive, run the same command with:\n\n```powershell\n  --presentation-mode expressive\n```\n\nAfter draft cue generation, inspect the active B-roll operation and its bound plan\nread-only. Enable composite-aware placement only when the operation is active, its\nstatus is `approved` or `verified`, `review.status` is `approved`, the\nspeaker-inset analysis/agent-input/preview/clearance bindings are complete, and at\nleast one verified or normalized shot has a maintained composite layout. The\nspatial builder uses explicit subshot `display_mode: enabled` for speaker geometry;\n`pure_broll` and missing speaker evidence never imply a speaker rectangle.\n\nWhen eligible, align draft cues to every B-roll visual boundary before filling any\nExpressive `layout_beats` or `hero_line`. Alignment splits only at word gaps and\nwrites back atomically; a boundary inside a word keeps the word whole and records\nmandatory review evidence:\n\n```powershell\npython \"$SkillRoot\\scripts\\caption_spatial_context.py\" align `\n  --project-root $ProjectRoot --plan $Plan --out-plan $Plan\n```\n\nFor an eligible project, prepare the optional arguments used by every later gate:\n\n```powershell\n$SpatialArgs = @(\"--spatial-context\", $SpatialContext)\n$SpatialReviewArgs = @(\"--spatial-context\", $SpatialContext, \"--project-root\", $ProjectRoot)\n```\n\nWhen no eligible composite exists, skip all four spatial commands and leave\n`$SpatialArgs` and `$SpatialReviewArgs` empty. Do not create or attach an empty\ncontext.\n\nBefore starting style selection or generating a preview, the Agent must plan the\nentire Expressive program in one pass:\n\n1. Read the transcript, available understanding artifacts, canonical timeline,\n   generated caption cues, and any real source-frame evidence needed to justify\n   layout changes.\n2. Fill all `presentation.layout_beats`, annotate exceptional word\n   `semantic_role` values, add at most one contiguous `hero_line` with canonical\n   `level: hero` to a cue when\n   warranted, write one rationale per beat/hero line, and write the overall\n   `presentation.planner.rationale`.\n3. Set `presentation.planning_status` to `complete` only after every cue is covered\n   exactly once.\n4. Validate the completed plan before preview generation:\n\n```powershell\npython \"$SkillRoot\\scripts\\build_captions.py\" --validate-plan $Plan\n```\n\nFor an eligible composite, build, validate, and attach the context only after the\nfinal cue IDs and Expressive candidates are complete. This preserves the upstream\nlayout and binds its current revision and hashes into the caption plan:\n\n```powershell\npython \"$SkillRoot\\scripts\\caption_spatial_context.py\" build `\n  --project-root $ProjectRoot --plan $Plan --out $SpatialContext\n```\n\n```powershell\npython \"$SkillRoot\\scripts\\caption_spatial_context.py\" validate `\n  --project-root $ProjectRoot --plan $Plan --context $SpatialContext\n```\n\n```powershell\npython \"$SkillRoot\\scripts\\caption_spatial_context.py\" attach `\n  --project-root $ProjectRoot --plan $Plan --context $SpatialContext\n```\n\nDo not ask the user to choose a position cue by cue. The user reviews the existing\nHTML evidence and corrects only a small number of anomalous beats. Starting the\ninteraction after planning keeps the existing caption-plan hash binding authoritative.\n\nStart one decision mode. For human mode, publish the maintained offline gallery\ninto the project review directory without asking the user to locate a file:\n\n```powershell\n$StartOutput = node \"$SkillRoot\\scripts\\caption_interaction.mjs\" start `\n  --state $Receipt --source $SourceVideo --captions $Plan `\n  --review-dir $Review --no-open true @SpatialArgs\n$StartOutput | Write-Host\n```\n\n`start` prints the authoritative `captions-style-review-<UUID>.html` project page\npath and the fixed question. `captions-style-review.html` is a non-authoritative\nlatest convenience alias only. The Agent must take the UUID path printed after\n`Caption style review:`, assign that exact path to `$StyleReviewPage`, and open it\nwith the native command for the host OS:\n\nWindows PowerShell:\n\n```powershell\n$StyleReviewPage = \"<authoritative UUID page path printed after Caption style review:>\"\nStart-Process -FilePath (Resolve-Path $StyleReviewPage)\n```\n\nmacOS:\n\n```bash\nopen \"$StyleReviewPage\"\n```\n\nLinux:\n\n```bash\nxdg-open \"$StyleReviewPage\"\n```\n\nIf opening fails, diagnose the command or path and retry it. Do not ask the user\nto find the page. Present the opened gallery and the fixed question exactly as\nprinted, then **Present + STOP**. Do not continue until the human copies the\nstructured summary from the page. It has this form:\n\n```text\nCaption style review\nReview: <UUID from the opened page>\nDecision: select\nChoice: pill-yellow\n```\n\nPass the user's exact summary unchanged to `select --response`. In PowerShell, a\nsingle-quoted here-string preserves the lines safely:\n\n```powershell\n$StyleResponse = @'\nCaption style review\nReview: <UUID from the opened page>\nDecision: select\nChoice: pill-yellow\n'@\nnode \"$SkillRoot\\scripts\\caption_interaction.mjs\" select `\n  --state $Receipt --response $StyleResponse\n```\n\nThe block above shows the structure; use the user's returned block, including\ntheir review UUID and choice. Never infer, normalize, or simulate a human\nresponse.\n\nFor explicit delegation, create a separate Agent-mode receipt. Keep the bound\ngallery, suppress the script's Windows-only auto-open, open the authoritative UUID\npage with the matching native command above, and inspect it before choosing:\n\n```powershell\n$StartOutput = node \"$SkillRoot\\scripts\\caption_interaction.mjs\" start `\n  --state $Receipt --source $SourceVideo --captions $Plan `\n  --review-dir $Review `\n  --decision-mode agent `\n  --delegation-note \"User delegated caption style and preview approval.\" `\n  --no-open true @SpatialArgs\n$StartOutput | Write-Host\n\nnode \"$SkillRoot\\scripts\\caption_interaction.mjs\" agent-select `\n  --state $Receipt --choice clean `\n  --rationale \"Conservative readable treatment preserves the talking-head frame.\"\n```\n\nUse `agent-select` only after inspecting the bound gallery and record the real\nrationale. Do not manufacture a human summary or use `select` in Agent mode.\n\nGenerate a transparent preview composition. The project contains only local GSAP\nand font files and preserves rational FPS in `project-meta.json`:\n\n```powershell\nnode \"$SkillRoot\\scripts\\generate_caption_project.mjs\" `\n  --video $SourceVideo --captions $Plan --out $PreviewProject `\n  --interaction-state $Receipt --project-root $ProjectRoot --mode preview @SpatialArgs\n\nnpx.cmd hyperframes check $PreviewProject --at 1 --timeout 10000 --no-contrast\n```\n\nFor the primary Expressive review, append `--karaoke false`. Generate the supported\ncoexistence comparison from the same source, plan, interaction selection, and timing by changing\nonly the output directory and appending `--karaoke true`:\n\n```powershell\nnode \"$SkillRoot\\scripts\\generate_caption_project.mjs\" `\n  --video $SourceVideo --captions $Plan --out $ComparisonProject `\n  --interaction-state $Receipt --project-root $ProjectRoot `\n  --karaoke true --mode preview @SpatialArgs\n```\n\nFor Standard, build mapped early/middle/late/no-caption evidence. This command captures\ntransparent HyperFrames snapshots, maps each program time through\n`timeline.json`, extracts the matching original source frame, and composites the\ntwo with Pillow:\n\n```powershell\npython \"$SkillRoot\\scripts\\build_caption_review.py\" `\n  --source $SourceVideo --timeline \"$Work\\timeline.json\" --plan $Plan `\n  --project $PreviewProject --cache \"$Cache\\review-cache\" --out $Review `\n  --interaction-state $Receipt @SpatialReviewArgs\n```\n\nFor Expressive, the same builder emits exhaustive machine samples, a compact\nhuman-review index, and a separately bound semantic-only/Karaoke comparison:\n\n```powershell\npython \"$SkillRoot\\scripts\\build_caption_review.py\" `\n  --source $SourceVideo --timeline \"$Work\\timeline.json\" --plan $Plan `\n  --project $PreviewProject --comparison-project $ComparisonProject `\n  --cache \"$Cache\\review-cache\" --out $Review --interaction-state $Receipt `\n  @SpatialReviewArgs\n```\n\nThe builder writes `captions-review.html` as the one authoritative page. Standard\nwithout spatial context contains exactly the four maintained evidence labels. For\nExpressive or composite-aware review, `captions-evidence.json.samples` retains all\ndense machine evidence while `review_samples` contains at most one representative\nfor each category actually present: `bottom-standard`, `center-emphasis`,\n`preset-bottom`, `frame-center`, `panel-center`, and `Hero 1.5x`. One PNG may carry\nmultiple category labels; absent categories are not fabricated. `no-caption`\nremains machine evidence, and the Karaoke comparison remains separate from the\nrepresentative count. The\nAgent must open that page with the native command for the host OS; for example,\nset `$EvidenceReviewPage = \"$Review\\captions-review.html\"` and run one of:\n\n```powershell\nStart-Process -FilePath (Resolve-Path $EvidenceReviewPage)\n```\n\n```bash\nopen \"$EvidenceReviewPage\"\n```\n\n```bash\nxdg-open \"$EvidenceReviewPage\"\n```\n\nIf opening fails, diagnose and retry. Inspect every representative primary image for readability,\nsafe-area placement, clipping, word wrapping, stable beat placement, and unwanted\npixels. Machine validation must already have passed every dense sample, including\n`no-caption`, before this gate. Standard without spatial context binds the\nmaintained four images:\n\n```powershell\n$Evidence = @(\n  \"$Review\\preview-early.png\",\n  \"$Review\\preview-middle.png\",\n  \"$Review\\preview-late.png\",\n  \"$Review\\preview-no-caption.png\"\n) -join \",\"\n\nnode \"$SkillRoot\\scripts\\caption_interaction.mjs\" preview-ready `\n  --state $Receipt --project-meta \"$PreviewProject\\project-meta.json\" `\n  --evidence $Evidence --review-page \"$Review\\captions-review.html\" `\n  --timeline \"$Work\\timeline.json\"\n```\n\nWith spatial context, keep those maintained Standard samples and add dense\nstart/25%/50%/75%/end evidence only for placement beats that carry a composite\nbackground. Do not add dense evidence for A-roll placement beats.\n\nUse that fixed four-image command only when Standard has no spatial context. For\nStandard with a bound spatial context, bind only the representative PNGs and bind\nthe complete machine evidence document separately. Standard has no coexistence comparison, so do not pass\n`--comparison-evidence`:\n\n```powershell\n$StandardSpatialEvidenceDocument = Get-Content \"$Review\\captions-evidence.json\" -Raw | ConvertFrom-Json\n$Evidence = @($StandardSpatialEvidenceDocument.review_samples | ForEach-Object {\n  Join-Path $Review $_.preview\n}) -join \",\"\n\nnode \"$SkillRoot\\scripts\\caption_interaction.mjs\" preview-ready `\n  --state $Receipt --project-meta \"$PreviewProject\\project-meta.json\" `\n  --evidence $Evidence --evidence-document \"$Review\\captions-evidence.json\" `\n  --review-page \"$Review\\captions-review.html\" `\n  --timeline \"$Work\\timeline.json\"\n```\n\nExpressive binds the representative PNGs, complete machine document, and separate\ncoexistence pair from `captions-evidence.json`:\n\n```powershell\n$EvidenceDocument = Get-Content \"$Review\\captions-evidence.json\" -Raw | ConvertFrom-Json\n$Evidence = @($EvidenceDocument.review_samples | ForEach-Object {\n  Join-Path $Review $_.preview\n}) -join \",\"\n$ComparisonEvidence = @($EvidenceDocument.experimental_comparison.samples | ForEach-Object {\n  Join-Path $Review $_.preview\n}) -join \",\"\n\nnode \"$SkillRoot\\scripts\\caption_interaction.mjs\" preview-ready `\n  --state $Receipt --project-meta \"$PreviewProject\\project-meta.json\" `\n  --evidence $Evidence --evidence-document \"$Review\\captions-evidence.json\" `\n  --comparison-evidence $ComparisonEvidence `\n  --review-page \"$Review\\captions-review.html\" --timeline \"$Work\\timeline.json\"\n```\n\nIn human mode, present the opened page and its images, then **Present + STOP**.\nThe human copies one of these structured summaries from the page:\n\n```text\nCaption preview review\nReview: <UUID from the opened page>\nDecision: approve\nEvidence: early, middle, late, no-caption\n```\n\nExpressive approval uses the dynamic evidence token and requires the explicit\nKaraoke choice made after inspecting the bound comparison evidence:\n\n```text\nCaption preview review\nReview: <UUID from the opened page>\nDecision: approve\nEvidence: expressive-layout-beats\nKaraoke: on|off\n```\n\nWhen `caption-spatial-context.json` is bound, the authoritative page emits the\ncomposite-aware token. Standard uses the same block without the Karaoke line;\nExpressive requires the explicit Karaoke choice:\n\n```text\nCaption preview review\nReview: <UUID from the opened page>\nDecision: approve\nEvidence: composite-aware\nKaraoke: on|off\n```\n\n```text\nCaption preview review\nReview: <UUID from the opened page>\nDecision: revise\nChanges: Raise captions 20 pixels.\n```\n\nPass the returned summary unchanged to `confirm --response` or\n`adjust --response`. Quote the multiline response safely in PowerShell:\n\n```powershell\n$PreviewResponse = @'\nCaption preview review\nReview: <UUID from the opened page>\nDecision: approve\nEvidence: early, middle, late, no-caption\n'@\nnode \"$SkillRoot\\scripts\\caption_interaction.mjs\" confirm `\n  --state $Receipt --response $PreviewResponse\n```\n\nFor a `revise` summary, preserve it in `$PreviewResponse` the same way and run:\n\n```powershell\nnode \"$SkillRoot\\scripts\\caption_interaction.mjs\" adjust `\n  --state $Receipt --response $PreviewResponse\n```\n\nApply the requested change, then regenerate the preview project, review page, and\nall evidence before presenting the gate again. Never convert human feedback into\nan Agent decision.\n\nIn delegated mode, the Agent must inspect the same `captions-review.html` page and\nall primary evidence plus any coexistence comparison before recording its own rationale.\nFor Standard, keep the existing command unchanged:\n\n```powershell\nnode \"$SkillRoot\\scripts\\caption_interaction.mjs\" agent-confirm `\n  --state $Receipt `\n  --rationale \"All bound primary frames are readable and collision-free; coexistence comparison evidence was inspected separately when present.\"\n```\n\nFor Expressive, pass the explicit Karaoke choice made from the comparison:\n\n```powershell\nnode \"$SkillRoot\\scripts\\caption_interaction.mjs\" agent-confirm `\n  --state $Receipt --karaoke off `\n  --rationale \"The semantic-only comparison is clearer for this footage.\"\n```\n\nGenerate the approved overlay project. This formal render is the only step that\nfinalizes `style`, `review`, and hashed runtime assets in the canonical plan.\nExpressive overlay generation uses the preview-approved Karaoke choice; an\nexplicit `--karaoke` value must match that approval:\n\nFor Expressive, formal overlay generation stores the Human-visible representative\nPNGs in `review.representative_evidence`. It keeps `review.evidence` as the shared\ndelivery compiler compatibility set: one already-bound machine sample per layout beat\nplus `no-caption`, selected from `captions-evidence.json`. This does not add cards to\nthe human review or claim that the user inspected machine-only samples; the complete\ndocument path, hash, and sample count remain in `review.machine_evidence_document`.\n\n```powershell\nnode \"$SkillRoot\\scripts\\generate_caption_project.mjs\" `\n  --video $SourceVideo --captions $Plan --out $OverlayProject `\n  --interaction-state $Receipt --project-root $ProjectRoot --mode overlay @SpatialArgs\n\nnpx.cmd hyperframes check $OverlayProject --at 1 --timeout 10000 --no-contrast\n\n$RenderMeta = Get-Content -Raw \"$OverlayProject\\project-meta.json\" | ConvertFrom-Json\n$RenderFps = \"$($RenderMeta.fpsRational.num)/$($RenderMeta.fpsRational.den)\"\nPush-Location $OverlayProject\ntry {\n  npx.cmd hyperframes render --format png-sequence --fps $RenderFps --output $OverlayFrames\n}\nfinally {\n  Pop-Location\n}\n```\n\nCanonical overlay generation refreshes the `## Approval` section in\n`captions-summary.md` with the selected style, decision actor/rationale, and\nhash-binding result. It deliberately leaves rendered-frame and shared-delivery\nchecks pending; replace that pending line with the measured results before marking\nthe operation verified.\n\nAfter cache deletion, rebuild the composition without replaying approval:\n\n```powershell\nnode \"$SkillRoot\\scripts\\generate_caption_project.mjs\" `\n  --video $SourceVideo --captions $Plan --out $OverlayProject `\n  --approved-plan true --project-root $ProjectRoot --mode overlay @SpatialArgs\n```\n\nThe command verifies that the frozen runtime hashes still match.\n\n## Project Registration\n\nAdd or revise one `captions` operation in `work/project.json`. Depend on\n`understanding`; also depend on the active `cut` operation when it exists.\nWhen active B-roll supplied the spatial context, also add `b-roll` to\n`depends_on`; its `based_on` value must equal the bound active B-roll revision.\nWhen there is no spatial context, omit `b-roll` from `depends_on` and\n`based_on`. All `based_on` values must equal the current dependency revisions.\n\n```json\n{\n  \"id\": \"captions\",\n  \"skill\": \"video-add-captions\",\n  \"revision\": 1,\n  \"depends_on\": [\"understanding\", \"cut\", \"b-roll\"],\n  \"based_on\": {\"understanding\": 1, \"cut\": 1, \"b-roll\": 4},\n  \"status\": \"verified\",\n  \"plan\": \"captions/captions-plan.json\",\n  \"outputs\": [\"cache/captions/overlay-frames\"],\n  \"target\": {\"sequence\": \"main\", \"scope\": \"captions\"},\n  \"effects\": {\n    \"changes_timeline\": false,\n    \"changes_geometry\": false,\n    \"changes_video_pixels\": false,\n    \"changes_audio\": false,\n    \"adds_track\": \"captions\"\n  },\n  \"check\": {\"status\": \"pass\", \"report\": \"../review/05-captions/captions-summary.md\"},\n  \"render\": {\n    \"kind\": \"overlay\",\n    \"asset\": \"cache/captions/overlay-frames\",\n    \"asset_type\": \"image-sequence\",\n    \"pattern\": \"frame_%06d.png\",\n    \"start_number\": 1,\n    \"fps\": {\"num\": 30000, \"den\": 1001}\n  }\n}\n```\n\nUse the actual timeline FPS. Insert `captions` before active `content-cards` and\n`motion-graphics` operations. Captions never depend on either downstream operation;\nadding captions to a project that already contains them requires reordering the active\nsequence. Then compile and render delivery once:\n\n```powershell\npython \"$RepoRoot\\skills\\video-understand\\scripts\\validate.py\" project `\n  \"$Work\\project.json\" $ProjectRoot\npython \"$RepoRoot\\skills\\video-understand\\scripts\\build_render_plan.py\" $ProjectRoot\npython \"$RepoRoot\\skills\\video-understand\\scripts\\render_project.py\" `\n  \"$Work\\render\\render-plan.json\"\n```\n\nThe shared renderer copies audio only for an identity timeline with no audio\nfilters. Cuts or retiming require encoded/filtered audio. Caption code never\noverrides that decision.\n\n## Compatibility\n\nWithout `--timeline`, `build_captions.py` still writes the old cue array.\n`generate_caption_project.mjs` accepts that array with the interaction receipt.\nStandalone exact ID and skip responses are legacy compatibility only. Standalone\n`approve` is also a legacy compatibility response. Historical non-English aliases\nremain accepted as input compatibility but are never emitted or documented as\nuser instructions.\n`composite_caption_overlay.ps1` accepts an overlay video or `frame_%06d.png`\ndirectory and writes compatible H.264/yuv420p while copying source audio. Use this\nstandalone path only when no active operation changes time; canonical projects use\nthe shared renderer.\n\n## Self Check\n\nRun before declaring the operation verified:\n\n```powershell\npython \"$SkillRoot\\scripts\\check_project_protocol.py\"\nnode \"$SkillRoot\\scripts\\check_caption_style_config.mjs\"\npython \"$SkillRoot\\scripts\\check_caption_spatial_context.py\"\npython \"$SkillRoot\\scripts\\check_caption_review.py\"\nnode \"$SkillRoot\\scripts\\check_caption_interaction.mjs\"\npowershell.exe -ExecutionPolicy Bypass -File \"$SkillRoot\\scripts\\check_structure.ps1\"\nnode \"$SkillRoot\\scripts\\caption_interaction.mjs\" status --state $Receipt\n```\n\nAlso verify:\n\n- cue and word times are finite, ordered, within program duration, and never cross clips;\n- early/middle/late captions appear on the correct mapped source pixels;\n- dropped speech has no cue;\n- overlay first frame exists, dimensions match, FPS equals timeline rational FPS,\n  frame count equals `ceil(program_duration_s * fps.num / fps.den)`, duration covers\n  the complete program, and non-caption pixels are transparent;\n- the shared delivery exists, has synchronized audio, and passes the project's\n  delivery report;\n- `captions-summary.md` records the selected style, approval mode/rationale,\n  evidence, and validation result.\n\nHTML generation alone is not success. Inspect actual pixels and the final delivery\nbefore reporting completion.\n\nFile v1.0.8:_meta.json\n\n{\n  \"ownerId\": \"kn70hcphbcepjj43vwv7j0zbnh8b0jpm\",\n  \"slug\": \"video-add-captions\",\n  \"version\": \"1.0.8\",\n  \"publishedAt\": 1791346398173\n}\n\nFile v1.0.8:scripts/caption-styles.json\n\n{\n  \"presets\": {\n    \"clean\": {\n      \"preset\": \"clean\",\n      \"font\": {\n        \"family\": \"system-ui, sans-serif\",\n        \"sizeRatio\": 0.0416,\n        \"weight\": 800,\n        \"color\": \"#F5F4ED\",\n        \"lineHeight\": 1.32,\n        \"letterSpacing\": 0\n      },\n      \"layout\": {\n        \"anchor\": \"bottom\",\n        \"align\": \"center\",\n        \"maxWidth\": 0.92,\n        \"paddingBottomRatio\": 0.07\n      },\n      \"background\": {\n        \"enabled\": false,\n        \"theme\": \"gray\",\n        \"shape\": \"rounded\",\n        \"color\": \"#000000\",\n        \"opacity\": 0.48,\n        \"radiusRatio\": 0.018,\n        \"paddingXRatio\": 0.035,\n        \"paddingYRatio\": 0.018\n      },\n      \"effects\": {\n        \"shadow\": {\n          \"strength\": \"none\",\n          \"color\": \"#000000\",\n          \"opacity\": 0.55,\n          \"offsetYRatio\": 0.02,\n          \"blurRatio\": 0.04\n        },\n        \"stroke\": {\n          \"strength\": \"none\",\n          \"color\": \"#000000\",\n          \"widthRatio\": 0\n        }\n      },\n      \"stroke\": {\n        \"enabled\": false,\n        \"theme\": \"black\",\n        \"color\": \"#000000\",\n        \"opacity\": 0.85,\n        \"widthRatio\": 0\n      },\n      \"wordHighlight\": {\n        \"enabled\": true,\n        \"mode\": \"textColor\",\n        \"activeColor\": \"#FF7A45\",\n        \"activeScale\": 1.12,\n        \"upcomingOpacity\": 0.55,\n        \"backgroundColor\": \"#FF7A45\",\n        \"backgroundOpacity\": 0.24,\n        \"backgroundRadiusRatio\": 0.01\n      },\n      \"animation\": {\n        \"type\": \"pop\",\n        \"popInFrames\": 6,\n        \"popOutFrames\": 4,\n        \"translateYPx\": 22\n      }\n    },\n    \"minimal\": {\n      \"preset\": \"minimal\",\n      \"font\": {\n        \"family\": \"system-ui, sans-serif\",\n        \"sizeRatio\": 0.0352,\n        \"weight\": 650,\n        \"color\": \"#FFFFFF\",\n        \"lineHeight\": 1.342,\n        \"letterSpacing\": 0\n      },\n      \"layout\": {\n        \"anchor\": \"bottom\",\n        \"align\": \"center\",\n        \"maxWidth\": 0.86,\n        \"paddingBottomRatio\": 0.075\n      },\n      \"background\": {\n        \"enabled\": false,\n        \"theme\": \"gray\",\n        \"shape\": \"rounded\",\n        \"color\": \"#000000\",\n        \"opacity\": 0.32,\n        \"radiusRatio\": 0.014,\n        \"paddingXRatio\": 0.028,\n        \"paddingYRatio\": 0.014\n      },\n      \"effects\": {\n        \"shadow\": {\n          \"strength\": \"none\",\n          \"color\": \"#000000\",\n          \"opacity\": 0,\n          \"offsetYRatio\": 0,\n          \"blurRatio\": 0\n        },\n        \"stroke\": {\n          \"strength\": \"none\",\n          \"color\": \"#000000\",\n          \"widthRatio\": 0\n        }\n      },\n      \"stroke\": {\n        \"enabled\": false,\n        \"theme\": \"black\",\n        \"color\": \"#000000\",\n        \"opacity\": 0.85,\n        \"widthRatio\": 0\n      },\n      \"wordHighlight\": {\n        \"enabled\": false,\n        \"mode\": \"none\",\n        \"activeColor\": \"#FFFFFF\",\n        \"activeScale\": 1,\n        \"upcomingOpacity\": 1,\n        \"backgroundColor\": \"#FFFFFF\",\n        \"backgroundOpacity\": 0,\n        \"backgroundRadiusRatio\": 0.01\n      },\n      \"animation\": {\n        \"type\": \"fade\",\n        \"popInFrames\": 6,\n        \"popOutFrames\": 4,\n        \"translateYPx\": 0\n      }\n    },\n    \"social-bold\": {\n      \"preset\": \"social-bold\",\n      \"font\": {\n        \"family\": \"system-ui, sans-serif\",\n        \"sizeRatio\": 0.0826,\n        \"weight\": 850,\n        \"color\": \"#FFFFFF\",\n        \"lineHeight\": 1.232,\n        \"letterSpacing\": 0\n      },\n      \"layout\": {\n        \"anchor\": \"bottom\",\n        \"align\": \"center\",\n        \"maxWidth\": 0.94,\n        \"paddingBottomRatio\": 0.08\n      },\n      \"background\": {\n        \"enabled\": false,\n        \"theme\": \"gray\",\n        \"shape\": \"rounded\",\n        \"color\": \"#000000\",\n        \"opacity\": 0.5,\n        \"radiusRatio\": 0.018,\n        \"paddingXRatio\": 0.035,\n        \"paddingYRatio\": 0.018\n      },\n      \"effects\": {\n        \"shadow\": {\n          \"strength\": \"none\",\n          \"color\": \"#000000\",\n          \"opacity\": 0.78,\n          \"offsetYRatio\": 0.04,\n          \"blurRatio\": 0.08\n        },\n        \"stroke\": {\n          \"strength\": \"soft\",\n          \"color\": \"#000000\",\n          \"widthRatio\": 0.0035\n        }\n      },\n      \"stroke\": {\n        \"enabled\": false,\n        \"theme\": \"black\",\n        \"color\": \"#000000\",\n        \"opacity\": 0.85,\n        \"widthRatio\": 0\n      },\n      \"wordHighlight\": {\n        \"enabled\": true,\n        \"mode\": \"textColor\",\n        \"activeColor\": \"#FFD43B\",\n        \"activeScale\": 1.14,\n        \"upcomingOpacity\": 0.58,\n        \"backgroundColor\": \"#FFD43B\",\n        \"backgroundOpacity\": 0.28,\n        \"backgroundRadiusRatio\": 0.012\n      },\n      \"animation\": {\n        \"type\": \"pop\",\n        \"popInFrames\": 5,\n        \"popOutFrames\": 4,\n        \"translateYPx\": 24\n      }\n    },\n    \"pill\": {\n      \"preset\": \"pill\",\n      \"font\": {\n        \"family\": \"system-ui, sans-serif\",\n        \"sizeRatio\": 0.0416,\n        \"weight\": 800,\n        \"color\": \"#FFFFFF\",\n        \"lineHeight\": 1.298,\n        \"letterSpacing\": 0\n      },\n      \"layout\": {\n        \"anchor\": \"bottom\",\n        \"align\": \"center\",\n        \"maxWidth\": 0.9,\n        \"paddingBottomRatio\": 0.075\n      },\n      \"background\": {\n        \"enabled\": true,\n        \"theme\": \"gray\",\n        \"shape\": \"pill\",\n        \"color\": \"#111827\",\n        \"opacity\": 0.62,\n        \"radiusRatio\": 0.04,\n        \"paddingXRatio\": 0.04,\n        \"paddingYRatio\": 0.0126\n      },\n      \"effects\": {\n        \"shadow\": {\n          \"strength\": \"none\",\n          \"color\": \"#000000\",\n          \"opacity\": 0.34,\n          \"offsetYRatio\": 0.014,\n          \"blurRatio\": 0.035\n        },\n        \"stroke\": {\n          \"strength\": \"none\",\n          \"color\": \"#000000\",\n          \"widthRatio\": 0\n        }\n      },\n      \"stroke\": {\n        \"enabled\": false,\n        \"theme\": \"black\",\n        \"color\": \"#000000\",\n        \"opacity\": 0.85,\n        \"widthRatio\": 0\n      },\n      \"wordHighlight\": {\n        \"enabled\": true,\n        \"mode\": \"textColor\",\n        \"activeColor\": \"#FFB000\",\n        \"activeScale\": 1.12,\n        \"upcomingOpacity\": 0.62,\n        \"backgroundColor\": \"#FFB000\",\n        \"backgroundOpacity\": 0.28,\n        \"backgroundRadiusRatio\": 0.012\n      },\n      \"animation\": {\n        \"type\": \"pop\",\n        \"popInFrames\": 6,\n        \"popOutFrames\": 4,\n        \"translateYPx\": 18\n      }\n    },\n    \"boxed\": {\n      \"preset\": \"boxed\",\n      \"font\": {\n        \"family\": \"system-ui, sans-serif\",\n        \"sizeRatio\": 0.0416,\n        \"weight\": 800,\n        \"color\": \"#FFFFFF\",\n        \"lineHeight\": 1.298,\n        \"letterSpacing\": 0\n      },\n      \"layout\": {\n        \"anchor\": \"bottom\",\n        \"align\": \"center\",\n        \"maxWidth\": 0.9,\n        \"paddingBottomRatio\": 0.07\n      },\n      \"background\": {\n        \"enabled\": true,\n        \"theme\": \"gray\",\n        \"shape\": \"rounded\",\n        \"color\": \"#111111\",\n        \"opacity\": 0.72,\n        \"radiusRatio\": 0.008,\n        \"paddingXRatio\": 0.032,\n        \"paddingYRatio\": 0.0112\n      },\n      \"effects\": {\n        \"shadow\": {\n          \"strength\": \"none\",\n          \"color\": \"#000000\",\n          \"opacity\": 0,\n          \"offsetYRatio\": 0,\n          \"blurRatio\": 0\n        },\n        \"stroke\": {\n          \"strength\": \"none\",\n          \"color\": \"#000000\",\n          \"widthRatio\": 0\n        }\n      },\n      \"stroke\": {\n        \"enabled\": false,\n        \"theme\": \"black\",\n        \"color\": \"#000000\",\n        \"opacity\": 0.85,\n        \"widthRatio\": 0\n      },\n      \"wordHighlight\": {\n        \"enabled\": true,\n        \"mode\": \"textColor\",\n        \"activeColor\": \"#8BE9FD\",\n        \"activeScale\": 1.12,\n        \"upcomingOpacity\": 0.68,\n        \"backgroundColor\": \"#8BE9FD\",\n        \"backgroundOpacity\": 0.22,\n        \"backgroundRadiusRatio\": 0.008\n      },\n      \"animation\": {\n        \"type\": \"fade\",\n        \"popInFrames\": 5,\n        \"popOutFrames\": 4,\n        \"translateYPx\": 0\n      }\n    },\n    \"stroked\": {\n      \"preset\": \"stroked\",\n      \"font\": {\n        \"family\": \"system-ui, sans-serif\",\n        \"sizeRatio\": 0.0416,\n        \"weight\": 800,\n        \"color\": \"#F5F4ED\",\n        \"lineHeight\": 1.32,\n        \"letterSpacing\": 0\n      },\n      \"layout\": {\n        \"anchor\": \"bottom\",\n        \"align\": \"center\",\n        \"maxWidth\": 0.92,\n        \"paddingBottomRatio\": 0.07\n      },\n      \"background\": {\n        \"enabled\": false,\n        \"theme\": \"gray\",\n        \"shape\": \"rounded\",\n        \"color\": \"#000000\",\n        \"opacity\": 0.48,\n        \"radiusRatio\": 0.018,\n        \"paddingXRatio\": 0.035,\n        \"paddingYRatio\": 0.018\n      },\n      \"effects\": {\n        \"shadow\": {\n          \"strength\": \"none\",\n          \"color\": \"#000000\",\n          \"opacity\": 0,\n          \"offsetYRatio\": 0,\n          \"blurRatio\": 0\n        },\n        \"stroke\": {\n          \"strength\": \"none\",\n          \"color\": \"#000000\",\n          \"widthRatio\": 0\n        }\n      },\n      \"stroke\": {\n        \"enabled\": true,\n        \"theme\": \"black\",\n        \"color\": \"#000000\",\n        \"opacity\": 0.9,\n        \"widthRatio\": 0.009\n      },\n      \"wordHighlight\": {\n        \"enabled\": true,\n        \"mode\": \"textColor\",\n        \"activeColor\": \"#FFD43B\",\n        \"activeScale\": 1,\n        \"upcomingOpacity\": 0.58,\n        \"backgroundColor\": \"#FFD43B\",\n        \"backgroundOpacity\": 0.28,\n        \"backgroundRadiusRatio\": 0.012\n      },\n      \"animation\": {\n        \"type\": \"pop\",\n        \"popInFrames\": 6,\n        \"popOutFrames\": 4,\n        \"translateYPx\": 18\n      }\n    },\n    \"shorts\": {\n      \"preset\": \"shorts\",\n      \"font\": {\n        \"family\": \"Cal_Sans, Inter, system-ui, sans-serif\",\n        \"sizeRatio\": 0.035,\n        \"weight\": 900,\n        \"color\": \"#FFFFFF\",\n        \"lineHeight\": 1.18,\n        \"letterSpacing\": 0.012\n      },\n      \"layout\": {\n        \"anchor\": \"bottom\",\n        \"align\": \"center\",\n        \"maxWidth\": 0.9,\n        \"paddingBottomRatio\": 0.2\n      },\n      \"background\": {\n        \"enabled\": false,\n        \"theme\": \"gray\",\n        \"shape\": \"rounded\",\n        \"color\": \"#000000\",\n        \"opacity\": 0.48,\n        \"radiusRatio\": 0.018,\n        \"paddingXRatio\": 0.035,\n        \"paddingYRatio\": 0.018\n      },\n      \"effects\": {\n        \"shadow\": {\n          \"strength\": \"none\",\n          \"color\": \"#000000\",\n          \"opacity\": 0.35,\n          \"offsetYRatio\": 0.014,\n          \"blurRatio\": 0.03\n        },\n        \"stroke\": {\n          \"strength\": \"none\",\n          \"color\": \"#000000\",\n          \"widthRatio\": 0\n        }\n      },\n      \"stroke\": {\n        \"enabled\": true,\n        \"theme\": \"black\",\n        \"color\": \"#000000\",\n        \"opacity\": 0.92,\n        \"widthRatio\": 0.008\n      },\n      \"wordHighlight\": {\n        \"enabled\": true,\n        \"mode\": \"textColor\",\n        \"activeColor\": \"#21D32E\",\n        \"activeScale\": 1,\n        \"upcomingOpacity\": 0.62,\n        \"backgroundColor\": \"#21D32E\",\n        \"backgroundOpacity\": 0.28,\n        \"backgroundRadiusRatio\": 0.012\n      },\n      \"animation\": {\n        \"type\": \"pop\",\n        \"popInFrames\": 5,\n        \"popOutFrames\": 4,\n        \"translateYPx\": 20\n      }\n    }\n  },\n  \"themes\": {\n    \"highlight\": {\n      \"yellow\": {\n        \"wordHighlight\": {\n          \"activeColor\": \"#F8F54F\",\n          \"backgroundColor\": \"#F8F54F\"\n        }\n      },\n      \"green\": {\n        \"wordHighlight\": {\n          \"activeColor\": \"#21D32E\",\n          \"backgroundColor\": \"#21D32E\"\n        }\n      },\n      \"orange\": {\n        \"wordHighlight\": {\n          \"activeColor\": \"#F8BD6D\",\n          \"backgroundColor\": \"#F8BD6D\"\n        }\n      },\n      \"purple\": {\n        \"wordHighlight\": {\n          \"activeColor\": \"#C084FC\",\n          \"backgroundColor\": \"#C084FC\"\n        }\n      },\n      \"blue\": {\n        \"wordHighlight\": {\n          \"activeColor\": \"#2563EB\",\n          \"backgroundColor\": \"#2563EB\"\n        }\n      },\n      \"pink\": {\n        \"wordHighlight\": {\n          \"activeColor\": \"#DB2777\",\n          \"backgroundColor\": \"#DB2777\"\n        }\n      }\n    },\n    \"background\": {\n      \"gray\": {\n        \"background\": {\n          \"color\": \"#111827\",\n          \"opacity\": 0.62\n        },\n        \"font\": {\n          \"color\": \"#FFFFFF\"\n        },\n        \"wordHighlight\": {\n          \"activeColor\": \"#FFD43B\"\n        }\n      },\n      \"yellow\": {\n        \"background\": {\n          \"color\": \"#FACC15\",\n          \"opacity\": 0.82\n        },\n        \"font\": {\n          \"color\": \"#1A1A1A\"\n        },\n        \"wordHighlight\": {\n          \"activeColor\": \"#B45309\"\n        }\n      },\n      \"blue\": {\n        \"background\": {\n          \"color\": \"#2563EB\",\n          \"opacity\": 0.72\n        },\n        \"font\": {\n          \"color\": \"#FFFFFF\"\n        },\n        \"wordHighlight\": {\n          \"activeColor\": \"#BFDBFE\"\n        }\n      },\n      \"pink\": {\n        \"background\": {\n          \"color\": \"#DB2777\",\n          \"opacity\": 0.72\n        },\n        \"font\": {\n          \"color\": \"#FFFFFF\"\n        },\n        \"wordHighlight\": {\n          \"activeColor\": \"#FBCFE8\"\n        }\n      },\n      \"green\": {\n        \"background\": {\n          \"color\": \"#16A34A\",\n          \"opacity\": 0.72\n        },\n        \"font\": {\n          \"color\": \"#FFFFFF\"\n        },\n        \"wordHighlight\": {\n          \"activeColor\": \"#BBF7D0\"\n        }\n      }\n    },\n    \"stroke\": {\n      \"black\": {\n        \"stroke\": {\n          \"color\": \"#000000\",\n          \"opacity\": 0.9\n        },\n        \"wordHighlight\": {\n          \"activeColor\": \"#FFD43B\"\n        }\n      },\n      \"yellow\": {\n        \"stroke\": {\n          \"color\": \"#CA8A04\",\n          \"opacity\": 0.95\n        },\n        \"wordHighlight\": {\n          \"activeColor\": \"#CA8A04\"\n        }\n      },\n      \"blue\": {\n        \"stroke\": {\n          \"color\": \"#2563EB\",\n          \"opacity\": 0.95\n        },\n        \"wordHighlight\": {\n          \"activeColor\": \"#93C5FD\"\n        }\n      },\n      \"pink\": {\n        \"stroke\": {\n          \"color\": \"#DB2777\",\n          \"opacity\": 0.95\n        },\n        \"wordHighlight\": {\n          \"activeColor\": \"#F9A8D4\"\n        }\n      },\n      \"green\": {\n        \"stroke\": {\n          \"color\": \"#16A34A\",\n          \"opacity\": 0.95\n        },\n        \"wordHighlight\": {\n          \"activeColor\": \"#86EFAC\"\n        }\n      }\n    }\n  },\n  \"expressiveTreatments\": {\n    \"semanticScaleByRole\": {\n      \"normal\": 1.0,\n      \"keyword\": 1.22,\n      \"number\": 1.22,\n      \"contrast\": 1.22\n    },\n    \"heroLine\": {\n      \"color\": \"#F4C542\",\n      \"lineHeight\": 1.05,\n      \"canonicalLevel\": \"hero\",\n      \"levels\": {\n        \"strong\": {\n          \"scale\": 1.5\n        },\n        \"hero\": {\n          \"scale\": 1.5\n        }\n      }\n    }\n  }\n}\n\nFile v1.0.8:reference/caption-feedback-mapping.md\n\n# Caption Feedback Mapping\n\nThis skill is agent-facing. In the canonical workflow, users choose a style and\napprove or revise source-backed previews by copying structured summaries from the\nbound HTML review pages. Standalone compatibility accepts an exact gallery\ncombination ID or `skip`, and accepts `approve` only after source-backed preview\nevidence exists. Historical non-English aliases remain accepted silently but are\nnot user instructions. The agent maps only recorded user feedback to optional JSON\noverrides accepted by `scripts/generate_caption_project.mjs`.\n\n## Safe Edit Points\n\n- Use `scripts/caption-styles.json` as the source of official preset and theme names.\n- Record the exact gallery response with `scripts/caption_interaction.mjs select`.\n- Pass `--interaction-state` to the generator. The generator reads the selected\n  preset, themes, and Karaoke value from that state and rejects conflicting flags.\n- Put only requested property overrides in a JSON file passed with `--overrides`.\n- Edit caption cue JSON only when correcting subtitle text or timing data.\n\nThe user may skip gallery selection, which explicitly chooses `clean`. The\nsource-backed preview confirmation cannot be skipped. Canonical full rendering\nrequires the exact copied approval summary; standalone compatibility requires the\nexact public response `approve`.\n\n## Official Style Vocabulary\n\nOfficial presets:\n\n- `clean`\n- `minimal`\n- `social-bold`\n- `pill`\n- `boxed`\n- `stroked`\n- `shorts`\n\nBackground themes:\n\n- `gray`\n- `yellow`\n- `blue`\n- `pink`\n- `green`\n\nStroke themes:\n\n- `black`\n- `yellow`\n- `blue`\n- `pink`\n- `green`\n\nShorts highlight colors:\n\n- `yellow` -> `#F8F54F`\n- `green` -> `#21D32E`\n- `orange` -> `#F8BD6D`\n- `black` -> `#000000`\n- `blue` -> `#2563EB`\n- `pink` -> `#DB2777`\n\nKaraoke is an option, not a preset. Preview ids such as `pill-yellow`,\n`boxed-green`, `stroked-blue`, `shorts-yellow`, and `social-bold-karaoke` are\npreset/config combinations, not official presets.\n\n## Feedback Rules\n\nFont and visual intensity:\n\n- \"make the text bigger\" -> increase `font.sizeRatio`\n- \"make the text smaller\" -> decrease `font.sizeRatio`\n- \"cleaner\" -> move toward `clean` or `minimal`\n- \"more subtle\" -> move toward `minimal`\n- \"more eye-catching\" -> move toward `social-bold`\n- \"more like TikTok\", \"big short-video captions\" -> move toward `social-bold`\n- \"vertical shorts style\", \"Reels style\", \"YouTube Shorts style\" -> use `shorts`\n\nBackground:\n\n- \"no background\" -> `background.enabled = false`\n- \"add a background\" -> choose `pill` or `boxed`\n- \"rounded background\", \"capsule background\" -> `preset = \"pill\"` or `background.shape = \"pill\"`\n- \"small rounded background\", \"rectangle background bar\" -> `preset = \"boxed\"` or `background.shape = \"rounded\"`\n- \"make the background more transparent\" -> decrease `background.opacity`\n- \"make the background stronger\" -> increase `background.opacity`\n- \"gray background\" -> `background.theme = \"gray\"`\n- \"yellow background\" -> `background.theme = \"yellow\"`\n- \"blue background\" -> `background.theme = \"blue\"`\n- \"pink background\" -> `background.theme = \"pink\"`\n- \"green background\" -> `background.theme = \"green\"`\n\nStroke:\n\n- \"no background, add text outline\" -> `preset = \"stroked\"` and `background.enabled = false`\n- \"black outline\" -> `stroke.theme = \"black\"`\n- \"yellow outline\" -> `stroke.theme = \"yellow\"`\n- \"blue outline\" -> `stroke.theme = \"blue\"`\n- \"pink outline\" -> `stroke.theme = \"pink\"`\n- \"green outline\" -> `stroke.theme = \"green\"`\n- \"make the outline thicker\" -> increase `stroke.widthRatio`\n- \"make the outline thinner\" -> decrease `stroke.widthRatio`\n- \"remove the outline\" -> `stroke.enabled = false`\n\nKaraoke / word highlight:\n\n- \"enable word-by-word highlight\", \"karaoke\" -> `karaoke = true` or `wordHighlight.enabled = true`\n- \"disable word-by-word highlight\" -> `karaoke = false` or `wordHighlight.enabled = false`\n- \"do not enlarge the active word\" -> `wordHighlight.activeScale = 1`\n- \"yellow highlight\" -> choose a yellow `wordHighlight.activeColor`\n- \"blue highlight\" -> choose a blue `wordHighlight.activeColor`\n- \"pink highlight\" -> choose a pink `wordHighlight.activeColor`\n- \"green highlight\" -> choose a green `wordHighlight.activeColor`\n\nShorts highlight:\n\n- \"shorts with green highlight\" -> `preset = \"shorts\"`, `wordHighlight.activeColor = \"#21D32E\"`\n- \"shorts with orange highlight\" -> `preset = \"shorts\"`, `wordHighlight.activeColor = \"#F8BD6D\"`\n- \"shorts with yellow highlight\" -> `preset = \"shorts\"`, `wordHighlight.activeColor = \"#F8F54F\"`\n- \"shorts with black highlight\" -> `preset = \"shorts\"`, `wordHighlight.activeColor = \"#000000\"`\n- \"shorts with blue highlight\" -> `preset = \"shorts\"`, `wordHighlight.activeColor = \"#2563EB\"`\n- \"shorts with pink highlight\" -> `preset = \"shorts\"`, `wordHighlight.activeColor = \"#DB2777\"`\n- \"shorts, no karaoke\" -> `preset = \"shorts\"`, `karaoke = false`\n\nIf a user only says \"I do not like it\", do not ask an open-ended question first.\nOffer a small set of directions:\n\n- cleaner\n- more eye-catching\n- bigger text\n- stronger background\n- change background color\n- switch to outline text\n- switch to shorts style\n- enable or disable word-by-word highlight\n- generate another preview set\n\n## Examples\n\nClean no-background captions:\n\n```ts\n{\n  preset: \"clean\",\n  overrides: {\n    background: { enabled: false }\n  }\n}\n```\n\nYellow capsule background:\n\n```ts\n{\n  preset: \"pill\",\n  overrides: {\n    background: {\n      enabled: true,\n      shape: \"pill\",\n      theme: \"yellow\"\n    }\n  }\n}\n```\n\nBlue small rounded background bar:\n\n```ts\n{\n  preset: \"boxed\",\n  overrides: {\n    background: {\n      enabled: true,\n      shape: \"rounded\",\n      theme: \"blue\"\n    }\n  }\n}\n```\n\nWhite text with green outline and no background:\n\n```ts\n{\n  preset: \"stroked\",\n  overrides: {\n    background: { enabled: false },\n    stroke: {\n      enabled: true,\n      theme: \"green\"\n    }\n  }\n}\n```\n\nSocial bold with karaoke:\n\n```ts\n{\n  preset: \"social-bold\",\n  karaoke: true\n}\n```\n\nVertical shorts with green highlight:\n\n```ts\n{\n  preset: \"shorts\",\n  karaoke: true,\n  overrides: {\n    wordHighlight: {\n      activeColor: \"#21D32E\",\n      backgroundColor: \"#21D32E\"\n    }\n  }\n}\n```\n\nFile v1.0.8:reference/caption-rules.md\n\n# Caption Rules and Data Shape\n\n## How build_captions.py chunks words into cues\n\nA new cue is closed when any of these conditions is met:\n\n- the word ends a sentence: `.`, `?`, `!`, or ellipsis\n- adding the next word would exceed the line budget: `--max-chars` x `--max-lines`\n- adding the next word would exceed `--max-dur` seconds on screen\n- there is a speech gap of at least `--gap` seconds before the next word\n\nEach cue keeps per-word timings, so the renderer can highlight the word currently\nbeing spoken. `lines[]` is the cue wrapped to `--max-chars` for display and for\nthe SRT file.\n\nUseful defaults:\n\n```text\n--max-chars 42 --max-lines 2 --max-dur 6 --gap 0.6\n```\n\nFor fast-cut vertical/social captions, try shorter cues:\n\n```text\n--max-chars 24 --max-dur 3\n```\n\n## captions.json schema\n\n```jsonc\n[\n  {\n    \"index\": 1,\n    \"start\": 1.0,\n    \"end\": 3.4,\n    \"text\": \"Hey, it's Thariq from the Claude Code team.\",\n    \"lines\": [\"Hey, it's Thariq from the Claude\", \"Code team.\"],\n    \"words\": [\n      { \"word\": \"Hey,\", \"start\": 1.0, \"end\": 1.2 }\n    ]\n  }\n]\n```\n\n`captions.srt` is the same content as portable SubRip. Use it as a sanity read\nor to hand to a player or another tool.\n\n## Renderer Contract\n\n- Treat `captions.json` as renderer-neutral cue data.\n- Preserve `start`, `end`, `text`, `lines[]`, and per-word timings when passing cues\n  to a renderer.\n- Karaoke is a true/false option, not a preset. Use `karaoke: true` for per-word\n  highlight and `karaoke: false` for plain blocks.\n- Keep style and compositing implementation outside this data-shaping contract.\n\n## Expressive Planning\n\nStandard is the default. A Standard canonical plan needs no `presentation` field,\nand the legacy top-level cue array remains Standard-compatible. Expressive must be\nexplicitly requested with `--presentation-mode expressive`; it is a presentation\nmode, not a preset.\n\n`build_captions.py` creates only the base cues and a draft planning shell. It does\nnot infer placement. The Agent reads the transcript,\navailable understanding artifacts, timeline, generated cues, and necessary real\nvisual evidence, then fills the complete plan once for the whole program.\n\nAgent layout rules:\n\n1. Default to `bottom-standard`.\n2. Keep ordinary explanatory sentences in a continuous bottom layout when possible.\n3. Use `center-emphasis` for short keywords, numbers, or conclusions when emphasis is justified.\n4. Do not mechanically alternate between bottom and center.\n5. Do not change position in the middle of a sentence or cue.\n6. Prefer merging adjacent ordinary cues into one stable layout beat.\n7. Avoid repeated consecutive `center-emphasis` beats.\n8. Every position change must have a semantic reason recorded in the beat rationale.\n9. When uncertain, fall back to `bottom-standard`.\n\nA layout beat covers one or more complete, contiguous cues. Beat IDs must be unique;\nbeats must follow time and cue order, must not overlap, and must not start or end\ninside a cue. A completed Expressive plan covers every cue exactly once and contains\none whole-program Agent rationale plus a non-empty rationale for every beat.\n\nThe supported word roles are `normal`, `keyword`, `number`, and `contrast`. A missing\n`semantic_role` is interpreted as `normal`. Semantic role is independent of the\nexisting karaoke `upcoming`, `active`, and `completed` playback states.\n\nThe renderer applies the approved layout beat for the complete cue: familiar lower\nplacement for `bottom-standard` or the central safe region for `center-emphasis`.\nIt never infers a variant or changes position inside a beat. A plan containing the\nremoved `top-statement` variant must be rejected and replanned; it must not be\nsilently remapped. In Expressive mode, non-`normal` semantic roles remain visibly\nemphasized for the full cue; Standard ignores semantic-role styling.\n\nExpressive supports two stable configurations. `semantic-only` uses Expressive\nwith Karaoke off. `semantic-plus-karaoke` keeps the same full-cue semantic emphasis\nwhile the existing upcoming, active, and completed Karaoke states progress. In\ncoexistence mode, semantic emphasis and the active Karaoke word both use\n`style.wordHighlight.activeColor` as their foreground color, even when\n`wordHighlight.mode` is `background`; the configured background remains an\nadditional effect. The effective scale is the larger of semantic scale and Karaoke\nactive scale, never their product.\n\nExpressive never renders an underline in either configuration. The compatible\n`contrast` semantic role may use color, scale, glow, or another non-underline\ntreatment.\n\nThe maintained semantic scales are `normal=1.0` and\n`keyword=number=contrast=1.22`. These values live in\n`caption-styles.json.expressiveTreatments`, not in a preset or renderer constant.\n\n## Hero-line phrase emphasis\n\n`hero_line` is one optional phrase-level capability on an Expressive cue. It is\nnot a presentation mode or preset. Canonical `hero` renders one contiguous phrase\nas a gold `#F4C542` line at `1.5` times the normal cue size. Legacy `strong` is\naccepted only as a compatibility input and renders identically at `1.5`; new\nAgent-authored plans use only `hero`. A cue may contain at most one hero line:\n\n```json\n{\n  \"hero_line\": {\n    \"level\": \"hero\",\n    \"word_indexes\": [2, 3],\n    \"rationale\": \"The single conclusion phrase carries the strongest hierarchy.\"\n  }\n}\n```\n\n`word_indexes` are 1-based, unique, ascending, contiguous, and within the cue's\n`words[]`. The rationale is required non-empty single-line text. Standard plans,\nlegacy cue arrays, cross-cue phrases, non-contiguous words, and a `hero_lines`\narray are invalid. Hero words may retain `semantic_role` for explanation, but the\nhero level owns their base scale; semantic and hero scales are never multiplied.\nWith Karaoke, the effective scale remains `max(hero scale, Karaoke active scale)`\nand the word remains gold in upcoming, active, and completed states.\n\nHero lines use `white-space: nowrap` and never shrink or wrap automatically. The\nreview builder must reject clipping or an alpha bbox that reaches the horizontal\nedge of its allowed region. Shorten the phrase, split the cue at a valid word gap,\nor remove the hero line; never silently shrink, wrap, or downgrade it.\n\n## Composite-aware placement\n\nComposite-aware captions consume an already approved B-roll speaker-inset\ncomposition without changing or reselecting it. Preset, presentation mode, and\nresolved placement remain separate:\n\n| Visual context | Standard | Expressive `bottom-standard` | Expressive `center-emphasis` |\n|---|---|---|---|\n| A-roll | `preset-bottom` | `preset-bottom` | `frame-center` |\n| `focused-panel` | `panel-center` | `panel-center` | `panel-center` |\n| `full-bleed-wash` | `preset-bottom` | `preset-bottom` | `frame-center` |\n| `corner-pip` | `preset-bottom` | `preset-bottom` | `frame-center` |\n\n`panel-center` and `panel-bottom` are resolved placements, not Expressive layout\nvariants. `panel-center` anchors to the center of the maintained focused panel rect\n`{x:0.04,y:0.08,width:0.92,height:0.40}`. `panel-bottom` is a boundary-only fallback:\nwhen an `unsplittable_word_boundary` touches a lower-center focused panel, center\nthe stable cue in the reserved strip between the structured speaker rect bottom\nand the frame's maintained 4% safe margin. `preset-bottom` preserves the selected\npreset's existing `paddingBottomRatio`; do not move `shorts` globally and do not\ncreate layout-specific combination presets.\n\nBefore Expressive planning, run `caption_spatial_context.py align`. For every\nB-roll start/end boundary strictly inside a cue, split at the nearest gap between\ncomplete words. Derived cues rebuild text, lines, program/source ranges and\nindexes, retain `original_cue_id`, and bind original/derived word signatures. If\nthe boundary falls inside a word, keep the complete word, record its midpoint side\nas provenance, add `unsplittable_word_boundary`, and force evidence on both sides.\nMidpoint ownership is not the collision model: record every visual context and\nstructured rectangle intersected by the cue. Never split a word or switch placement\ninside a cue. If either side is a lower-center focused panel, resolve the whole\nstable cue to `panel-bottom`; other unsplittable boundaries retain their existing\nplacement unless an actual sampled context reports a structured collision.\n\nAfter final cue IDs and Expressive layout beats exist, `build` emits\n`caption-spatial-context.json`; `attach` adds this optional plan binding:\n\n```json\n{\n  \"spatial_context\": {\n    \"policy\": \"composite-aware\",\n    \"path\": \"captions/caption-spatial-context.json\",\n    \"sha256\": \"<64 lowercase hex>\",\n    \"source_operation\": \"b-roll\",\n    \"source_revision\": 4\n  }\n}\n```\n\nThe context binds the active B-roll operation/revision/status, reviewed B-roll\nplan, speaker-inset analysis, Agent input, preview, clearance, and every normalized\ncomposite by SHA-256. It records `cue_alignment` and ordered, non-overlapping\n`placement_beats` that cover every aligned cue exactly once. `approved` with\n`check.status=pending` remains `approved`; captions must not report it as an\nupstream `verified` result. Any source revision, file hash, context, cue, placement,\nreview payload, or project-metadata change invalidates preview approval.\n\nComposite-aware review densely samples only placement beats with a composite\nbackground, plus B-roll visual boundaries, every hero-line cue, every\nunsplittable word boundary, the maintained base evidence,\nand a no-caption frame. B-roll samples use the hash-bound normalized composite at\n`program_s - program_start_s`, never the raw A-roll source. Alpha-bbox clearance\nmust keep `panel-center` and `panel-bottom` inside their `allowed_rect`, compare\nevery visible caption bbox with the actual sampled context's structured speaker\nrect, and reject hero-line clipping before approval. Boundary lookup uses integer\nframe identity and the exact rational timeline FPS. A frame-aligned rounded decimal\nboundary is snapped to its frame; otherwise the first active frame is the first\nframe at or after the half-open interval start.\n\nStandard HTML review keeps exactly `early`, `middle`, `late`, and `no-caption`.\nFor Expressive or composite-aware review, `samples` remains the exhaustive machine\nset and `review_samples` references a compact human set. Human categories are\n`bottom-standard`, `center-emphasis`, `preset-bottom`, `frame-center`,\n`panel-center`, and `hero-1.5x`; each present category appears at most once, one PNG\nmay carry several categories, absent categories are not invented, and there are at\nmost six unique representative PNGs. `no-caption`, dense spatial purposes, visual\nboundaries, and unsplittable-boundary samples remain machine-only. The two Karaoke\ncomparison PNGs are hash-bound separately and do not increase the representative\ncount. The receipt binds representative PNG hashes and the complete\n`captions-evidence.json` hash, so changing hidden machine evidence invalidates\napproval. Standard without spatial context remains exactly `early`, `middle`,\n`late`, and `no-caption`.\n\nFormal Expressive overlay generation preserves that distinction in the canonical\nplan. `review.representative_evidence` lists only the Human-visible representative\nPNGs. `review.evidence` remains the shared delivery compiler compatibility field and\ncontains one hash-bound `layout-beat` machine sample per planned layout beat plus the\nsingle `no-caption` sample. `review.machine_evidence_document` records the complete\ndocument path, SHA-256, and sample count. These compatibility entries are not extra\nhuman-review claims.\n\nRun `python scripts/build_captions.py --validate-plan <captions-plan.json>` after\nAgent planning and before preview generation. `generate_caption_project.mjs` repeats\nthe Expressive validation as a generation guard. Generate preview evidence only\nafter this validation passes.\n\nFile v1.0.8:reference/caption-style-themes.md\n\n# Caption Style Themes\n\nThemes are semantic options layered on top of official presets. Do not make each\ncolor combination an official preset.\n\n## Background Themes\n\nBackground color is configured with `background.theme`.\n\nSupported values:\n\n- `gray`\n- `yellow`\n- `blue`\n- `pink`\n- `green`\n\nAgents should map natural language to `preset` plus `background.theme`:\n\n```ts\n{\n  preset: \"pill\",\n  overrides: {\n    background: {\n      enabled: true,\n      shape: \"pill\",\n      theme: \"yellow\"\n    }\n  }\n}\n```\n\nUse `preset: \"pill\"` for a large rounded/capsule background and `preset: \"boxed\"`\nfor a small-radius rectangular background.\n\nExamples:\n\n- \"yellow rounded background\" -> `preset: \"pill\"`, `background.theme = \"yellow\"`\n- \"green background\" -> keep the current preset shape, set `background.theme = \"green\"`\n- \"blue small rounded background bar\" -> `preset: \"boxed\"`, `background.theme = \"blue\"`\n\nDo not create official presets such as `pill-yellow` or `boxed-green`. Names like\n`pill-yellow` are preview candidate ids only.\n\n## Stroke Themes\n\nText stroke is configured with `stroke.theme`.\n\nSupported values:\n\n- `black`\n- `yellow`\n- `blue`\n- `pink`\n- `green`\n\nAgents should map natural language to `preset: \"stroked\"` plus `stroke.theme`:\n\n```ts\n{\n  preset: \"stroked\",\n  overrides: {\n    background: { enabled: false },\n    stroke: {\n      enabled: true,\n      theme: \"black\"\n    }\n  }\n}\n```\n\nExamples:\n\n- \"no background, black outline\" -> `preset: \"stroked\"`, `stroke.theme = \"black\"`\n- \"yellow outline captions\" -> `preset: \"stroked\"`, `stroke.theme = \"yellow\"`\n- \"white text with green stroke\" -> `preset: \"stroked\"`, `background.enabled = false`, `stroke.theme = \"green\"`\n\nDo not create official presets such as `stroked-yellow` or `stroked-blue`. Names\nlike `stroked-yellow` are preview candidate ids only.\n\n## Shorts Highlight Colors\n\n`shorts` uses its own highlight colors. These are options, not official presets.\n\nSupported values:\n\n- `yellow`: `#F8F54F`\n- `green`: `#21D32E`\n- `orange`: `#F8BD6D`\n- `black`: `#000000`\n- `blue`: `#2563EB`\n- `pink`: `#DB2777`\n\nMap them by overriding `wordHighlight.activeColor` and `wordHighlight.backgroundColor`:\n\n```ts\n{\n  preset: \"shorts\",\n  karaoke: true,\n  overrides: {\n    wordHighlight: {\n      activeColor: \"#F8F54F\",\n      backgroundColor: \"#F8F54F\"\n    }\n  }\n}\n```\n\nDo not create official presets such as `shorts-yellow`. Names like\n`shorts-yellow` are preview candidate ids only.\n\nFile v1.0.8:skill-card.md\n\n## Description:\n\nAdds word-timed captions to an Open Recut program using its canonical transcript and timeline, with style review and a transparent overlay for delivery.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[whitetowerai](https://clawhub.ai/user/whitetowerai)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nVideo editors and developers use this skill to turn a validated, word-level transcript into timeline-aligned captions, review their appearance, and add a caption overlay to an Open Recut delivery.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: Local media processing can read and generate project files.\n\nMitigation: Use only with projects that expect local media processing and keep the working directory project-scoped.\n\nRisk: Caption layout or timing may not match the intended delivery.\n\nMitigation: Review the generated HTML evidence and source-backed preview before approval.\n\nRisk: On-demand HyperFrames resolution can introduce an unpinned dependency.\n\nMitigation: Consider pinning HyperFrames rather than relying on ambient npx resolution.\n\n## Reference(s):\n\n- [ClawHub skill release](https://clawhub.ai/whitetowerai/skills/video-add-captions)\n- [Caption rules and data shape](reference/caption-rules.md)\n- [Caption style themes](reference/caption-style-themes.md)\n- [Caption feedback mapping](reference/caption-feedback-mapping.md)\n\n## Skill Output:\n\n**Output Type(s):** [Files, Markdown, Guidance, Shell commands]\n\n**Output Format:** [Caption plan and review evidence in JSON, subtitles in SRT, review in HTML and Markdown, and a transparent PNG overlay sequence]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Caption timing follows the canonical program timeline; review and approval precede delivery registration.]\n\n## Skill Version(s):\n\n1.0.8 (source: ClawHub release metadata)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nFile v1.0.8:assets/style-previews/preview-manifest.json\n\n{\n    \"schemaVersion\":  1,\n    \"generatedAt\":  \"2026-07-16T03:50:50.1057047Z\",\n    \"generator\":  \"scripts/render_style_previews.ps1\",\n    \"total\":  25,\n    \"groups\":  [\n                   {\n                       \"id\":  \"core\",\n                       \"items\":  [\n                                     {\n                                         \"id\":  \"clean\",\n                                         \"label\":  \"Clean\",\n                                         \"preset\":  \"clean\",\n                                         \"themeType\":  \"none\",\n                                         \"theme\":  null,\n                                         \"karaoke\":  false,\n                                         \"orientation\":  \"landscape\",\n                                         \"aspectRatio\":  \"16:9\",\n                                         \"width\":  1280,\n                                         \"height\":  720,\n                                         \"image\":  \"preview-clean.png\",\n                                         \"props\":  \"props-clean.json\"\n                                     },\n                                     {\n                                         \"id\":  \"minimal\",\n                                         \"label\":  \"Minimal\",\n                                         \"preset\":  \"minimal\",\n                                         \"themeType\":  \"none\",\n                                         \"theme\":  null,\n                                         \"karaoke\":  false,\n                                         \"orientation\":  \"landscape\",\n                                         \"aspectRatio\":  \"16:9\",\n                                         \"width\":  1280,\n                                         \"height\":  720,\n                                         \"image\":  \"preview-minimal.png\",\n                                         \"props\":  \"props-minimal.json\"\n                                     },\n                                     {\n                                         \"id\":  \"social-bold\",\n                                         \"label\":  \"Social Bold\",\n                                         \"preset\":  \"social-bold\",\n                                         \"themeType\":  \"none\",\n                                         \"theme\":  null,\n                                         \"karaoke\":  false,\n                                         \"orientation\":  \"landscape\",\n                                         \"aspectRatio\":  \"16:9\",\n                                         \"width\":  1280,\n                                         \"height\":  720,\n                                         \"image\":  \"preview-social-bold.png\",\n                                         \"props\":  \"props-social-bold.json\"\n                                     },\n                                     {\n                                         \"id\":  \"social-bold-karaoke\",\n                                         \"label\":  \"Social Bold Karaoke\",\n                                         \"preset\":  \"social-bold\",\n                                         \"themeType\":  \"none\",\n                                         \"theme\":  null,\n                                         \"karaoke\":  true,\n                                         \"orientation\":  \"landscape\",\n                                         \"aspectRatio\":  \"16:9\",\n                                         \"width\":  1280,\n                                         \"height\":  720,\n                                         \"image\":  \"preview-social-bold-karaoke.png\",\n                                         \"props\":  \"props-social-bold-karaoke.json\"\n                                     }\n                                 ]\n                   },\n                   {\n                       \"id\":  \"pill\",\n                       \"items\":  [\n                                     {\n                                         \"id\":  \"pill-gray\",\n                                         \"label\":  \"Pill Gray\",\n                                         \"preset\":  \"pill\",\n                                         \"themeType\":  \"background\",\n                                         \"theme\":  \"gray\",\n                                         \"karaoke\":  false,\n                                         \"orientation\":  \"landscape\",\n                                         \"aspectRatio\":  \"16:9\",\n                                         \"width\":  1280,\n                                         \"height\":  720,\n                                         \"image\":  \"preview-pill-gray.png\",\n                                         \"props\":  \"props-pill-gray.json\"\n                                     },\n                                     {\n                                         \"id\":  \"pill-yellow\",\n                                         \"label\":  \"Pill Yellow\",\n                                         \"preset\":  \"pill\",\n                                         \"themeType\":  \"background\",\n                                         \"theme\":  \"yellow\",\n                                         \"karaoke\":  false,\n                                         \"orientation\":  \"landscape\",\n                                         \"aspectRatio\":  \"16:9\",\n                                         \"width\":  1280,\n                                         \"height\":  720,\n                                         \"image\":  \"preview-pill-yellow.png\",\n                                         \"props\":  \"props-pill-yellow.json\"\n                                     },\n                                     {\n                                         \"id\":  \"pill-blue\",\n                                         \"label\":  \"Pill Blue\",\n                                         \"preset\":  \"pill\",\n                                         \"themeType\":  \"background\",\n                                         \"theme\":  \"blue\",\n                                         \"karaoke\":  false,\n                                         \"orientation\":  \"landscape\",\n                                         \"aspectRatio\":  \"16:9\",\n                                         \"width\":  1280,\n                                         \"height\":  720,\n                                         \"image\":  \"preview-pill-blue.png\",\n                                         \"props\":  \"props-pill-blue.json\"\n                                     },\n                                     {\n                                         \"id\":  \"pill-pink\",\n                                         \"label\":  \"Pill Pink\",\n                                         \"preset\":  \"pill\",\n                                         \"themeType\":  \"background\",\n                                         \"theme\":  \"pink\",\n                                         \"karaoke\":  false,\n                                         \"orientation\":  \"landscape\",\n                                         \"aspectRatio\":  \"16:9\",\n                                         \"width\":  1280,\n                                         \"height\":  720,\n                                         \"image\":  \"preview-pill-pink.png\",\n                                         \"props\":  \"props-pill-pink.json\"\n                                     },\n                                     {\n                                         \"id\":  \"pill-green\",\n                                         \"label\":  \"Pill Green\",\n                                         \"preset\":  \"pill\",\n                                         \"themeType\":  \"background\",\n                                         \"theme\":  \"green\",\n                                         \"karaoke\":  false,\n                                         \"orientation\":  \"landscape\",\n                                         \"aspectRatio\":  \"16:9\",\n                                         \"width\":  1280,\n                                         \"height\":  720,\n                                         \"image\":  \"preview-pill-green.png\",\n                                         \"props\":  \"props-pill-green.json\"\n                                     }\n                                 ]\n                   },\n                   {\n                       \"id\":  \"boxed\",\n                       \"items\":  [\n                                     {\n                                         \"id\":  \"boxed-gray\",\n                                         \"label\":  \"Boxed Gray\",\n                                         \"preset\":  \"boxed\",\n                                         \"themeType\":  \"background\",\n                                         \"theme\":  \"gray\",\n                                         \"karaoke\":  false,\n                                         \"orientation\":  \"landscape\",\n                                         \"aspectRatio\":  \"16:9\",\n                                         \"width\":  1280,\n                                         \"height\":  720,\n                                         \"image\":  \"preview-boxed-gray.png\",\n                                         \"props\":  \"props-boxed-gray.json\"\n                                     },\n                                     {\n                                         \"id\":  \"boxed-yellow\",\n                                         \"label\":  \"Boxed Yellow\",\n                                         \"preset\":  \"boxed\",\n                                         \"themeType\":  \"background\",\n                                         \"theme\":  \"yellow\",\n                                         \"karaoke\":  false,\n                                         \"orientation\":  \"landscape\",\n                                         \"aspectRatio\":  \"16:9\",\n                                         \"width\":  1280,\n                                         \"height\":  720,\n                                         \"image\":  \"preview-boxed-yellow.png\",\n                                         \"props\":  \"props-boxed-yellow.json\"\n                                     },\n                                     {\n                                         \"id\":  \"boxed-blue\",\n                                         \"label\":  \"Boxed Blue\",\n                                         \"preset\":  \"boxed\",\n                                         \"themeType\":  \"background\",\n                                         \"theme\":  \"blue\",\n                                         \"karaoke\":  false,\n                                         \"orientation\":  \"landscape\",\n                                         \"aspectRatio\":  \"16:9\",\n                                         \"width\":  1280,\n                                         \"height\":  720,\n                                         \"image\":  \"preview-boxed-blue.png\",\n                                         \"props\":  \"props-boxed-blue.json\"\n                                     },\n                                     {\n                                         \"id\":  \"boxed-pink\",\n                                         \"label\":  \"Boxed Pink\",\n                                         \"preset\":  \"boxed\",\n                                         \"themeType\":  \"background\",\n                                         \"theme\":  \"pink\",\n                                         \"karaoke\":  false,\n                                         \"orientation\":  \"landscape\",\n                                         \"aspectRatio\":  \"16:9\",\n                                         \"width\":  1280,\n                                         \"height\":  720,\n                                         \"image\":  \"preview-boxed-pink.png\",\n                                         \"props\":  \"props-boxed-pink.json\"\n                                     },\n                                     {\n                                         \"id\":  \"boxed-green\",\n                                         \"label\":  \"Boxed Green\",\n                                         \"preset\":  \"boxed\",\n                                         \"themeType\":  \"background\",\n                                         \"theme\":  \"green\",\n                                         \"karaoke\":  false,\n                                         \"orientation\":  \"landscape\",\n                                         \"aspectRatio\":  \"16:9\",\n                                         \"width\":  1280,\n                                         \"height\":  720,\n                                         \"image\":  \"preview-boxed-green.png\",\n                                         \"props\":  \"props-boxed-green.json\"\n                                     }\n                                 ]\n                   },\n                   {\n                       \"id\":  \"stroked\",\n                       \"items\":  [\n                                     {\n                                         \"id\":  \"stroked-black\",\n                                         \"label\":  \"Stroked Black\",\n                                         \"preset\":  \"stroked\",\n                                         \"themeType\":  \"stroke\",\n                                         \"theme\":  \"black\",\n                                         \"karaoke\":  false,\n                                         \"orientation\":  \"landscape\",\n                                         \"aspectRatio\":  \"16:9\",\n                                         \"width\":  1280,\n                                         \"height\":  720,\n                                         \"image\":  \"preview-stroked-black.png\",\n                                         \"props\":  \"props-stroked-black.json\"\n                                     },\n                                     {\n                                         \"id\":  \"stroked-yellow\",\n                                         \"label\":  \"Stroked Yellow\",\n                                         \"preset\":  \"stroked\",\n                                         \"themeType\":  \"stroke\",\n                                         \"theme\":  \"yellow\",\n                                         \"karaoke\":  false,\n                                         \"orientation\":  \"landscape\",\n                                         \"aspectRatio\":  \"16:9\",\n                                         \"width\":  1280,\n                                         \"height\":  720,\n                                         \"image\":  \"preview-stroked-yellow.png\",\n                                         \"props\":  \"props-stroked-yellow.json\"\n                                     },\n                                     {\n                                         \"id\":  \"stroked-blue\",\n                                         \"label\":  \"Stroked Blue\",\n                                         \"preset\":  \"stroked\",\n                                         \"themeType\":  \"stroke\",\n                                         \"theme\":  \"blue\",\n                                         \"karaoke\":  false,\n                                         \"orientation\":  \"landscape\",\n                                         \"aspectRatio\":  \"16:9\",\n                                         \"width\":  1280,\n                                         \"height\":  720,\n                                         \"image\":  \"preview-stroked-blue.png\",\n                                         \"props\":  \"props-stroked-blue.json\"\n                                     },\n                                     {\n                                         \"id\":  \"stroked-pink\",\n                                         \"label\":  \"Stroked Pink\",\n                                         \"preset\":  \"stroked\",\n                                         \"themeType\":  \"stroke\",\n                                         \"theme\":  \"pink\",\n                                         \"karaoke\":  false,\n                                         \"orientation\":  \"landscape\",\n                                         \"aspectRatio\":  \"16:9\",\n                                         \"width\":  1280,\n                                         \"height\":  720,\n                                         \"image\":  \"preview-stroked-pink.png\",\n                                         \"props\":  \"props-stroked-pink.json\"\n                                     },\n                                     {\n                                         \"id\":  \"stroked-green\",\n                                         \"label\":  \"Stroked Green\",\n                                         \"preset\":  \"stroked\",\n                                         \"themeType\":  \"stroke\",\n                                         \"theme\":  \"green\",\n                                         \"karaoke\":  false,\n                                         \"orientation\":  \"landscape\",\n                                         \"aspectRatio\":  \"16:9\",\n                                         \"width\":  1280,\n                                         \"height\":  720,\n                                         \"image\":  \"preview-stroked-green.png\",\n                                         \"props\":  \"props-stroked-green.json\"\n                                     }\n                                 ]\n                   },\n                   {\n                       \"id\":  \"shorts\",\n                       \"items\":  [\n                                     {\n                                         \"id\":  \"shorts-yellow\",\n                                         \"label\":  \"Shorts Yellow\",\n                                         \"preset\":  \"shorts\",\n                                         \"themeType\":  \"highlight\",\n                                         \"theme\":  \"yellow\",\n                                         \"karaoke\":  true,\n                                         \"orientation\":  \"shorts\",\n                                         \"aspectRatio\":  \"9:16\",\n                                         \"width\":  1080,\n                                         \"height\":  1920,\n                                         \"image\":  \"preview-shorts-yellow.png\",\n                                         \"props\":  \"props-shorts-yellow.json\"\n                                     },\n                                     {\n                                         \"id\":  \"shorts-green\",\n                                         \"label\":  \"Shorts Green\",\n                                         \"preset\":  \"shorts\",\n                                         \"themeType\":  \"highlight\",\n                                         \"theme\":  \"green\",\n                                         \"karaoke\":  true,\n                                         \"orientation\":  \"shorts\",\n                                         \"aspectRatio\":  \"9:16\",\n                                         \"width\":  1080,\n                                         \"height\":  1920,\n                                         \"image\":  \"preview-shorts-green.png\",\n                                         \"props\":  \"props-shorts-green.json\"\n                                     },\n                                     {\n                                         \"id\":  \"shorts-orange\",\n                                         \"label\":  \"Shorts Orange\",\n                                         \"preset\":  \"shorts\",\n                                         \"themeType\":  \"highlight\",\n                                         \"theme\":  \"orange\",\n                                         \"karaoke\":  true,\n                                         \"orientation\":  \"shorts\",\n                                         \"aspectRatio\":  \"9:16\",\n                                         \"width\":  1080,\n                                         \"height\":  1920,\n                                         \"image\":  \"preview-shorts-orange.png\",\n                                         \"props\":  \"props-shorts-orange.json\"\n                                     },\n                                     {\n                                         \"id\":  \"shorts-purple\",\n                                         \"label\":  \"Shorts Purple\",\n                                         \"preset\":  \"shorts\",\n                                         \"themeType\":  \"highlight\",\n                                         \"theme\":  \"purple\",\n                                         \"karaoke\":  true,\n                                         \"orientation\":  \"shorts\",\n                                         \"aspectRatio\":  \"9:16\",\n                                         \"width\":  1080,\n                                         \"height\":  1920,\n                                         \"image\":  \"preview-shorts-purple.png\",\n                                         \"props\":  \"props-shorts-purple.json\"\n                                     },\n                                     {\n                                         \"id\":  \"shorts-blue\",\n                                         \"label\":  \"Shorts Blue\",\n                                         \"preset\":  \"shorts\",\n                                         \"themeType\":  \"highlight\",\n                                         \"theme\":  \"blue\",\n                                         \"karaoke\":  true,\n                                         \"orientation\":  \"shorts\",\n                                         \"aspectRatio\":  \"9:16\",\n                                         \"width\":  1080,\n                                         \"height\":  1920,\n                                         \"image\":  \"preview-shorts-blue.png\",\n                                         \"props\":  \"props-shorts-blue.json\"\n                                     },\n                                     {\n                                         \"id\":  \"shorts-pink\",\n                                         \"label\":  \"Shorts Pink\",\n                                         \"preset\":  \"shorts\",\n                                         \"themeType\":  \"highlight\",\n                                         \"theme\":  \"pink\",\n                                         \"karaoke\":  true,\n                                         \"orientation\":  \"shorts\",\n                                         \"aspectRatio\":  \"9:16\",\n                                         \"width\":  1080,\n                                         \"height\":  1920,\n                                         \"image\":  \"preview-shorts-pink.png\",\n                                         \"props\":  \"props-shorts-pink.json\"\n                                     }\n                                 ]\n                   }\n               ]\n}\n\nFile v1.0.8:assets/style-previews/props-boxed-blue.json\n\n{\n    \"id\":  \"boxed-blue\",\n    \"preset\":  \"boxed\",\n    \"backgroundTheme\":  \"blue\",\n    \"strokeTheme\":  null,\n    \"highlightTheme\":  null,\n    \"karaoke\":  false,\n    \"mode\":  \"preview\",\n    \"width\":  1280,\n    \"height\":  720,\n    \"fps\":  30,\n    \"snapshotTime\":  1.55,\n    \"sourceVideo\":  \"TEST/videos/video-add-captions-work/phase-4-style-previews/fixtures/preview-landscape.mp4\",\n    \"captionsFile\":  \"TEST/videos/video-add-captions-work/phase-4-style-previews/fixtures/preview-captions.json\",\n    \"resolvedStyle\":  {\n                          \"preset\":  \"boxed\",\n                          \"font\":  {\n                                       \"family\":  \"system-ui, sans-serif\",\n                                       \"sizeRatio\":  0.0416,\n                                       \"weight\":  800,\n                                       \"color\":  \"#FFFFFF\",\n                                       \"lineHeight\":  1.298,\n                                       \"letterSpacing\":  0\n                                   },\n                          \"layout\":  {\n                                         \"anchor\":  \"bottom\",\n                                         \"align\":  \"center\",\n                                         \"maxWidth\":  0.9,\n                                         \"paddingBottomRatio\":  0.07\n                                     },\n                          \"background\":  {\n                                             \"enabled\":  true,\n                                             \"theme\":  \"blue\",\n                                             \"shape\":  \"rounded\",\n                                             \"color\":  \"#2563EB\",\n                                             \"opacity\":  0.72,\n                                             \"radiusRatio\":  0.008,\n                                             \"paddingXRatio\":  0.032,\n                                             \"paddingYRatio\":  0.0112\n                                         },\n                          \"effects\":  {\n                                          \"shadow\":  {\n                                                         \"strength\":  \"none\",\n                                                         \"color\":  \"#000000\",\n                                                         \"opacity\":  0,\n                                                         \"offsetYRatio\":  0,\n                                                         \"blurRatio\":  0\n                                                     },\n                                          \"stroke\":  {\n                                                         \"strength\":  \"none\",\n                                                         \"color\":  \"#000000\",\n                                                         \"widthRatio\":  0\n                                                     }\n                                      },\n                          \"stroke\":  {\n                                         \"enabled\":  false,\n                                         \"theme\":  \"black\",\n                                         \"color\":  \"#000000\",\n                                         \"opacity\":  0.85,\n                                         \"widthRatio\":  0\n                                     },\n                          \"wordHighlight\":  {\n                                                \"enabled\":  true,\n                                                \"mode\":  \"textColor\",\n                                                \"activeColor\":  \"#BFDBFE\",\n                                                \"activeScale\":  1.12,\n                                                \"upcomingOpacity\":  0.68,\n                                                \"backgroundColor\":  \"#8BE9FD\",\n                                                \"backgroundOpacity\":  0.22,\n                                                \"backgroundRadiusRatio\":  0.008\n                                            },\n                          \"animation\":  {\n                                            \"type\":  \"fade\",\n                                            \"popInFrames\":  5,\n                                            \"popOutFrames\":  4,\n                                            \"translateYPx\":  0\n                                        }\n                      }\n}\n\nFile v1.0.8:assets/style-previews/props-boxed-gray.json\n\n{\n    \"id\":  \"boxed-gray\",\n    \"preset\":  \"boxed\",\n    \"backgroundTheme\":  \"gray\",\n    \"strokeTheme\":  null,\n    \"highlightTheme\":  null,\n    \"karaoke\":  false,\n    \"mode\":  \"preview\",\n    \"width\":  1280,\n    \"height\":  720,\n    \"fps\":  30,\n    \"snapshotTime\":  1.55,\n    \"sourceVideo\":  \"TEST/videos/video-add-captions-work/phase-4-style-previews/fixtures/preview-landscape.mp4\",\n    \"captionsFile\":  \"TEST/videos/video-add-captions-work/phase-4-style-previews/fixtures/preview-captions.json\",\n    \"resolvedStyle\":  {\n                          \"preset\":  \"boxed\",\n                          \"font\":  {\n                                       \"family\":  \"system-ui, sans-serif\",\n                                       \"sizeRatio\":  0.0416,\n                                       \"weight\":  800,\n                                       \"color\":  \"#FFFFFF\",\n                                       \"lineHeight\":  1.298,\n                                       \"letterSpacing\":  0\n                                   },\n                          \"layout\":  {\n                                         \"anchor\":  \"bottom\",\n                                         \"align\":  \"center\",\n                                         \"maxWidth\":  0.9,\n                                         \"paddingBottomRatio\":  0.07\n                                     },\n                          \"background\":  {\n                                             \"enabled\":  true,\n                                             \"theme\":  \"gray\",\n                                             \"shape\":  \"rounded\",\n                                             \"color\":  \"#111827\",\n                                             \"opacity\":  0.62,\n                                             \"radiusRatio\":  0.008,\n                                             \"paddingXRatio\":  0.032,\n                                             \"paddingYRatio\":  0.0112\n                                         },\n                          \"effects\":  {\n                                          \"shadow\":  {\n                                                         \"strength\":  \"none\",\n                                                         \"color\":  \"#000000\",\n                                                         \"opacity\":  0,\n                                                         \"offsetYRatio\":  0,\n                                                         \"blurRatio\":  0\n                                                     },\n                                          \"stroke\":  {\n                                                         \"strength\":  \"none\",\n                                                         \"color\":  \"#000000\",\n                                                         \"widthRatio\":  0\n                                                     }\n                                      },\n                          \"stroke\":  {\n                                         \"enabled\":  false,\n                                         \"theme\":  \"black\",\n                                         \"color\":  \"#000000\",\n                                         \"opacity\":  0.85,\n                                         \"widthRatio\":  0\n                                     },\n                          \"wordHighlight\":  {\n                                                \"enabled\":  true,\n                                                \"mode\":  \"textColor\",\n                                                \"activeColor\":  \"#FFD43B\",\n                                                \"activeScale\":  1.12,\n                                                \"upcomingOpacity\":  0.68,\n                                                \"backgroundColor\":  \"#8BE9FD\",\n                                                \"backgroundOpacity\":  0.22,\n                                                \"backgroundRadiusRatio\":  0.008\n                                            },\n                          \"animation\":  {\n                                            \"type\":  \"fade\",\n                                            \"popInFrames\":  5,\n                                            \"popOutFrames\":  4,\n                                            \"translateYPx\":  0\n                                        }\n                      }\n}\n\nFile v1.0.8:assets/style-previews/props-boxed-green.json\n\n{\n    \"id\":  \"boxed-green\",\n    \"preset\":  \"boxed\",\n    \"backgroundTheme\":  \"green\",\n    \"strokeTheme\":  null,\n    \"highlightTheme\":  null,\n    \"karaoke\":  false,\n    \"mode\":  \"preview\",\n    \"width\":  1280,\n    \"height\":  720,\n    \"fps\":  30,\n    \"snapshotTime\":  1.55,\n    \"sourceVideo\":  \"TEST/videos/video-add-captions-work/phase-4-style-previews/fixtures/preview-landscape.mp4\",\n    \"captionsFile\":  \"TEST/videos/video-add-captions-work/phase-4-style-previews/fixtures/preview-captions.json\",\n    \"resolvedStyle\":  {\n                          \"preset\":  \"boxed\",\n                          \"font\":  {\n                                       \"family\":  \"system-ui, sans-serif\",\n                                       \"sizeRatio\":  0.0416,\n                                       \"weight\":  800,\n                                       \"color\":  \"#FFFFFF\",\n                                       \"lineHeight\":  1.298,\n                                       \"letterSpacing\":  0\n                                   },\n                          \"layout\":  {\n                                         \"anchor\":  \"bottom\",\n                                         \"align\":  \"center\",\n                                         \"maxWidth\":  0.9,\n                                         \"paddingBottomRatio\":  0.07\n                                     },\n                          \"background\":  {\n                                             \"enabled\":  true,\n                                             \"theme\":  \"green\",\n                                             \"shape\":  \"rounded\",\n                                             \"color\":  \"#16A34A\",\n                                             \"opacity\":  0.72,\n                                             \"radiusRatio\":  0.008,\n                                             \"paddingXRatio\":  0.032,\n                                             \"paddingYRatio\":  0.0112\n                                         },\n                          \"effects\":  {\n                                          \"shadow\":  {\n                                                         \"strength\":  \"none\",\n                                                         \"color\":  \"#000000\",\n                                                         \"opacity\":  0,\n                                                         \"offsetYRatio\":  0,\n                                                         \"blurRatio\":  0\n                                                     },\n                                          \"stroke\":  {\n                                                         \"strength\":  \"none\",\n                                                         \"color\":  \"#000000\",\n                                                         \"widthRatio\":  0\n                                                     }\n                                      },\n                          \"stroke\":  {\n                                         \"enabled\":  false,\n                                         \"theme\":  \"black\",\n                                         \"color\":  \"#000000\",\n                                         \"opacity\":  0.85,\n                                         \"widthRatio\":  0\n                                     },\n                          \"wordHighlight\":  {\n                                                \"enabled\":  true,\n                                                \"mode\":  \"textColor\",\n                                                \"activeColor\":  \"#BBF7D0\",\n                                                \"activeScale\":  1.12,\n                                                \"upcomingOpacity\":  0.68,\n                                                \"backgroundColor\":  \"#8BE9FD\",\n                                                \"backgroundOpacity\":  0.22,\n                                                \"backgroundRadiusRatio\":  0.008\n                                            },\n                          \"animation\":  {\n                                            \"type\":  \"fade\",\n                                            \"popInFrames\":  5,\n                                            \"popOutFrames\":  4,\n                                            \"translateYPx\":  0\n                                        }\n                      }\n}\n\nFile v1.0.8:assets/style-previews/props-boxed-pink.json\n\n{\n    \"id\":  \"boxed-pink\",\n    \"preset\":  \"boxed\",\n    \"backgroundTheme\":  \"pink\",\n    \"strokeTheme\":  null,\n    \"highlightTheme\":  null,\n    \"karaoke\":  false,\n    \"mode\":  \"preview\",\n    \"width\":  1280,\n    \"height\":  720,\n    \"fps\":  30,\n    \"snapshotTime\":  1.55,\n    \"sourceVideo\":  \"TEST/videos/video-add-captions-work/phase-4-style-previews/fixtures/preview-landscape.mp4\",\n    \"captionsFile\":  \"TEST/videos/video-add-captions-work/phase-4-style-previews/fixtures/preview-captions.json\",\n    \"resolvedStyle\":  {\n                          \"preset\":  \"boxed\",\n                          \"font\":  {\n                                       \"family\":  \"system-ui, sans-serif\",\n                                       \"sizeRatio\":  0.0416,\n                                       \"weight\":  800,\n                                       \"color\":  \"#FFFFFF\",\n                                       \"lineHeight\":  1.298,\n                                       \"letterSpacing\":  0\n                                   },\n                          \"layout\":  {\n                                         \"anchor\":  \"bottom\",\n                                         \"align\":  \"center\",\n                                         \"maxWidth\":  0.9,\n                                         \"paddingBottomRatio\":  0.07\n                                     },\n                          \"background\":  {\n                                             \"enabled\":  true,\n                                             \"theme\":  \"pink\",\n                                             \"shape\":  \"rounded\",\n                                             \"color\":  \"#DB2777\",\n                                             \"opacity\":  0.72,\n                                             \"radiusRatio\":  0.008,\n                                             \"paddingXRatio\":  0.032,\n                                             \"paddingYRatio\":  0.0112\n                                         },\n                          \"effects\":  {\n                                          \"shadow\":  {\n                                                         \"strength\":  \"none\",\n                                                         \"color\":  \"#000000\",\n                                                         \"opacity\":  0,\n                                                         \"offsetYRatio\":  0,\n                                                         \"blurRatio\":  0\n                                                     },\n                                          \"stroke\":  {\n                                                         \"strength\":  \"none\",\n                                                         \"color\":  \"#000000\",\n                                                         \"widthRatio\":  0\n                                                     }\n                                      },\n                          \"stroke\":  {\n                                         \"enabled\":  false,\n                                         \"theme\":  \"black\",\n                                         \"color\":  \"#000000\",\n                                         \"opacity\":  0.85,\n                                         \"widthRatio\":  0\n                                     },\n                          \"wordHighlight\":  {\n                                                \"enabled\":  true,\n                                                \"mode\":  \"textColor\",\n                                                \"activeColor\":  \"#FBCFE8\",\n                                                \"activeScale\":  1.12,\n                                                \"upcomingOpacity\":  0.68,\n                                                \"backgroundColor\":  \"#8BE9FD\",\n                                                \"backgroundOpacity\":  0.22,\n                                                \"backgroundRadiusRatio\":  0.008\n                                            },\n                          \"animation\":  {\n                                            \"type\":  \"fade\",\n                                            \"popInFrames\":  5,\n                                            \"popOutFrames\":  4,\n                                            \"translateYPx\":  0\n                                        }\n                      }\n}\n\nArchive v1.0.7: 53 files, 202880 bytes\n\nFiles: assets/captions-review.html (19535b), assets/style-previews/index.html (50456b), assets/style-previews/preview-manifest.json (22431b), assets/style-previews/props-boxed-blue.json (4292b), assets/style-previews/props-boxed-gray.json (4292b), assets/style-previews/props-boxed-green.json (4295b), assets/style-previews/props-boxed-pink.json (4292b), assets/style-previews/props-boxed-yellow.json (4298b), assets/style-previews/props-clean.json (4293b), assets/style-previews/props-minimal.json (4279b), assets/style-previews/props-pill-blue.json (4296b), assets/style-previews/props-pill-gray.json (4296b), assets/style-previews/props-pill-green.json (4299b), assets/style-previews/props-pill-pink.json (4296b), assets/style-previews/props-pill-yellow.json (4302b), assets/style-previews/props-shorts-blue.json (4319b), assets/style-previews/props-shorts-green.json (4321b), assets/style-previews/props-shorts-orange.json (4323b), assets/style-previews/props-shorts-pink.json (4319b), assets/style-previews/props-shorts-purple.json (4323b), assets/style-previews/props-shorts-yellow.json (4323b), assets/style-previews/props-social-bold-karaoke.json (4324b), assets/style-previews/props-social-bold.json (4317b), assets/style-previews/props-stroked-black.json (4299b), assets/style-previews/props-stroked-blue.json (4297b), assets/style-previews/props-stroked-green.json (4300b), assets/style-previews/props-stroked-pink.json (4297b), assets/style-previews/props-stroked-yellow.json (4303b), examples/index.html (4642b), public/gsap.min.js (72212b), reference/caption-feedback-mapping.md (6174b), reference/caption-rules.md (11879b), reference/caption-style-themes.md (2437b), scripts/build_caption_review.py (46537b), scripts/build_captions.py (23815b), scripts/build_style_preview_gallery.mjs (18396b), scripts/caption_interaction_state.mjs (34656b), scripts/caption_interaction.mjs (38165b), scripts/caption_spatial_context.py (36736b), scripts/caption_style_config.mjs (3840b), scripts/caption-styles.json (14381b), scripts/check_caption_interaction.mjs (72875b), scripts/check_caption_review.py (42733b), scripts/check_caption_spatial_context.py (23035b), scripts/check_caption_style_config.mjs (4550b), scripts/check_project_protocol.py (43931b), scripts/check_structure.ps1 (13020b), scripts/composite_caption_overlay.ps1 (2244b), scripts/generate_caption_project.mjs (48255b), scripts/render_style_previews.ps1 (18045b), skill-card.md (2615b), SKILL.md (31302b), _meta.json (137b)\n\nFile v1.0.7:SKILL.md\n\n---\nname: video-add-captions\ndescription: >\n  Add word-timed captions to an Open Recut program. Use this skill to map the\n  canonical transcript through timeline.json, review a maintained style on\n  source-backed pixels, render a local transparent HyperFrames PNG sequence,\n  and register it as an overlay contribution for the shared delivery render.\n---\n\n# Video Add Captions\n\n## Dependencies\n\n`/video-understand` is a prerequisite. Run it first so captions use the validated\nword-level transcript and canonical timeline.\nBefore starting, verify that it is installed. If it is not, warn the user that\nthis prerequisite is missing and stop before processing media.\n\nRequire `ffmpeg`/`ffprobe` on PATH, Python with `Pillow`, and Node.js >= 22 (for the `.mjs` scripts and `npx hyperframes`, fetched on demand). `hyperframes render`/`snapshot` drives a headless Chrome — it manages its own `chrome-headless-shell`, and falls back to a system Chrome (set `CHROME` to override) when the cached one is unusable. Check these before processing media.\n\n## Scope\n\nThis skill owns caption grouping, style selection, review, and the transparent\ncaption track. It does not transcribe, cut, retime, grade, reframe, or choose the\ndelivery audio policy.\n\nRun captions before content cards and graphic motion whenever either is selected.\nThe approved caption layout establishes a reserved subtitle region that both later\noperations must keep clear. This relative order also applies when only one pair is active.\n\nUse `video-understand` first. If a cut exists, caption the active program\ntimeline; do not treat source transcript seconds as program seconds.\n\n## Protocol Inputs\n\nRequired:\n\n- `work/project.json`\n- `work/understand/transcript.json`, with word-level source timestamps\n- `work/understand/media.json`\n- `work/timeline.json`\n- the source video named by `work/project.json`\n\nThe timeline is the only source-to-program mapping. All ranges are half-open\n`[start_s, end_s)`. `scripts/build_captions.py` uses the shared\n`projectlib.map_transcript_to_timeline`; words in dropped source ranges disappear,\nretained words move to program time, and cues always break at clip boundaries.\n\n## Durable Outputs\n\n```text\nwork/captions/\n|-- captions-plan.json\n|-- caption-spatial-context.json (only for eligible B-roll composites)\n`-- caption-interaction.json\nwork/cache/captions/\n|-- preview-project/\n|-- preview-snapshots/\n|-- overlay-project/\n`-- overlay-frames/frame_000001.png ...\nreview/05-captions/\n|-- captions.srt\n|-- captions-style-review-<UUID>.html\n|-- captions-style-review.html\n|-- captions-review.html\n|-- preview-early.png\n|-- preview-middle.png\n|-- preview-late.png\n|-- preview-no-caption.png\n|-- captions-evidence.json\n`-- captions-summary.md\n```\n\nGenerated HTML, copied runtime files, extracted source frames, and overlay frames\nare cache. The plan, optional spatial context, SRT, decision receipt, and review\nevidence are durable.\n\n## Caption Plan\n\n`work/captions/captions-plan.json` is the canonical caption plan, targets `overlay`, and uses\nprogram time. It records:\n\n- `timeline_id`, `program_duration_s`, and the source transcript path;\n- cue grouping settings;\n- approved style, decision mode, rationale, gallery choice, and complete resolved style;\n- source-backed review evidence and approval actor;\n- cues with compatibility fields `start`, `end`, `text`, `lines`, and `words`;\n- `program_range` and `source_ranges` on every cue;\n- program/source ranges and `clip_id` on every mapped word;\n- a local renderer recipe with exact rational FPS and hashed runtime assets.\n\nThe generator also accepts the old top-level cue array. That is a standalone\ncompatibility adapter only; it cannot express timeline provenance or regenerate an\napproved protocol contribution.\n\nStandard is the default presentation mode. Standard plans do not need a\n`presentation` field and must not be migrated merely to add one. Expressive is an\nexplicit presentation mode, not a preset: request it with\n`--presentation-mode expressive`. The build step then adds stable cue IDs, default\nword `semantic_role` values, and a draft `presentation` planning shell; it does not\nguess layout variants.\n\nExpressive supports only `bottom-standard` and `center-emphasis`. Read\n`reference/caption-rules.md` before filling the shell. The\nexisting renderer consumes only completed `presentation.layout_beats`; it does not\ninfer positions or change layout inside a beat.\n\nExpressive cues may also define one contiguous phrase-level `hero_line`.\nUse canonical `level: hero`; it renders the phrase gold on its own line at\n`1.5x`. Legacy `level: strong` remains accepted as an input alias and renders\nidentically, but new Agent-authored plans must not emit it. The maintained word roles `keyword`, `number`, and\n`contrast` each use `1.22x`; `normal` remains `1.0x`. These treatments are\northogonal to presets and do not create new style IDs. Read\n`reference/caption-rules.md` for the schema, overflow policy, and Karaoke rules.\n\nWhen an active, review-approved B-roll operation contains approved or verified\nspeaker-inset composites, captions may derive `caption-spatial-context.json` from\nthe frozen upstream plan, evidence, normalized composites, and explicit subshot\n`display_mode`. This does not modify or reselect the B-roll layout. It aligns cue\nboundaries before Expressive planning and resolves only the maintained\n`preset-bottom`, `frame-center`, `panel-center`, and boundary-only `panel-bottom`\nplacements. `panel-bottom` is allowed only for an `unsplittable_word_boundary`\nadjacent to a lower-center focused panel; it centers the stable cue inside the\nreserved strip below the structured speaker rect. Such a cue is checked against\nevery visual context it intersects, not only the context containing the word\nmidpoint. Without this context, all existing Standard and Expressive placement\nbehavior remains unchanged.\n\n## Styles\n\nMaintained presets are `clean`, `minimal`, `social-bold`, `pill`, `boxed`,\n`stroked`, and `shorts`. Gallery combination IDs add maintained themes, for\nexample `pill-yellow`, `stroked-blue`, and `shorts-green`. Karaoke is an option,\nnot a separate preset.\n\nDefault delegated choice: `clean`, Karaoke off. Choose a more expressive preset\nonly when the destination or footage justifies it. Store job-specific changes in\nan overrides JSON; do not edit the generator for ordinary style feedback.\n\nExpressive supports both `semantic-only` (Karaoke off) and\n`semantic-plus-karaoke` (Karaoke on). Semantic emphasis persists for the full cue\nin both configurations. In coexistence mode, semantic emphasis and the active\nKaraoke word use `style.wordHighlight.activeColor` as their foreground color in\nevery highlight mode; a background highlight remains an additional effect. The\neffective scale is `max(semantic scale, karaoke active scale)`, never their\nproduct. Expressive never renders an underline, including for the compatible\n`contrast` semantic role.\n\nThe existing review contract continues to use semantic-only as the primary\nExpressive evidence and binds semantic-plus-karaoke separately. The compatibility\npayload field remains named `experimental_comparison`; do not rename it without an\napproval-protocol migration.\n\nRead:\n\n- `reference/caption-rules.md` before changing grouping limits;\n- `reference/caption-style-themes.md` for style vocabulary;\n- `reference/caption-feedback-mapping.md` for override mapping.\n\n## Decision Modes\n\nTwo honest modes share the same hash-bound state machine:\n\n- `human`: pass the exact copied gallery summary to `select`, show all real\n  preview evidence, and pass the exact copied approval or revision summary to\n  `confirm` or `adjust`. Review-page-bound commands do not accept bare approval\n  phrases.\n- `agent`: use only when the user explicitly delegates caption decisions. Start\n  with a non-empty delegation note, then use `agent-select` and `agent-confirm`\n  with non-empty rationales.\n\nHuman commands fail in agent mode and agent commands fail in human mode. Standard\nwithout spatial context requires the four maintained source-backed images;\nExpressive and composite-aware reviews use their dynamic evidence contracts. A\nchange to the source, plan, timeline, style, override, project metadata,\nreview page, or evidence invalidates approval; spatial context, upstream B-roll revision,\nor bound-composite changes do the same. Rebuild the affected artifacts and review\nthem again.\n\n## Canonical Workflow\n\nRun from the repository root. Set project paths:\n\n```powershell\n$RepoRoot = (Resolve-Path \".\").Path\n$SkillRoot = Join-Path $RepoRoot \"skills\\video-add-captions\"\n$ProjectRoot = (Resolve-Path \"<project-root>\").Path\n$Work = Join-Path $ProjectRoot \"work\"\n$SourceVideo = \"<absolute path from work/project.json>\"\n$Plan = Join-Path $Work \"captions\\captions-plan.json\"\n$SpatialContext = Join-Path $Work \"captions\\caption-spatial-context.json\"\n$SpatialArgs = @()\n$SpatialReviewArgs = @()\n$Receipt = Join-Path $Work \"captions\\caption-interaction.json\"\n$Review = Join-Path $ProjectRoot \"review\\05-captions\"\n$Cache = Join-Path $Work \"cache\\captions\"\n$PreviewProject = Join-Path $Cache \"preview-project\"\n$ComparisonProject = Join-Path $Cache \"preview-project-expressive-karaoke\"\n$OverlayProject = Join-Path $Cache \"overlay-project\"\n$OverlayFrames = Join-Path $Cache \"overlay-frames\"\nNew-Item -ItemType Directory -Force -Path (Split-Path $Plan),$Review,$Cache | Out-Null\n```\n\nBefore building the caption plan, ask the user to choose the presentation mode\nunless the current request already explicitly selects one. Render the question in\nthe user's current conversation language while keeping the `Standard` and\n`Expressive` identifiers untranslated. Keep the English source prompt below in\nthis file, present the localized choice in the current session, then\n**Present + STOP**:\n\n```text\nChoose the caption presentation mode:\n\n1. Standard: Uses stable conventional placement and suits most content.\n2. Expressive: Switches between bottom and center placement based on meaning and emphasizes keywords and numbers.\n\nReply with Standard, Expressive, or \"Use the default (Standard).\"\n```\n\nDo not run `build_captions.py` until the user responds. If the user explicitly\ndelegates the choice in any wording, select Standard. State that Standard was\nselected as the compatibility-preserving default before building the plan.\n\nBuild program-time cues and the review SRT:\n\n```powershell\npython \"$SkillRoot\\scripts\\build_captions.py\" `\n  \"$Work\\understand\\transcript.json\" `\n  $Plan `\n  \"$Review\\captions.srt\" `\n  --timeline \"$Work\\timeline.json\" `\n  --source-transcript \"understand/transcript.json\" `\n  --max-chars 42 --max-lines 2 --max-dur 6 --gap 0.6\n```\n\nAfter the user selects Standard, the command above produces the unchanged Standard\nplan. After the user selects Expressive, run the same command with:\n\n```powershell\n  --presentation-mode expressive\n```\n\nAfter draft cue generation, inspect the active B-roll operation and its bound plan\nread-only. Enable composite-aware placement only when the operation is active, its\nstatus is `approved` or `verified`, `review.status` is `approved`, the\nspeaker-inset analysis/agent-input/preview/clearance bindings are complete, and at\nleast one verified or normalized shot has a maintained composite layout. The\nspatial builder uses explicit subshot `display_mode: enabled` for speaker geometry;\n`pure_broll` and missing speaker evidence never imply a speaker rectangle.\n\nWhen eligible, align draft cues to every B-roll visual boundary before filling any\nExpressive `layout_beats` or `hero_line`. Alignment splits only at word gaps and\nwrites back atomically; a boundary inside a word keeps the word whole and records\nmandatory review evidence:\n\n```powershell\npython \"$SkillRoot\\scripts\\caption_spatial_context.py\" align `\n  --project-root $ProjectRoot --plan $Plan --out-plan $Plan\n```\n\nFor an eligible project, prepare the optional arguments used by every later gate:\n\n```powershell\n$SpatialArgs = @(\"--spatial-context\", $SpatialContext)\n$SpatialReviewArgs = @(\"--spatial-context\", $SpatialContext, \"--project-root\", $ProjectRoot)\n```\n\nWhen no eligible composite exists, skip all four spatial commands and leave\n`$SpatialArgs` and `$SpatialReviewArgs` empty. Do not create or attach an empty\ncontext.\n\nBefore starting style selection or generating a preview, the Agent must plan the\nentire Expressive program in one pass:\n\n1. Read the transcript, available understanding artifacts, canonical timeline,\n   generated caption cues, and any real source-frame evidence needed to justify\n   layout changes.\n2. Fill all `presentation.layout_beats`, annotate exceptional word\n   `semantic_role` values, add at most one contiguous `hero_line` with canonical\n   `level: hero` to a cue when\n   warranted, write one rationale per beat/hero line, and write the overall\n   `presentation.planner.rationale`.\n3. Set `presentation.planning_status` to `complete` only after every cue is covered\n   exactly once.\n4. Validate the completed plan before preview generation:\n\n```powershell\npython \"$SkillRoot\\scripts\\build_captions.py\" --validate-plan $Plan\n```\n\nFor an eligible composite, build, validate, and attach the context only after the\nfinal cue IDs and Expressive candidates are complete. This preserves the upstream\nlayout and binds its current revision and hashes into the caption plan:\n\n```powershell\npython \"$SkillRoot\\scripts\\caption_spatial_context.py\" build `\n  --project-root $ProjectRoot --plan $Plan --out $SpatialContext\n```\n\n```powershell\npython \"$SkillRoot\\scripts\\caption_spatial_context.py\" validate `\n  --project-root $ProjectRoot --plan $Plan --context $SpatialContext\n```\n\n```powershell\npython \"$SkillRoot\\scripts\\caption_spatial_context.py\" attach `\n  --project-root $ProjectRoot --plan $Plan --context $SpatialContext\n```\n\nDo not ask the user to choose a position cue by cue. The user reviews the existing\nHTML evidence and corrects only a small number of anomalous beats. Starting the\ninteraction after planning keeps the existing caption-plan hash binding authoritative.\n\nStart one decision mode. For human mode, publish the maintained offline gallery\ninto the project review directory without asking the user to locate a file:\n\n```powershell\n$StartOutput = node \"$SkillRoot\\scripts\\caption_interaction.mjs\" start `\n  --state $Receipt --source $SourceVideo --captions $Plan `\n  --review-dir $Review --no-open true @SpatialArgs\n$StartOutput | Write-Host\n```\n\n`start` prints the authoritative `captions-style-review-<UUID>.html` project page\npath and the fixed question. `captions-style-review.html` is a non-authoritative\nlatest convenience alias only. The Agent must take the UUID path printed after\n`Caption style review:`, assign that exact path to `$StyleReviewPage`, and open it\nwith the native command for the host OS:\n\nWindows PowerShell:\n\n```powershell\n$StyleReviewPage = \"<authoritative UUID page path printed after Caption style review:>\"\nStart-Process -FilePath (Resolve-Path $StyleReviewPage)\n```\n\nmacOS:\n\n```bash\nopen \"$StyleReviewPage\"\n```\n\nLinux:\n\n```bash\nxdg-open \"$StyleReviewPage\"\n```\n\nIf opening fails, diagnose the command or path and retry it. Do not ask the user\nto find the page. Present the opened gallery and the fixed question exactly as\nprinted, then **Present + STOP**. Do not continue until the human copies the\nstructured summary from the page. It has this form:\n\n```text\nCaption style review\nReview: <UUID from the opened page>\nDecision: select\nChoice: pill-yellow\n```\n\nPass the user's exact summary unchanged to `select --response`. In PowerShell, a\nsingle-quoted here-string preserves the lines safely:\n\n```powershell\n$StyleResponse = @'\nCaption style review\nReview: <UUID from the opened page>\nDecision: select\nChoice: pill-yellow\n'@\nnode \"$SkillRoot\\scripts\\caption_interaction.mjs\" select `\n  --state $Receipt --response $StyleResponse\n```\n\nThe block above shows the structure; use the user's returned block, including\ntheir review UUID and choice. Never infer, normalize, or simulate a human\nresponse.\n\nFor explicit delegation, create a separate Agent-mode receipt. Keep the bound\ngallery, suppress the script's Windows-only auto-open, open the authoritative UUID\npage with the matching native command above, and inspect it before choosing:\n\n```powershell\n$StartOutput = node \"$SkillRoot\\scripts\\caption_interaction.mjs\" start `\n  --state $Receipt --source $SourceVideo --captions $Plan `\n  --review-dir $Review `\n  --decision-mode agent `\n  --delegation-note \"User delegated caption style and preview approval.\" `\n  --no-open true @SpatialArgs\n$StartOutput | Write-Host\n\nnode \"$SkillRoot\\scripts\\caption_interaction.mjs\" agent-select `\n  --state $Receipt --choice clean `\n  --rationale \"Conservative readable treatment preserves the talking-head frame.\"\n```\n\nUse `agent-select` only after inspecting the bound gallery and record the real\nrationale. Do not manufacture a human summary or use `select` in Agent mode.\n\nGenerate a transparent preview composition. The project contains only local GSAP\nand font files and preserves rational FPS in `project-meta.json`:\n\n```powershell\nnode \"$SkillRoot\\scripts\\generate_caption_project.mjs\" `\n  --video $SourceVideo --captions $Plan --out $PreviewProject `\n  --interaction-state $Receipt --project-root $ProjectRoot --mode preview @SpatialArgs\n\nnpx.cmd hyperframes check $PreviewProject --at 1 --timeout 10000 --no-contrast\n```\n\nFor the primary Expressive review, append `--karaoke false`. Generate the supported\ncoexistence comparison from the same source, plan, interaction selection, and timing by changing\nonly the output directory and appending `--karaoke true`:\n\n```powershell\nnode \"$SkillRoot\\scripts\\generate_caption_project.mjs\" `\n  --video $SourceVideo --captions $Plan --out $ComparisonProject `\n  --interaction-state $Receipt --project-root $ProjectRoot `\n  --karaoke true --mode preview @SpatialArgs\n```\n\nFor Standard, build mapped early/middle/late/no-caption evidence. This command captures\ntransparent HyperFrames snapshots, maps each program time through\n`timeline.json`, extracts the matching original source frame, and composites the\ntwo with Pillow:\n\n```powershell\npython \"$SkillRoot\\scripts\\build_caption_review.py\" `\n  --source $SourceVideo --timeline \"$Work\\timeline.json\" --plan $Plan `\n  --project $PreviewProject --cache \"$Cache\\review-cache\" --out $Review `\n  --interaction-state $Receipt @SpatialReviewArgs\n```\n\nFor Expressive, the same builder emits exhaustive machine samples, a compact\nhuman-review index, and a separately bound semantic-only/Karaoke comparison:\n\n```powershell\npython \"$SkillRoot\\scripts\\build_caption_review.py\" `\n  --source $SourceVideo --timeline \"$Work\\timeline.json\" --plan $Plan `\n  --project $PreviewProject --comparison-project $ComparisonProject `\n  --cache \"$Cache\\review-cache\" --out $Review --interaction-state $Receipt `\n  @SpatialReviewArgs\n```\n\nThe builder writes `captions-review.html` as the one authoritative page. Standard\nwithout spatial context contains exactly the four maintained evidence labels. For\nExpressive or composite-aware review, `captions-evidence.json.samples` retains all\ndense machine evidence while `review_samples` contains at most one representative\nfor each category actually present: `bottom-standard`, `center-emphasis`,\n`preset-bottom`, `frame-center`, `panel-center`, and `Hero 1.5x`. One PNG may carry\nmultiple category labels; absent categories are not fabricated. `no-caption`\nremains machine evidence, and the Karaoke comparison remains separate from the\nrepresentative count. The\nAgent must open that page with the native command for the host OS; for example,\nset `$EvidenceReviewPage = \"$Review\\captions-review.html\"` and run one of:\n\n```powershell\nStart-Process -FilePath (Resolve-Path $EvidenceReviewPage)\n```\n\n```bash\nopen \"$EvidenceReviewPage\"\n```\n\n```bash\nxdg-open \"$EvidenceReviewPage\"\n```\n\nIf opening fails, diagnose and retry. Inspect every representative primary image for readability,\nsafe-area placement, clipping, word wrapping, stable beat placement, and unwanted\npixels. Machine validation must already have passed every dense sample, including\n`no-caption`, before this gate. Standard without spatial context binds the\nmaintained four images:\n\n```powershell\n$Evidence = @(\n  \"$Review\\preview-early.png\",\n  \"$Review\\preview-middle.png\",\n  \"$Review\\preview-late.png\",\n  \"$Review\\preview-no-caption.png\"\n) -join \",\"\n\nnode \"$SkillRoot\\scripts\\caption_interaction.mjs\" preview-ready `\n  --state $Receipt --project-meta \"$PreviewProject\\project-meta.json\" `\n  --evidence $Evidence --review-page \"$Review\\captions-review.html\" `\n  --timeline \"$Work\\timeline.json\"\n```\n\nWith spatial context, keep those maintained Standard samples and add dense\nstart/25%/50%/75%/end evidence only for placement beats that carry a composite\nbackground. Do not add dense evidence for A-roll placement beats.\n\nUse that fixed four-image command only when Standard has no spatial context. For\nStandard with a bound spatial context, bind only the representative PNGs and bind\nthe complete machine evidence document separately. Standard has no coexistence comparison, so do not pass\n`--comparison-evidence`:\n\n```powershell\n$StandardSpatialEvidenceDocument = Get-Content \"$Review\\captions-evidence.json\" -Raw | ConvertFrom-Json\n$Evidence = @($StandardSpatialEvidenceDocument.review_samples | ForEach-Object {\n  Join-Path $Review $_.preview\n}) -join \",\"\n\nnode \"$SkillRoot\\scripts\\caption_interaction.mjs\" preview-ready `\n  --state $Receipt --project-meta \"$PreviewProject\\project-meta.json\" `\n  --evidence $Evidence --evidence-document \"$Review\\captions-evidence.json\" `\n  --review-page \"$Review\\captions-review.html\" `\n  --timeline \"$Work\\timeline.json\"\n```\n\nExpressive binds the representative PNGs, complete machine document, and separate\ncoexistence pair from `captions-evidence.json`:\n\n```powershell\n$EvidenceDocument = Get-Content \"$Review\\captions-evidence.json\" -Raw | ConvertFrom-Json\n$Evidence = @($EvidenceDocument.review_samples | ForEach-Object {\n  Join-Path $Review $_.preview\n}) -join \",\"\n$ComparisonEvidence = @($EvidenceDocument.experimental_comparison.samples | ForEach-Object {\n  Join-Path $Review $_.preview\n}) -join \",\"\n\nnode \"$SkillRoot\\scripts\\caption_interaction.mjs\" preview-ready `\n  --state $Receipt --project-meta \"$PreviewProject\\project-meta.json\" `\n  --evidence $Evidence --evidence-document \"$Review\\captions-evidence.json\" `\n  --comparison-evidence $ComparisonEvidence `\n  --review-page \"$Review\\captions-review.html\" --timeline \"$Work\\timeline.json\"\n```\n\nIn human mode, present the opened page and its images, then **Present + STOP**.\nThe human copies one of these structured summaries from the page:\n\n```text\nCaption preview review\nReview: <UUID from the opened page>\nDecision: approve\nEvidence: early, middle, late, no-caption\n```\n\nExpressive approval uses the dynamic evidence token and requires the explicit\nKaraoke choice made after inspecting the bound comparison evidence:\n\n```text\nCaption preview review\nReview: <UUID from the opened page>\nDecision: approve\nEvidence: expressive-layout-beats\nKaraoke: on|off\n```\n\nWhen `caption-spatial-context.json` is bound, the authoritative page emits the\ncomposite-aware token. Standard uses the same block without the Karaoke line;\nExpressive requires the explicit Karaoke choice:\n\n```text\nCaption preview review\nReview: <UUID from the opened page>\nDecision: approve\nEvidence: composite-aware\nKaraoke: on|off\n```\n\n```text\nCaption preview review\nReview: <UUID from the opened page>\nDecision: revise\nChanges: Raise captions 20 pixels.\n```\n\nPass the returned summary unchanged to `confirm --response` or\n`adjust --response`. Quote the multiline response safely in PowerShell:\n\n```powershell\n$PreviewResponse = @'\nCaption preview review\nReview: <UUID from the opened page>\nDecision: approve\nEvidence: early, middle, late, no-caption\n'@\nnode \"$SkillRoot\\scripts\\caption_interaction.mjs\" confirm `\n  --state $Receipt --response $PreviewResponse\n```\n\nFor a `revise` summary, preserve it in `$PreviewResponse` the same way and run:\n\n```powershell\nnode \"$SkillRoot\\scripts\\caption_interaction.mjs\" adjust `\n  --state $Receipt --response $PreviewResponse\n```\n\nApply the requested change, then regenerate the preview project, review page, and\nall evidence before presenting the gate again. Never convert human feedback into\nan Agent decision.\n\nIn delegated mode, the Agent must inspect the same `captions-review.html` page and\nall primary evidence plus any coexistence comparison before recording its own rationale.\nFor Standard, keep the existing command unchanged:\n\n```powershell\nnode \"$SkillRoot\\scripts\\caption_interaction.mjs\" agent-confirm `\n  --state $Receipt `\n  --rationale \"All bound primary frames are readable and collision-free; coexistence comparison evidence was inspected separately when present.\"\n```\n\nFor Expressive, pass the explicit Karaoke choice made from the comparison:\n\n```powershell\nnode \"$SkillRoot\\scripts\\caption_interaction.mjs\" agent-confirm `\n  --state $Receipt --karaoke off `\n  --rationale \"The semantic-only comparison is clearer for this footage.\"\n```\n\nGenerate the approved overlay project. This formal render is the only step that\nfinalizes `style`, `review`, and hashed runtime assets in the canonical plan.\nExpressive overlay generation uses the preview-approved Karaoke choice; an\nexplicit `--karaoke` value must match that approval:\n\nFor Expressive, formal overlay generation stores the Human-visible representative\nPNGs in `review.representative_evidence`. It keeps `review.evidence` as the shared\ndelivery compiler compatibility set: one already-bound machine sample per layout beat\nplus `no-caption`, selected from `captions-evidence.json`. This does not add cards to\nthe human review or claim that the user inspected machine-only samples; the complete\ndocument path, hash, and sample count remain in `review.machine_evidence_document`.\n\n```powershell\nnode \"$SkillRoot\\scripts\\generate_caption_project.mjs\" `\n  --video $SourceVideo --captions $Plan --out $OverlayProject `\n  --interaction-state $Receipt --project-root $ProjectRoot --mode overlay @SpatialArgs\n\nnpx.cmd hyperframes check $OverlayProject --at 1 --timeout 10000 --no-contrast\n\n$RenderMeta = Get-Content -Raw \"$OverlayProject\\project-meta.json\" | ConvertFrom-Json\n$RenderFps = \"$($RenderMeta.fpsRational.num)/$($RenderMeta.fpsRational.den)\"\nPush-Location $OverlayProject\ntry {\n  npx.cmd hyperframes render --format png-sequence --fps $RenderFps --output $OverlayFrames\n}\nfinally {\n  Pop-Location\n}\n```\n\nCanonical overlay generation refreshes the `## Approval` section in\n`captions-summary.md` with the selected style, decision actor/rationale, and\nhash-binding result. It deliberately leaves rendered-frame and shared-delivery\nchecks pending; replace that pending line with the measured results before marking\nthe operation verified.\n\nAfter cache deletion, rebuild the composition without replaying approval:\n\n```powershell\nnode \"$SkillRoot\\scripts\\generate_caption_project.mjs\" `\n  --video $SourceVideo --captions $Plan --out $OverlayProject `\n  --approved-plan true --project-root $ProjectRoot --mode overlay @SpatialArgs\n```\n\nThe command verifies that the frozen runtime hashes still match.\n\n## Project Registration\n\nAdd or revise one `captions` operation in `work/project.json`. Depend on\n`understanding`; also depend on the active `cut` operation when it exists.\nWhen active B-roll supplied the spatial context, also add `b-roll` to\n`depends_on`; its `based_on` value must equal the bound active B-roll revision.\nWhen there is no spatial context, omit `b-roll` from `depends_on` and\n`based_on`. All `based_on` values must equal the current dependency revisions.\n\n```json\n{\n  \"id\": \"captions\",\n  \"skill\": \"video-add-captions\",\n  \"revision\": 1,\n  \"depends_on\": [\"understanding\", \"cut\", \"b-roll\"],\n  \"based_on\": {\"understanding\": 1, \"cut\": 1, \"b-roll\": 4},\n  \"status\": \"verified\",\n  \"plan\": \"captions/captions-plan.json\",\n  \"outputs\": [\"cache/captions/overlay-frames\"],\n  \"target\": {\"sequence\": \"main\", \"scope\": \"captions\"},\n  \"effects\": {\n    \"changes_timeline\": false,\n    \"changes_geometry\": false,\n    \"changes_video_pixels\": false,\n    \"changes_audio\": false,\n    \"adds_track\": \"captions\"\n  },\n  \"check\": {\"status\": \"pass\", \"report\": \"../review/05-captions/captions-summary.md\"},\n  \"render\": {\n    \"kind\": \"overlay\",\n    \"asset\": \"cache/captions/overlay-frames\",\n    \"asset_type\": \"image-sequence\",\n    \"pattern\": \"frame_%06d.png\",\n    \"start_number\": 1,\n    \"fps\": {\"num\": 30000, \"den\": 1001}\n  }\n}\n```\n\nUse the actual timeline FPS. Insert `captions` before active `content-cards` and\n`graphic-motion` operations. Captions never depend on either downstream operation;\nadding captions to a project that already contains them requires reordering the active\nsequence. Then compile and render delivery once:\n\n```powershell\npython \"$RepoRoot\\skills\\video-understand\\scripts\\validate.py\" project `\n  \"$Work\\project.json\" $ProjectRoot\npython \"$RepoRoot\\skills\\video-understand\\scripts\\build_render_plan.py\" $ProjectRoot\npython \"$RepoRoot\\skills\\video-understand\\scripts\\render_project.py\" `\n  \"$Work\\render\\render-plan.json\"\n```\n\nThe shared renderer copies audio only for an identity timeline with no audio\nfilters. Cuts or retiming require encoded/filtered audio. Caption code never\noverrides that decision.\n\n## Compatibility\n\nWithout `--timeline`, `build_captions.py` still writes the old cue array.\n`generate_caption_project.mjs` accepts that array with the interaction receipt.\nStandalone exact ID and skip responses are legacy compatibility only. Standalone\n`approve` is also a legacy compatibility response. Historical non-English aliases\nremain accepted as input compatibility but are never emitted or documented as\nuser instructions.\n`composite_caption_overlay.ps1` accepts an overlay video or `frame_%06d.png`\ndirectory and writes compatible H.264/yuv420p while copying source audio. Use this\nstandalone path only when no active operation changes time; canonical projects use\nthe shared renderer.\n\n## Self Check\n\nRun before declaring the operation verified:\n\n```powershell\npython \"$SkillRoot\\scripts\\check_project_protocol.py\"\nnode \"$SkillRoot\\scripts\\check_caption_style_config.mjs\"\npython \"$SkillRoot\\scripts\\check_caption_spatial_context.py\"\npython \"$SkillRoot\\scripts\\check_caption_review.py\"\nnode \"$SkillRoot\\scripts\\check_caption_interaction.mjs\"\npowershell.exe -ExecutionPolicy Bypass -File \"$SkillRoot\\scripts\\check_structure.ps1\"\nnode \"$SkillRoot\\scripts\\caption_interaction.mjs\" status --state $Receipt\n```\n\nAlso verify:\n\n- cue and word times are finite, ordered, within program duration, and never cross clips;\n- early/middle/late captions appear on the correct mapped source pixels;\n- dropped speech has no cue;\n- overlay first frame exists, dimensions match, FPS equals timeline rational FPS,\n  frame count equals `ceil(program_duration_s * fps.num / fps.den)`, duration covers\n  the complete program, and non-caption pixels are transparent;\n- the shared delivery exists, has synchronized audio, and passes the project's\n  delivery report;\n- `captions-summary.md` records the selected style, approval mode/rationale,\n  evidence, and validation result.\n\nHTML generation alone is not success. Inspect actual pixels and the final delivery\nbefore reporting completion.\n\nFile v1.0.7:_meta.json\n\n{\n  \"ownerId\": \"kn70hcphbcepjj43vwv7j0zbnh8b0jpm\",\n  \"slug\": \"video-add-captions\",\n  \"version\": \"1.0.7\",\n  \"publishedAt\": 1787121456937\n}\n\nFile v1.0.7:scripts/caption-styles.json\n\n{\n  \"presets\": {\n    \"clean\": {\n      \"preset\": \"clean\",\n      \"font\": {\n        \"family\": \"system-ui, sans-serif\",\n        \"sizeRatio\": 0.0416,\n        \"weight\": 800,\n        \"color\": \"#F5F4ED\",\n        \"lineHeight\": 1.32,\n        \"letterSpacing\": 0\n      },\n      \"layout\": {\n        \"anchor\": \"bottom\",\n        \"align\": \"center\",\n        \"maxWidth\": 0.92,\n        \"paddingBottomRatio\": 0.07\n      },\n      \"background\": {\n        \"enabled\": false,\n        \"theme\": \"gray\",\n        \"shape\": \"rounded\",\n        \"c\n\nArchive v1.0.6: 51 files, 157957 bytes\n\nFiles: assets/captions-review.html (17006b), assets/style-previews/index.html (50456b), assets/style-previews/preview-manifest.json (22431b), assets/style-previews/props-boxed-blue.json (4292b), assets/style-previews/props-boxed-gray.json (4292b), assets/style-previews/props-boxed-green.json (4295b), assets/style-previews/props-boxed-pink.json (4292b), assets/style-previews/props-boxed-yellow.json (4298b), assets/style-previews/props-clean.json (4293b), assets/style-previews/props-minimal.json (4279b), assets/style-previews/props-pill-blue.json (4296b), assets/style-previews/props-pill-gray.json (4296b), assets/style-previews/props-pill-green.json (4299b), assets/style-previews/props-pill-pink.json (4296b), assets/style-previews/props-pill-yellow.json (4302b), assets/style-previews/props-shorts-blue.json (4319b), assets/style-previews/props-shorts-green.json (4321b), assets/style-previews/props-shorts-orange.json (4323b), assets/style-previews/props-shorts-pink.json (4319b), assets/style-previews/props-shorts-purple.json (4323b), assets/style-previews/props-shorts-yellow.json (4323b), assets/style-previews/props-social-bold-karaoke.json (4324b), assets/style-previews/props-social-bold.json (4317b), assets/style-previews/props-stroked-black.json (4299b), assets/style-previews/props-stroked-blue.json (4297b), assets/style-previews/props-stroked-green.json (4300b), assets/style-previews/props-stroked-pink.json (4297b), assets/style-previews/props-stroked-yellow.json (4303b), examples/index.html (4642b), public/gsap.min.js (72212b), reference/caption-feedback-mapping.md (5890b), reference/caption-rules.md (5135b), reference/caption-style-themes.md (2437b), scripts/build_caption_review.py (24045b), scripts/build_captions.py (20281b), scripts/build_style_preview_gallery.mjs (18396b), scripts/caption_interaction_state.mjs (17973b), scripts/caption_interaction.mjs (30723b), scripts/caption_style_config.mjs (3740b), scripts/caption-styles.json (13984b), scripts/check_caption_interaction.mjs (49778b), scripts/check_caption_review.py (16514b), scripts/check_caption_style_config.mjs (3857b), scripts/check_project_protocol.py (38517b), scripts/check_structure.ps1 (8082b), scripts/composite_caption_overlay.ps1 (2244b), scripts/generate_caption_project.mjs (32804b), scripts/render_style_previews.ps1 (18045b), skill-card.md (3025b), SKILL.md (23977b), _meta.json (137b)\n\nArchive v1.0.5: 51 files, 157546 bytes\n\nFiles: assets/captions-review.html (17006b), assets/style-previews/index.html (50456b), assets/style-previews/preview-manifest.json (22431b), assets/style-previews/props-boxed-blue.json (4292b), assets/style-previews/props-boxed-gray.json (4292b), assets/style-previews/props-boxed-green.json (4295b), assets/style-previews/props-boxed-pink.json (4292b), assets/style-previews/props-boxed-yellow.json (4298b), assets/style-previews/props-clean.json (4293b), assets/style-previews/props-minimal.json (4279b), assets/style-previews/props-pill-blue.json (4296b), assets/style-previews/props-pill-gray.json (4296b), assets/style-previews/props-pill-green.json (4299b), assets/style-previews/props-pill-pink.json (4296b), assets/style-previews/props-pill-yellow.json (4302b), assets/style-previews/props-shorts-blue.json (4319b), assets/style-previews/props-shorts-green.json (4321b), assets/style-previews/props-shorts-orange.json (4323b), assets/style-previews/props-shorts-pink.json (4319b), assets/style-previews/props-shorts-purple.json (4323b), assets/style-previews/props-shorts-yellow.json (4323b), assets/style-previews/props-social-bold-karaoke.json (4324b), assets/style-previews/props-social-bold.json (4317b), assets/style-previews/props-stroked-black.json (4299b), assets/style-previews/props-stroked-blue.json (4297b), assets/style-previews/props-stroked-green.json (4300b), assets/style-previews/props-stroked-pink.json (4297b), assets/style-previews/props-stroked-yellow.json (4303b), examples/index.html (4642b), public/gsap.min.js (72212b), reference/caption-feedback-mapping.md (5890b), reference/caption-rules.md (5135b), reference/caption-style-themes.md (2437b), scripts/build_caption_review.py (24045b), scripts/build_captions.py (20281b), scripts/build_style_preview_gallery.mjs (18396b), scripts/caption_interaction_state.mjs (17973b), scripts/caption_interaction.mjs (30723b), scripts/caption_style_config.mjs (3740b), scripts/caption-styles.json (13984b), scripts/check_caption_interaction.mjs (49778b), scripts/check_caption_review.py (16514b), scripts/check_caption_style_config.mjs (3857b), scripts/check_project_protocol.py (38517b), scripts/check_structure.ps1 (8082b), scripts/composite_caption_overlay.ps1 (2244b), scripts/generate_caption_project.mjs (32804b), scripts/render_style_previews.ps1 (18045b), skill-card.md (2551b), SKILL.md (23538b), _meta.json (137b)\n\nArchive v1.0.4: 51 files, 136152 bytes\n\nFiles: assets/captions-review.html (10160b), assets/style-previews/index.html (50456b), assets/style-previews/preview-manifest.json (22431b), assets/style-previews/props-boxed-blue.json (4292b), assets/style-previews/props-boxed-gray.json (4292b), assets/style-previews/props-boxed-green.json (4295b), assets/style-previews/props-boxed-pink.json (4292b), assets/style-previews/props-boxed-yellow.json (4298b), assets/style-previews/props-clean.json (4293b), assets/style-previews/props-minimal.json (4279b), assets/style-previews/props-pill-blue.json (4296b), assets/style-previews/props-pill-gray.json (4296b), assets/style-previews/props-pill-green.json (4299b), assets/style-previews/props-pill-pink.json (4296b), assets/style-previews/props-pill-yellow.json (4302b), assets/style-previews/props-shorts-blue.json (4319b), assets/style-previews/props-shorts-green.json (4321b), assets/style-previews/props-shorts-orange.json (4323b), assets/style-previews/props-shorts-pink.json (4319b), assets/style-previews/props-shorts-purple.json (4323b), assets/style-previews/props-shorts-yellow.json (4323b), assets/style-previews/props-social-bold-karaoke.json (4324b), assets/style-previews/props-social-bold.json (4317b), assets/style-previews/props-stroked-black.json (4299b), assets/style-previews/props-stroked-blue.json (4297b), assets/style-previews/props-stroked-green.json (4300b), assets/style-previews/props-stroked-pink.json (4297b), assets/style-previews/props-stroked-yellow.json (4303b), examples/index.html (4642b), public/gsap.min.js (72212b), reference/caption-feedback-mapping.md (5890b), reference/caption-rules.md (1581b), reference/caption-style-themes.md (2437b), scripts/build_caption_review.py (11304b), scripts/build_captions.py (10055b), scripts/build_style_preview_gallery.mjs (18396b), scripts/caption_interaction_state.mjs (14983b), scripts/caption_interaction.mjs (25579b), scripts/caption_style_config.mjs (3740b), scripts/caption-styles.json (13984b), scripts/check_caption_interaction.mjs (37522b), scripts/check_caption_review.py (10385b), scripts/check_caption_style_config.mjs (3857b), scripts/check_project_protocol.py (15609b), scripts/check_structure.ps1 (6676b), scripts/composite_caption_overlay.ps1 (2244b), scripts/generate_caption_project.mjs (21638b), scripts/render_style_previews.ps1 (18045b), skill-card.md (2915b), SKILL.md (17045b), _meta.json (137b)\n\nArchive v1.0.3: 51 files, 136092 bytes\n\nFiles: assets/captions-review.html (10160b), assets/style-previews/index.html (50456b), assets/style-previews/preview-manifest.json (22431b), assets/style-previews/props-boxed-blue.json (4292b), assets/style-previews/props-boxed-gray.json (4292b), assets/style-previews/props-boxed-green.json (4295b), assets/style-previews/props-boxed-pink.json (4292b), assets/style-previews/props-boxed-yellow.json (4298b), assets/style-previews/props-clean.json (4293b), assets/style-previews/props-minimal.json (4279b), assets/style-previews/props-pill-blue.json (4296b), assets/style-previews/props-pill-gray.json (4296b), assets/style-previews/props-pill-green.json (4299b), assets/style-previews/props-pill-pink.json (4296b), assets/style-previews/props-pill-yellow.json (4302b), assets/style-previews/props-shorts-blue.json (4319b), assets/style-previews/props-shorts-green.json (4321b), assets/style-previews/props-shorts-orange.json (4323b), assets/style-previews/props-shorts-pink.json (4319b), assets/style-previews/props-shorts-purple.json (4323b), assets/style-previews/props-shorts-yellow.json (4323b), assets/style-previews/props-social-bold-karaoke.json (4324b), assets/style-previews/props-social-bold.json (4317b), assets/style-previews/props-stroked-black.json (4299b), assets/style-previews/props-stroked-blue.json (4297b), assets/style-previews/props-stroked-green.json (4300b), assets/style-previews/props-stroked-pink.json (4297b), assets/style-previews/props-stroked-yellow.json (4303b), examples/index.html (4642b), public/gsap.min.js (72212b), reference/caption-feedback-mapping.md (5890b), reference/caption-rules.md (1581b), reference/caption-style-themes.md (2437b), scripts/build_caption_review.py (11304b), scripts/build_captions.py (10055b), scripts/build_style_preview_gallery.mjs (18396b), scripts/caption_interaction_state.mjs (14983b), scripts/caption_interaction.mjs (25579b), scripts/caption_style_config.mjs (3740b), scripts/caption-styles.json (13984b), scripts/check_caption_interaction.mjs (37522b), scripts/check_caption_review.py (10385b), scripts/check_caption_style_config.mjs (3857b), scripts/check_project_protocol.py (15609b), scripts/check_structure.ps1 (6676b), scripts/composite_caption_overlay.ps1 (2244b), scripts/generate_caption_project.mjs (21638b), scripts/render_style_previews.ps1 (18045b), skill-card.md (2751b), SKILL.md (17015b), _meta.json (137b)\n\nArchive v1.0.2: 51 files, 135964 bytes\n\nFiles: assets/captions-review.html (10160b), assets/style-previews...","readmeExcerpt":"Skill: video-add-captions Owner: whitetowerai Summary: Add word-timed captions to an Open Recut program. Use this skill to map the canonical transcript through timeline.json, review a maintained style on source-backed pixels, render a local transparent HyperFrames PNG sequence, and register it as an overlay contribution for the shared delivery render. Tags: latest:1.0.8 Version history: v1.0.8 | 2026-10-07T04:13:18.1","codeSnippets":[],"executableExamples":[{"language":"text","snippet":"work/captions/\n|-- captions-plan.json\n|-- caption-spatial-context.json (only for eligible B-roll composites)\n`-- caption-interaction.json\nwork/cache/captions/\n|-- preview-project/\n|-- preview-snapshots/\n|-- overlay-project/\n`-- overlay-frames/frame_000001.png ...\nreview/05-captions/\n|-- captions.srt\n|-- captions-style-review-<UUID>.html\n|-- captions-style-review.html\n|-- captions-review.html\n|-- preview-early.png\n|-- preview-middle.png\n|-- preview-late.png\n|-- preview-no-caption.png\n|-- captions-evidence.json\n`-- captions-summary.md"},{"language":"powershell","snippet":"$RepoRoot = (Resolve-Path \".\").Path\n$SkillRoot = Join-Path $RepoRoot \"skills\\video-add-captions\"\n$ProjectRoot = (Resolve-Path \"<project-root>\").Path\n$Work = Join-Path $ProjectRoot \"work\"\n$SourceVideo = \"<absolute path from work/project.json>\"\n$Plan = Join-Path $Work \"captions\\captions-plan.json\"\n$SpatialContext = Join-Path $Work \"captions\\caption-spatial-context.json\"\n$SpatialArgs = @()\n$SpatialReviewArgs = @()\n$Receipt = Join-Path $Work \"captions\\caption-interaction.json\"\n$Review = Join-Path $ProjectRoot \"review\\05-captions\"\n$Cache = Join-Path $Work \"cache\\captions\"\n$PreviewProject = Join-Path $Cache \"preview-project\"\n$ComparisonProject = Join-Path $Cache \"preview-project-expressive-karaoke\"\n$OverlayProject = Join-Path $Cache \"overlay-project\"\n$OverlayFrames = Join-Path $Cache \"overlay-frames\"\nNew-Item -ItemType Directory -Force -Path (Split-Path $Plan),$Review,$Cache | Out-Null"},{"language":"text","snippet":"Choose the caption presentation mode:\n\n1. Standard: Uses stable conventional placement and suits most content.\n2. Expressive: Switches between bottom and center placement based on meaning and emphasizes keywords and numbers.\n\nReply with Standard, Expressive, or \"Use the default (Standard).\""},{"language":"powershell","snippet":"python \"$SkillRoot\\scripts\\build_captions.py\" `\n  \"$Work\\understand\\transcript.json\" `\n  $Plan `\n  \"$Review\\captions.srt\" `\n  --timeline \"$Work\\timeline.json\" `\n  --source-transcript \"understand/transcript.json\" `\n  --max-chars 42 --max-lines 2 --max-dur 6 --gap 0.6"},{"language":"powershell","snippet":"--presentation-mode expressive"},{"language":"powershell","snippet":"python \"$SkillRoot\\scripts\\caption_spatial_context.py\" align `\n  --project-root $ProjectRoot --plan $Plan --out-plan $Plan"}],"parameters":null,"dependencies":[],"permissions":[],"extractedFiles":[{"path":"SKILL.md","content":"---\nname: video-add-captions\ndescription: >\n  Add word-timed captions to an Open Recut program. Use this skill to map the\n  canonical transcript through timeline.json, review a maintained style on\n  source-backed pixels, render a local transparent HyperFrames PNG sequence,\n  and register it as an overlay contribution for the shared delivery render.\n---\n\n# Video Add Captions\n\n## Dependencies\n\n`/video-understand` is a prerequisite. Run it first so captions use the validated\nword-level transcript and canonical timeline.\nBefore starting, verify that it is installed. If it is not, warn the user that\nthis prerequisite is missing and stop before processing media.\n\nRequire `ffmpeg`/`ffprobe` on PATH, Python with `Pillow`, and Node.js >= 22 (for the `.mjs` scripts and `npx hyperframes`, fetched on demand). `hyperframes render`/`snapshot` drives a headless Chrome — it manages its own `chrome-headless-shell`, and falls back to a system Chrome (set `CHROME` to override) when the cached one is unusable. Check these before processing media.\n\n## Scope\n\nThis skill owns caption grouping, style selection, review, and the transparent\ncaption track. It does not transcribe, cut, retime, grade, reframe, or choose the\ndelivery audio policy.\n\nRun captions before content cards and motion graphics whenever either is selected.\nThe approved caption layout establishes a reserved subtitle region that both later\noperations must keep clear. This relative order also applies when only one pair is active.\n\nUse `video-understand` first. If a cut exists, caption the active program\ntimeline; do not treat source transcript seconds as program seconds.\n\n## Protocol Inputs\n\nRequired:\n\n- `work/project.json`\n- `work/understand/transcript.json`, with word-level source timestamps\n- `work/understand/media.json`\n- `work/timeline.json`\n- the source video named by `work/project.json`\n\nThe timeline is the only source-to-program mapping. All ranges are half-open\n`[start_s, end_s)`. `scripts/build_captions.py` uses the shared\n`projectlib.map_transcript_to_timeline`; words in dropped source ranges disappear,\nretained words move to program time, and cues always break at clip boundaries.\n\n## Durable Outputs\n\n```text\nwork/captions/\n|-- captions-plan.json\n|-- caption-spatial-context.json (only for eligible B-roll composites)\n`-- caption-interaction.json\nwork/cache/captions/\n|-- preview-project/\n|-- preview-snapshots/\n|-- overlay-project/\n`-- overlay-frames/frame_000001.png ...\nreview/05-captions/\n|-- captions.srt\n|-- captions-style-review-<UUID>.html\n|-- captions-style-review.html\n|-- captions-review.html\n|-- preview-early.png\n|-- preview-middle.png\n|-- preview-late.png\n|-- preview-no-caption.png\n|-- captions-evidence.json\n`-- captions-summary.md\n```\n\nGenerated HTML, copied runtime files, extracted source frames, and overlay frames\nare cache. The plan, optional spatial context, SRT, decision receipt, and review\nevidence are durable.\n\n## Caption Plan\n\n`work/captions/captions-plan.json` is the canonical c"},{"path":"_meta.json","content":"{\n  \"ownerId\": \"kn70hcphbcepjj43vwv7j0zbnh8b0jpm\",\n  \"slug\": \"video-add-captions\",\n  \"version\": \"1.0.8\",\n  \"publishedAt\": 1791346398173\n}"},{"path":"scripts/caption-styles.json","content":"{\n  \"presets\": {\n    \"clean\": {\n      \"preset\": \"clean\",\n      \"font\": {\n        \"family\": \"system-ui, sans-serif\",\n        \"sizeRatio\": 0.0416,\n        \"weight\": 800,\n        \"color\": \"#F5F4ED\",\n        \"lineHeight\": 1.32,\n        \"letterSpacing\": 0\n      },\n      \"layout\": {\n        \"anchor\": \"bottom\",\n        \"align\": \"center\",\n        \"maxWidth\": 0.92,\n        \"paddingBottomRatio\": 0.07\n      },\n      \"background\": {\n        \"enabled\": false,\n        \"theme\": \"gray\",\n        \"shape\": \"rounded\",\n        \"color\": \"#000000\",\n        \"opacity\": 0.48,\n        \"radiusRatio\": 0.018,\n        \"paddingXRatio\": 0.035,\n        \"paddingYRatio\": 0.018\n      },\n      \"effects\": {\n        \"shadow\": {\n          \"strength\": \"none\",\n          \"color\": \"#000000\",\n          \"opacity\": 0.55,\n          \"offsetYRatio\": 0.02,\n          \"blurRatio\": 0.04\n        },\n        \"stroke\": {\n          \"strength\": \"none\",\n          \"color\": \"#000000\",\n          \"widthRatio\": 0\n        }\n      },\n      \"stroke\": {\n        \"enabled\": false,\n        \"theme\": \"black\",\n        \"color\": \"#000000\",\n        \"opacity\": 0.85,\n        \"widthRatio\": 0\n      },\n      \"wordHighlight\": {\n        \"enabled\": true,\n        \"mode\": \"textColor\",\n        \"activeColor\": \"#FF7A45\",\n        \"activeScale\": 1.12,\n        \"upcomingOpacity\": 0.55,\n        \"backgroundColor\": \"#FF7A45\",\n        \"backgroundOpacity\": 0.24,\n        \"backgroundRadiusRatio\": 0.01\n      },\n      \"animation\": {\n        \"type\": \"pop\",\n        \"popInFrames\": 6,\n        \"popOutFrames\": 4,\n        \"translateYPx\": 22\n      }\n    },\n    \"minimal\": {\n      \"preset\": \"minimal\",\n      \"font\": {\n        \"family\": \"system-ui, sans-serif\",\n        \"sizeRatio\": 0.0352,\n        \"weight\": 650,\n        \"color\": \"#FFFFFF\",\n        \"lineHeight\": 1.342,\n        \"letterSpacing\": 0\n      },\n      \"layout\": {\n        \"anchor\": \"bottom\",\n        \"align\": \"center\",\n        \"maxWidth\": 0.86,\n        \"paddingBottomRatio\": 0.075\n      },\n      \"background\": {\n        \"enabled\": false,\n        \"theme\": \"gray\",\n        \"shape\": \"rounded\",\n        \"color\": \"#000000\",\n        \"opacity\": 0.32,\n        \"radiusRatio\": 0.014,\n        \"paddingXRatio\": 0.028,\n        \"paddingYRatio\": 0.014\n      },\n      \"effects\": {\n        \"shadow\": {\n          \"strength\": \"none\",\n          \"color\": \"#000000\",\n          \"opacity\": 0,\n          \"offsetYRatio\": 0,\n          \"blurRatio\": 0\n        },\n        \"stroke\": {\n          \"strength\": \"none\",\n          \"color\": \"#000000\",\n          \"widthRatio\": 0\n        }\n      },\n      \"stroke\": {\n        \"enabled\": false,\n        \"theme\": \"black\",\n        \"color\": \"#000000\",\n        \"opacity\": 0.85,\n        \"widthRatio\": 0\n      },\n      \"wordHighlight\": {\n        \"enabled\": false,\n        \"mode\": \"none\",\n        \"activeColor\": \"#FFFFFF\",\n        \"activeScale\": 1,\n        \"upcomingOpacity\": 1,\n        \"backgroundColor\": \"#FFFFFF\",\n        \"backgroundOpacity\": 0,\n        \"backgroundRadiusRatio\": 0.01\n      },\n      \"animation\": {\n        \""},{"path":"reference/caption-feedback-mapping.md","content":"# Caption Feedback Mapping\n\nThis skill is agent-facing. In the canonical workflow, users choose a style and\napprove or revise source-backed previews by copying structured summaries from the\nbound HTML review pages. Standalone compatibility accepts an exact gallery\ncombination ID or `skip`, and accepts `approve` only after source-backed preview\nevidence exists. Historical non-English aliases remain accepted silently but are\nnot user instructions. The agent maps only recorded user feedback to optional JSON\noverrides accepted by `scripts/generate_caption_project.mjs`.\n\n## Safe Edit Points\n\n- Use `scripts/caption-styles.json` as the source of official preset and theme names.\n- Record the exact gallery response with `scripts/caption_interaction.mjs select`.\n- Pass `--interaction-state` to the generator. The generator reads the selected\n  preset, themes, and Karaoke value from that state and rejects conflicting flags.\n- Put only requested property overrides in a JSON file passed with `--overrides`.\n- Edit caption cue JSON only when correcting subtitle text or timing data.\n\nThe user may skip gallery selection, which explicitly chooses `clean`. The\nsource-backed preview confirmation cannot be skipped. Canonical full rendering\nrequires the exact copied approval summary; standalone compatibility requires the\nexact public response `approve`.\n\n## Official Style Vocabulary\n\nOfficial presets:\n\n- `clean`\n- `minimal`\n- `social-bold`\n- `pill`\n- `boxed`\n- `stroked`\n- `shorts`\n\nBackground themes:\n\n- `gray`\n- `yellow`\n- `blue`\n- `pink`\n- `green`\n\nStroke themes:\n\n- `black`\n- `yellow`\n- `blue`\n- `pink`\n- `green`\n\nShorts highlight colors:\n\n- `yellow` -> `#F8F54F`\n- `green` -> `#21D32E`\n- `orange` -> `#F8BD6D`\n- `black` -> `#000000`\n- `blue` -> `#2563EB`\n- `pink` -> `#DB2777`\n\nKaraoke is an option, not a preset. Preview ids such as `pill-yellow`,\n`boxed-green`, `stroked-blue`, `shorts-yellow`, and `social-bold-karaoke` are\npreset/config combinations, not official presets.\n\n## Feedback Rules\n\nFont and visual intensity:\n\n- \"make the text bigger\" -> increase `font.sizeRatio`\n- \"make the text smaller\" -> decrease `font.sizeRatio`\n- \"cleaner\" -> move toward `clean` or `minimal`\n- \"more subtle\" -> move toward `minimal`\n- \"more eye-catching\" -> move toward `social-bold`\n- \"more like TikTok\", \"big short-video captions\" -> move toward `social-bold`\n- \"vertical shorts style\", \"Reels style\", \"YouTube Shorts style\" -> use `shorts`\n\nBackground:\n\n- \"no background\" -> `background.enabled = false`\n- \"add a background\" -> choose `pill` or `boxed`\n- \"rounded background\", \"capsule background\" -> `preset = \"pill\"` or `background.shape = \"pill\"`\n- \"small rounded background\", \"rectangle background bar\" -> `preset = \"boxed\"` or `background.shape = \"rounded\"`\n- \"make the background more transparent\" -> decrease `background.opacity`\n- \"make the background stronger\" -> increase `background.opacity`\n- \"gray background\" -> `background.theme = \"gray\"`\n- \"yellow background\" -> `background.theme = \"y"},{"path":"reference/caption-rules.md","content":"# Caption Rules and Data Shape\n\n## How build_captions.py chunks words into cues\n\nA new cue is closed when any of these conditions is met:\n\n- the word ends a sentence: `.`, `?`, `!`, or ellipsis\n- adding the next word would exceed the line budget: `--max-chars` x `--max-lines`\n- adding the next word would exceed `--max-dur` seconds on screen\n- there is a speech gap of at least `--gap` seconds before the next word\n\nEach cue keeps per-word timings, so the renderer can highlight the word currently\nbeing spoken. `lines[]` is the cue wrapped to `--max-chars` for display and for\nthe SRT file.\n\nUseful defaults:\n\n```text\n--max-chars 42 --max-lines 2 --max-dur 6 --gap 0.6\n```\n\nFor fast-cut vertical/social captions, try shorter cues:\n\n```text\n--max-chars 24 --max-dur 3\n```\n\n## captions.json schema\n\n```jsonc\n[\n  {\n    \"index\": 1,\n    \"start\": 1.0,\n    \"end\": 3.4,\n    \"text\": \"Hey, it's Thariq from the Claude Code team.\",\n    \"lines\": [\"Hey, it's Thariq from the Claude\", \"Code team.\"],\n    \"words\": [\n      { \"word\": \"Hey,\", \"start\": 1.0, \"end\": 1.2 }\n    ]\n  }\n]\n```\n\n`captions.srt` is the same content as portable SubRip. Use it as a sanity read\nor to hand to a player or another tool.\n\n## Renderer Contract\n\n- Treat `captions.json` as renderer-neutral cue data.\n- Preserve `start`, `end`, `text`, `lines[]`, and per-word timings when passing cues\n  to a renderer.\n- Karaoke is a true/false option, not a preset. Use `karaoke: true` for per-word\n  highlight and `karaoke: false` for plain blocks.\n- Keep style and compositing implementation outside this data-shaping contract.\n\n## Expressive Planning\n\nStandard is the default. A Standard canonical plan needs no `presentation` field,\nand the legacy top-level cue array remains Standard-compatible. Expressive must be\nexplicitly requested with `--presentation-mode expressive`; it is a presentation\nmode, not a preset.\n\n`build_captions.py` creates only the base cues and a draft planning shell. It does\nnot infer placement. The Agent reads the transcript,\navailable understanding artifacts, timeline, generated cues, and necessary real\nvisual evidence, then fills the complete plan once for the whole program.\n\nAgent layout rules:\n\n1. Default to `bottom-standard`.\n2. Keep ordinary explanatory sentences in a continuous bottom layout when possible.\n3. Use `center-emphasis` for short keywords, numbers, or conclusions when emphasis is justified.\n4. Do not mechanically alternate between bottom and center.\n5. Do not change position in the middle of a sentence or cue.\n6. Prefer merging adjacent ordinary cues into one stable layout beat.\n7. Avoid repeated consecutive `center-emphasis` beats.\n8. Every position change must have a semantic reason recorded in the beat rationale.\n9. When uncertain, fall back to `bottom-standard`.\n\nA layout beat covers one or more complete, contiguous cues. Beat IDs must be unique;\nbeats must follow time and cue order, must not overlap, and must not start or end\ninside a cue. A completed Expressive plan covers e"}],"languages":[],"docsSourceLabel":"CLAWHUB","editorialOverview":null,"editorialQuality":{"score":100,"threshold":65,"status":"thin","wordCount":2142,"uniquenessScore":43,"reasons":["uniqueness-below-45"]}},"media":{"evidence":{"source":"no-media","verified":false,"confidence":"low","updatedAt":"2026-10-11T09:51:19.684Z","emptyReason":"No screenshots, media assets, or demo links are available."},"primaryImageUrl":null,"mediaAssetCount":0,"assets":[],"demoUrl":null},"ownerResources":{"evidence":{"source":"unclaimed","verified":false,"confidence":"low","updatedAt":"2026-10-11T09:51:19.684Z","emptyReason":"This page has not been claimed by the agent owner."},"hasCustomPage":false,"customPageUpdatedAt":null,"customLinks":[],"structuredLinks":{"docsUrl":null,"demoUrl":null,"supportUrl":null,"pricingUrl":null,"statusUrl":null},"customPage":null},"relatedAgents":{"evidence":{"source":"protocol-neighbors","verified":false,"confidence":"medium","updatedAt":"2026-10-11T14:15:40.457Z","emptyReason":null},"items":[{"id":"8ebccd8e-3863-4187-8355-c3f14e1f9edf","entityType":"agent","canonicalPath":"/agent/iofficeai-aionui","slug":"iofficeai-aionui","name":"AionUi","description":"Free, local, open-source 24/7 Cowork app and OpenClaw for Gemini CLI, Claude Code, Codex, OpenCode, Qwen Code, Goose CLI, Auggie, and more | 🌟 Star if you like it!","url":"https://github.com/iOfficeAI/AionUi","homepage":"https://www.aionui.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-10-09T19:11:12.944Z","createdAt":"2026-02-25T03:38:16.584Z","downloads":null},{"id":"b917f68a-ebff-438e-84f8-3f4b2494c0bc","entityType":"agent","canonicalPath":"/agent/activepieces-activepieces","slug":"activepieces-activepieces","name":"activepieces","description":"AI Agents & MCPs & AI Workflow Automation • (~400 MCP servers for AI agents) • AI Automation / AI Agent with MCPs • AI Workflows & AI Agents • MCPs for AI Agents","url":"https://github.com/activepieces/activepieces","homepage":"https://www.activepieces.com","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-15T02:22:12.426Z","createdAt":"2026-02-25T03:38:12.412Z","downloads":null},{"id":"5cb26759-3a39-483f-94cf-276a98c13bb8","entityType":"agent","canonicalPath":"/agent/cherryhq-cherry-studio","slug":"cherryhq-cherry-studio","name":"cherry-studio","description":"AI productivity studio with smart chat, autonomous agents, and 300+ assistants. Unified access to frontier LLMs","url":"https://github.com/CherryHQ/cherry-studio","homepage":"https://cherry-ai.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-11T14:38:40.986Z","createdAt":"2026-02-25T03:38:19.379Z","downloads":null},{"id":"6f6582d0-5d76-4f0f-b81d-86520247950b","entityType":"agent","canonicalPath":"/agent/copilotkit-copilotkit","slug":"copilotkit-copilotkit","name":"CopilotKit","description":"The Frontend for Agents & Generative UI. React + Angular","url":"https://github.com/CopilotKit/CopilotKit","homepage":"https://docs.copilotkit.ai","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-03-25T09:50:57.846Z","createdAt":"2026-02-25T03:39:14.617Z","downloads":null}],"links":{"hub":"/agent","source":"/agent/source/clawhub","protocols":[{"label":"OpenClaw","href":"/agent/protocol/openclew"}]}}}