chore: import upstream snapshot with attribution
regression / regression-shards (style-16-prod style-9-prod style-17-prod iframe-render-compat variables-prod mp4-h265-sdr, shard-4) (push) Has been cancelled
regression / regression-shards (style-4-prod style-11-prod style-2-prod animejs-adapter typegpu-adapter parallel-capture-regression, shard-5) (push) Has been cancelled
regression / regression-shards (style-7-prod style-8-prod style-10-prod css-spinner-render-compat webm-transparency mp4-h264-sdr webm-vp9, shard-3) (push) Has been cancelled
regression / regression-shards (sub-composition-video style-18-prod raf-ball-render-compat font-variant-numeric sub-comp-t0 sub-comp-id-selector, shard-7) (push) Has been cancelled
Windows render verification / Detect changes (push) Has been cancelled
Windows render verification / Preflight (lint + format) (push) Has been cancelled
Windows render verification / Render on windows-latest (push) Has been cancelled
Windows render verification / Tests on windows-latest (push) Has been cancelled
CI / Detect changes (push) Has been cancelled
CI / Build (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Fallow audit (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / Typecheck (push) Has been cancelled
CI / Test (push) Has been cancelled
CI / Producer: integration tests (push) Has been cancelled
CI / Producer: unit tests (push) Has been cancelled
CI / File size check (push) Has been cancelled
CI / Test: skills (push) Has been cancelled
CI / Skills: manifest in sync (push) Has been cancelled
CI / CLI: npx shim (macos-latest) (push) Has been cancelled
CI / CLI: npx shim (ubuntu-latest) (push) Has been cancelled
CI / CLI: npx shim (windows-latest) (push) Has been cancelled
CI / SDK: unit + contract + smoke (push) Has been cancelled
CI / Test: runtime contract (push) Has been cancelled
CI / Studio: load smoke (push) Has been cancelled
CI / Smoke: global install (push) Has been cancelled
CI / CLI smoke (required) (push) Has been cancelled
CI / Semantic PR title (push) Has been cancelled
Player perf / Detect changes (push) Has been cancelled
Player perf / Preflight (lint + format) (push) Has been cancelled
Player perf / player-perf (push) Has been cancelled
Player perf / Perf: drift (push) Has been cancelled
Player perf / Perf: fps (push) Has been cancelled
Player perf / Perf: parity (push) Has been cancelled
Player perf / Perf: scrub (push) Has been cancelled
Player perf / Perf: load (push) Has been cancelled
preview-regression / Detect changes (push) Has been cancelled
preview-regression / Preflight (lint + format) (push) Has been cancelled
preview-regression / Preview parity (push) Has been cancelled
preview-regression / preview-regression (push) Has been cancelled
regression / regression (push) Has been cancelled
regression / Detect changes (push) Has been cancelled
regression / Preflight (lint + format) (push) Has been cancelled
regression / regression-shards (hdr-regression style-5-prod style-3-prod mov-prores, shard-1) (push) Has been cancelled
regression / regression-shards (overlay-montage-prod style-12-prod chat missing-host-comp-id png-sequence portrait-edge-bleed, shard-6) (push) Has been cancelled
regression / regression-shards (style-13-prod style-6-prod vignelli-stacking gsap-letters-render-compat audio-mux-parity, shard-8) (push) Has been cancelled
regression / regression-shards (style-15-prod hdr-hlg-regression style-1-prod many-cuts vfr-screen-recording render-symlinked-assets, shard-2) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Docs / Validate docs (push) Has been cancelled
Sync skills to ClawHub / Publish changed skills (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 12:58:35 +08:00
commit 85453da49f
4031 changed files with 710987 additions and 0 deletions
@@ -0,0 +1,284 @@
---
version: alpha
name: Broadside — Frame (video / frame layer)
description: >
Video-first companion to Broadside's design.md. The unit is the frame (1920×1080). Atoms are
identical and sacred — the two-register surface system (dark ink-black / fire-orange), massive
Barlow in lowercase weight 900 treated as graphic primitive, IBM Plex Mono chrome (uppercase,
0.14em), the single fire-orange accent, the flat plane, and 1px hairline dividers. Composition +
frame scale rewritten for the frame. Motion out of scope.
unit: the frame — 1920×1080 primary; 9:16 and 1:1 documented
principle: atoms are sacred · composition is free · numbers come from the script
colors:
ink-black: "#111111"
ink-black-alt: "#1A1A18"
fire-orange: "#E85D26"
cream: "#F0ECE5"
cream-muted: "#888880"
cream-hint: "#505048"
border-dark: "#282826"
ink-on-orange-muted: "rgba(17,17,17,0.75)"
ink-on-orange-hint: "rgba(17,17,17,0.55)"
ink-on-orange-faint: "rgba(17,17,17,0.40)"
ink-on-orange-border: "rgba(17,17,17,0.20)"
typography:
# — reading ramp —
body: { fontFamily: "Barlow", cqw: 1.2, weight: 400, lineHeight: 1.6 }
lead: { fontFamily: "Barlow", cqw: 1.6, weight: 400, lineHeight: 1.5 }
caption: { fontFamily: "Barlow", cqw: 0.9, weight: 400, lineHeight: 1.5 }
label: { fontFamily: "IBM Plex Mono", cqw: 0.72, weight: 500, tracking: "0.14em", upper: true }
# — display / hero ramp (Barlow, lowercase, negative tracking) —
h3: { fontFamily: "Barlow", cqw: 2.8, weight: 600, lineHeight: 1.2, lower: true }
quote-text:{ fontFamily: "Barlow", cqw: 3.8, weight: 700, lineHeight: 1.15, tracking: "-0.02em", lower: true }
h2: { fontFamily: "Barlow", cqw: 4.5, weight: 700, lineHeight: 1.1, tracking: "-0.02em", lower: true }
stat-value:{ fontFamily: "Barlow", cqw: 5.5, weight: 900, lineHeight: 1.0, tracking: "-0.04em" }
h1: { fontFamily: "Barlow", cqw: 7.5, weight: 800, lineHeight: 0.9, tracking: "-0.03em", lower: true }
fadelist-item:{ fontFamily: "Barlow", cqw: 7.5, weight: 900, lineHeight: 1.0, tracking: "-0.03em", lower: true }
quote-mark:{ fontFamily: "Barlow", cqw: 10.0, weight: 900, lineHeight: 0.6 }
fadelist-title:{ fontFamily: "Barlow", cqw: 10.5, weight: 900, lineHeight: 0.9, tracking: "-0.04em", lower: true }
display: { fontFamily: "Barlow", cqw: 13.0, weight: 900, lineHeight: 0.88, tracking: "-0.04em", lower: true }
spacing:
pad-x: "5.5cqw"
pad-y: "5.5cqw"
gap-lg: "3.5cqw"
gap-md: "2cqw"
gap-sm: "1cqw"
components:
registers:
dark: "ground {colors.ink-black}, text {colors.cream}, accent {colors.fire-orange}"
orange: "ground {colors.fire-orange}, text {colors.ink-black}"
description: "Two surfaces only — no cream/paper register. One register per frame."
slide-chrome:
rule: "1px solid {colors.border-dark} (dark) / 20% ink (orange)"
placement: "top + bottom bars (label left, number right)"
description: "SUPPRESSED on cover/chapter/statement/quote/end — declarative frames let type fill the field."
kicker:
typography: "{typography.label}"
color: "{colors.fire-orange} (dark) / 55% ink (orange)"
description: "Uppercase mono eyebrow."
rule:
backgroundColor: "{colors.fire-orange} (dark) / {colors.ink-black} (orange)"
size: "36×2px"
description: "Stub accent bar — the system's only ornament."
broadside-num:
typography: "{typography.label}"
placement: "top-left of orange cover/chapter, low opacity"
description: "Mono catalogue numeral."
stat-card:
borderTop: "1px solid {colors.border-dark}"
typography: "{typography.stat-value} (orange on dark / ink on orange) + {typography.body} + {typography.label}"
description: "Top-border-only block, no other borders."
bullet:
marker: "orange `/` mono via ::before"
typography: "{typography.lead}"
description: "Capped at THREE items."
bar-track:
borderLeft: "1px solid {colors.border-dark}"
bars: "{colors.cream-hint}, one .accent {colors.fire-orange}"
typography: "{typography.label} axis"
description: "Vertical bar chart, left axis only."
compare-panel:
layout: "two equal panels split by a 1px vertical rule"
payoff: "right panel may fill {colors.fire-orange}"
description: "Before/after."
fadelist:
typography: "{typography.fadelist-item} ×3 at opacity 1.0/0.5/0.22 + {typography.fadelist-title}"
description: "Three stacked words + one oversized title opposite."
---
# Broadside — Frame (video / frame layer)
## Overview
Broadside at frame scale is a **protest-poster system where type is so large it stops reading as
text and becomes graphic primitive.** Barlow `display` at 13cqw puts a single lowercase word
nearly across the frame. The system runs in **two registers**: a dark ink-black ground with cream
text for documentation, and a fire-orange ground with dark ink for declaration. Fire-orange is the
_only_ color — accent on dark, environment on orange. The plane is flat; hierarchy is weight, size,
and 1px hairlines.
**Barlow** carries every text role from display to body — expressive range from weight (400900)
and size, not face contrast. **IBM Plex Mono** is chrome only (numbers, kickers, tags, axis labels,
the `/` bullet marker), always uppercase and tracked. Display is **lowercase** — the system's most
distinctive single decision, a deliberate inversion of the brutalist norm.
**Key characteristics at frame scale:**
- **Two registers** — dark (cream text) / orange (ink text). No cream/paper register.
- **Massive lowercase Barlow 900**, negative-tracked, as graphic primitive (display 13cqw).
- **Fire-orange is the only color** — accent on dark, full environment on orange.
- **IBM Plex Mono chrome** — uppercase, 0.14em; the `/` bullet marker; mono catalogue numbers.
- **Flat plane** — no shadow, no radius (save nav dots), no gradient; 1px hairlines carry structure.
- **Low density** — one statement per frame, bullets capped at three, chrome suppressed on declarative frames.
## The Frame
### Frame Craft Bar
Three eyeball tests gate every frame before any structural check:
- **Squint** — exactly **one display moment dominates** at 36× everything else; nothing competes.
- **Silence** — declarative frames read **4555% empty**; the **stat grid is the one dense exception**.
- **Restraint** — **one register per frame**; **fire-orange is the only color** (accent on dark, environment on orange); one display moment; bullets capped at three.
- **Reference** — aim at **broadside printing / a SPACE10 report / a Wim Crouwel grid with one loud color**; failure looks like a **multi-accent corporate slide deck**.
- **Primary:** 1920×1080 (16:9). Type authored in **`cqw`** (`px ÷ 1920 × 100 = cqw`; or carry the source's `vw` 1:1).
- **Vertical:** 1080×1920 (9:16). **Square:** 1080×1080 (1:1).
- **Safe area:** `pad-x`/`pad-y` 5.5cqw — deliberately tight so the massive type crowds the frame edge.
**The container law (load-bearing).** Every frame ground sets `container-type: size`; ALL
frame-relative units are `cqw`/`cqh` against it — never `vw` (a `vw`-sized frame inflates when not
full-screen). 1px hairlines stay 1px.
## Colors
Tokens identical to the source, in two registers. **Dark:** `{colors.ink-black}` ground,
`{colors.cream}` text, `{colors.fire-orange}` accent (kickers, accent stat, bullet `/`, lead bar,
quote mark, rule stub). **Orange:** `{colors.fire-orange}` ground, `{colors.ink-black}` headlines +
body, with the dark-ink overlays (75/55/40/20%) as the muted tones. Choose one register per frame
and commit. **No second accent color** — on orange, emphasis is weight/opacity on the ink, never a
new hue. Cream text on orange does not exist (ink-on-fire is absolute).
## Typography
Two ramps. The **reading ramp** (Barlow body 1.2cqw, mono label 0.72cqw) carries copy + chrome; the
**display ramp** (Barlow `h2` 4.5cqw → `display` 13cqw, weight 700900) carries every statement.
- **Legibility floor:** any load-bearing line ≥ **1.4cqw**; mono labels are chrome only.
- **Fit-to-measure:** size the headline to its length. Cap the block at **≤ 78cqw**; ≤2 words → `display`; 34 → `h1`; 5+ → `h2`. Broadside packs only ONE display moment per frame.
- **Barlow display is lowercase, weight 700900, negative-tracked** (0.04em largest, 0.02em h2). **Mono chrome is uppercase, 0.1em+.** No italic, no underline, no uppercase display.
## Depth & Surface
Flat plane, the only technique. Hierarchy from:
- **Weight + size contrast** — the dominant signal (900 lowercase display).
- **1px hairlines** — chrome bars, stat-card top, compare divider, bar-track left, chart baseline.
- **Color shift** — orange on ink, ink on cream, cream-muted on cream.
- **Negative space** — generous, intentional empty regions.
**Ceiling:** no box-shadow, no elevation, no rounded surface (save nav dots), no gradient ground.
## Shapes
- **0 radius everywhere** except nav dots (50%). Cards, panels, tags, stat blocks, bars — sharp rectangles.
## Components
- **registers** — the two-surface system. **slide-chrome** — optional hairline bars, suppressed on declarative frames.
- **kicker** (mono eyebrow) / **rule** (36×2 stub) / **broadside-num** (catalogue mark) — the chrome ornament set.
- **stat-card** (top-border only) / **bullet** (orange `/`, max 3) / **bar-track** (one accent bar) / **compare-panel** (orange payoff) / **fadelist** (1.0/0.5/0.22 stack).
## Frame Treatments
> Recipe: ground · register · composes · focal · chrome · accent · silence · Fixed/Free · density.
> One statement per frame; chrome suppressed on declarative frames.
### 1 · Cover (identity · move: massive type · ORANGE register · left)
**Ground** fire-orange. **Composes** broadside-num, rule, kicker, display, lead. **Focal** a 12 word
Barlow `display` (13cqw) lowercase in ink, left-anchored, over a small ink rule stub + mono kicker; a
Barlow lead line beneath in 75% ink. **Chrome** mono catalogue number top-left, mono meta top-right
(no chrome bars). **Accent** the ink itself is the pop on orange. **Silence** ~45%. **Fixed** ink-on-fire,
lowercase 900, flat. **Free** the word, kicker, lead. **Density** low.
### 2 · Statement (declarative · move: type IS composition · DARK register · left)
**Ground** ink-black. **Composes** kicker, display. **Focal** a 24 word Barlow `display`/`h1`
lowercase in cream, with ONE clause inked `{colors.fire-orange}`. **Chrome** mono kicker; no bars.
**Accent** the orange clause. **Silence** ~55%. **Fixed** lowercase 900, one orange clause, flat.
**Free** the statement, which clause is orange. **Density** low.
### 3 · Stat Grid (data · move: top-border cards · DARK · the dense frame)
**Ground** ink-black, chrome bars present. **Composes** slide-chrome, kicker, 3× stat-card. **Focal** a
row of three top-border-only stat-cards — big Barlow-900 numeral in `{colors.fire-orange}`, Barlow
label, mono note. **Chrome** top + bottom hairline bars (label + number). **Accent** the orange
numerals. **Silence** moderate — the density exception. **Fixed** top-border-only cards, orange
numerals, 1px hairlines. **Free** figures (from script), labels. **Density** dense-exception.
### 4 · Fadelist (narrative · move: opacity stack · DARK)
**Ground** ink-black. **Composes** fadelist (3 stacked Barlow-900 words at 1.0/0.5/0.22), fadelist-title.
**Focal** the three-stage word stack opposite an oversized display title in `{colors.fire-orange}`
(before/during/after). **Accent** the orange title. **Silence** moderate. **Fixed** the opacity
ladder, lowercase 900. **Free** the three words, the title. **Density** low-moderate.
### 5 · Pull Quote (quote · move: oversized mark · DARK · left)
**Ground** ink-black, chrome suppressed. **Composes** quote-mark, quote-text, attribution. **Focal** a
Barlow `quote-text` (700, lowercase) at ≤78cqw under an oversized fire-orange `quote-mark` (10cqw,
line-height 0.6). **Chrome** mono attribution (name + role). **Accent** the orange quote mark. **Silence**
~50%. **Fixed** orange mark, lowercase quote. **Free** quote, attribution. **Density** low.
### 6 · Compare (argument · move: split + orange payoff · DARK→ORANGE)
**Ground** ink-black left panel + fire-orange right (payoff) panel, 1px divider. **Composes**
compare-panel pair, kicker, h3. **Focal** two panels — left documents (cream on dark), right declares
(ink on orange). **Chrome** mono panel labels. **Accent** the orange payoff panel. **Silence** moderate.
**Fixed** ink-on-fire right panel, 1px divider, flat. **Free** the before/after content. **Density** standard.
## Composition Rules
### Do
- Set every Barlow display in **lowercase weight 900**, negative-tracked — the system's signature.
- Use **fire-orange as full environment** on declarative frames, the **lone accent** on dark.
- Keep chrome in **IBM Plex Mono uppercase, 0.14em**; use the `/` mono bullet marker.
- **Cap bullets at three; one statement per frame**; build hierarchy from weight, size, 1px hairlines.
- Suppress chrome bars on cover/chapter/statement/quote/end; let type fill the field.
- Lean left on most frames; the type IS the composition.
### Don't
- Never uppercase Barlow display; never add a second accent color.
- Never put cream text on orange (ink-on-fire is absolute); never a cream/paper register.
- No drop shadow, no rounded surface (save nav dots), no gradient ground.
- No serif companion; chrome is never Barlow.
- Don't pack two display moments into one frame; don't blow a long line edge-to-edge — step down.
## Aspect-Ratio Behavior
| Treatment | 16:9 | 9:16 | 1:1 |
| ---------- | -------------------------- | -------------------------- | ---------------- |
| Cover | word left, lead below | word top, lead below | centered word |
| Statement | display left | display stacked taller | display centered |
| Stat Grid | 3 across | 3 stacked | 2+1 |
| Fadelist | stack + title side-by-side | stack over title | stack over title |
| Pull Quote | mark + quote left | mark top, quote below | centered |
| Compare | side-by-side panels | stacked (dark over orange) | stacked |
`pad-x` holds tight on the short edge; re-step display so the one big line stays ≤78cqw and above the
1.4cqw floor. Mono chrome stays Latin/digit-only.
## Approved Entities
No real customers, logos, or vendors are defined in the source — render any such mark as a
placeholder (the dashed `img-placeholder` at 55cqh). The system supplies type and one color, not brands.
## Numerals & Claims (hard rule)
Never invent figures, percentages, dates, or counts at frame scale. Render slots as `— figure —`,
`{metric}`, `NN%`. Stat-card numerals and bar heights carry placeholders until the script supplies
them. Catalogue numbers (No. 01) are decorative chrome and may be sequential.
## Pre-Render Self-Audit
- **Squint** — exactly one display moment dominates; nothing competes.
- **Silence** — declarative frames ~4555% empty; only the stat grid runs dense.
- **Register** — one register per frame; ink-on-fire on orange, cream on dark; no second hue.
- **Type** — Barlow lowercase 900 negative-tracked, fit-to-measure; mono chrome uppercase 0.14em; ≥1.4cqw floor.
- **Depth** — 0 shadow, 0 radius (save nav dots); 1px hairlines only.
- **Bullets** — capped at three, orange `/` marker.
- **Fabrication** — every numeral traces to the script, else placeholder.
## Known Gaps
- **Motion intentionally out of scope.** frame.md specifies composition only; the source's 0.8s deck slide + per-element entry animations are deck mechanics.
- **Barlow + IBM Plex Mono via Google Fonts**; Noto Sans SC is the CJK fallback (the lowercase-display signal has no CJK equivalent — the two-register color system carries the identity, per the source).
- **9:16 / 1:1 are guidance**; verify the one big line stays ≤78cqw and above the floor per ratio.
- Bars, compare panels, and the dashed image placeholder are CSS-only; no external imagery is required.
@@ -0,0 +1,225 @@
<!--
Broadside — caption skin (preset-local source).
This is the preset's own lower-third karaoke caption look. product-launch-video's
Step 2 copies it into the project as caption-skin.html; captions.mjs then fills the
three reserved holes, wraps the fragment in a <template>, and writes
compositions/captions.html:
· the empty GROUPS array → the engine's scene-aware word groups
· the 0 duration + 0 width/height → real total + canvas size
· the empty data-brand-tokens style → :root tokens derived from the project's frame.md
Token contract — reference ONLY this fixed vocab (captions.mjs injects it from
frame.md, so the Step-2 brand overlay flows through; each var has a broadside literal
fallback for standalone preview):
--cap-ink · --cap-canvas · --cap-accent · --cap-accent-2 · --font-display ·
--font-body · --cap-band-top · --cap-band-height
Hooks (do not rename): .caption-group / .caption-word + states .is-active / .is-spoken,
root data-composition-id="captions", timeline at window.__timelines["captions"].
State changes use gsap.set({className}) (applied on the engine's frame-by-frame seek);
never tl.call() callbacks (seek does not fire them → wrong state on render).
Visual: a protest-poster lower third — an ink-black plate on a single 1px fire-orange
hairline, SHARP corners, NO shadow (broadside's plane is flat, never cast). Massive
lowercase Barlow 900 as graphic primitive, cream glyphs on ink. Upcoming words sit in
faint cream; the current word flips register — a hard flat fire-orange block with ink
reading on top (broadside's signature two-register flip, no blur), the brand's lone
accent; spoken words settle to solid cream, the orange block cleared.
-->
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<!-- captions.mjs injects the project's :root tokens here -->
<style data-brand-tokens></style>
<style>
#captions-root {
position: absolute;
inset: 0;
pointer-events: none;
}
.caption-layer {
position: absolute;
inset: 0;
z-index: 20;
pointer-events: none;
}
/* the keep-out band, from captionBand(H) — frame content stays above it */
.caption-stage {
position: absolute;
left: 0;
right: 0;
top: var(--cap-band-top, 900px);
height: var(--cap-band-height, 180px);
display: flex;
align-items: center;
justify-content: center;
}
.caption-group {
/* every group is absolutely stacked over the SAME band — exactly one shown at a
time via opacity. Must be absolute: in-flow groups would lay out side-by-side. */
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
}
/* THE ink-black plate — single 1px fire-orange hairline (broadside's lone accent),
SHARP corners (0 radius), NO shadow (the plane is flat) */
.caption-pill {
max-width: 80%;
padding: 18px 44px 22px;
background: var(--cap-ink, #111111);
border: 1px solid var(--cap-accent, #e85d26);
border-radius: 0;
}
.caption-line {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 0.04em 0.3em;
font-family: var(--font-display, "Barlow"), system-ui, sans-serif;
font-weight: 900;
font-size: clamp(38px, 4.2vw, 56px);
line-height: 1.02;
letter-spacing: -0.03em;
text-transform: lowercase;
}
/* upcoming — faint cream, no block (massive lowercase Barlow 900 as graphic primitive) */
.caption-word {
display: inline-block;
padding: 0 0.08em;
color: color-mix(in srgb, var(--cap-canvas, #f0ece5) 42%, var(--cap-ink, #111111));
}
/* current word — THE register flip: a hard flat fire-orange block (box-shadow spread =
a sharp rectangle w/o layout shift; no blur), ink reading on top — the lone accent */
.caption-word.is-active {
color: var(--cap-ink, #111111);
background: var(--cap-accent, #e85d26);
box-shadow: 0 0 0 0.06em var(--cap-accent, #e85d26);
}
/* already spoken — solid cream, the orange block cleared */
.caption-word.is-spoken {
color: var(--cap-canvas, #f0ece5);
background: transparent;
box-shadow: none;
}
/* optional per-word accents (dormant until a word-classer tags them) */
/* brand / key term — fire-orange ink, the cream-on-ink register's only inflection */
.caption-word.cap-brand {
color: var(--cap-accent, #e85d26);
}
/* numerals / data — the IBM Plex Mono chrome voice, uppercase + tracked, recessed */
.caption-word.cap-num {
font-family: var(--font-body, "IBM Plex Mono"), ui-monospace, monospace;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.06em;
color: color-mix(in srgb, var(--cap-canvas, #f0ece5) 72%, var(--cap-ink, #111111));
}
/* emphasis — a fire-orange hairline underline (no second hue; the lone accent again) */
.caption-word.cap-cta {
text-decoration: underline;
text-decoration-thickness: 2px;
text-decoration-color: var(--cap-accent-2, #e85d26);
text-underline-offset: 0.1em;
}
@media (max-aspect-ratio: 9/16) {
.caption-pill {
max-width: 92%;
}
.caption-line {
font-size: clamp(32px, 5.2vw, 46px);
}
}
</style>
<div
id="captions-root"
data-composition-id="captions"
data-timeline-locked
data-start="0"
data-duration="0"
data-fps="30"
data-width="0"
data-height="0"
>
<div class="caption-layer" aria-hidden="true">
<div id="caption-stage" class="caption-stage"></div>
</div>
</div>
<script>
// captions.mjs injects the engine's scene-aware groups + real duration here.
var GROUPS = [];
var DURATION = 0;
(function () {
var stage = document.getElementById("caption-stage");
// build DOM: one .caption-group per group, .caption-word per word
GROUPS.forEach(function (group, g) {
var groupEl = document.createElement("div");
groupEl.className = "caption-group";
groupEl.id = "caption-group-" + g;
var pill = document.createElement("div");
pill.className = "caption-pill";
var line = document.createElement("div");
line.className = "caption-line";
(group.words || []).forEach(function (w, i) {
var span = document.createElement("span");
span.className = "caption-word";
span.id = "caption-word-" + g + "-" + i;
span.textContent = String(w.text);
line.appendChild(span);
});
pill.appendChild(line);
groupEl.appendChild(pill);
stage.appendChild(groupEl);
});
window.__timelines = window.__timelines || {};
var tl = gsap.timeline({ paused: true });
GROUPS.forEach(function (group, g) {
var groupEl = document.getElementById("caption-group-" + g);
var words = group.words || [];
var next = GROUPS[g + 1];
var isLast = g === GROUPS.length - 1;
var start = Math.max(0, Number(group.start));
var end = isLast ? DURATION : Math.min(Number(next.start), Number(group.end) + 0.3);
if (end <= start) end = start + 0.01;
// group on/off — exactly one group visible at a time
tl.set(groupEl, { opacity: 1 }, start);
tl.set(groupEl, { opacity: 0 }, end);
// 3-state karaoke via className SETS — seek-safe (gsap.set applies on the
// engine's frame-by-frame seek; .call callbacks would NOT fire).
words.forEach(function (w, i) {
var el = document.getElementById("caption-word-" + g + "-" + i);
var at = Math.max(start, Number(w.start));
tl.set(el, { className: "caption-word" }, start); // upcoming (also resets on reverse-seek)
tl.set(el, { className: "caption-word is-active" }, at); // current
tl.fromTo(el, { scale: 0.92 }, { scale: 1, duration: 0.1, ease: "power3.out" }, at);
if (i + 1 < words.length) {
var nextAt = Math.max(start, Number(words[i + 1].start));
tl.set(el, { className: "caption-word is-spoken" }, nextAt); // demote when next activates
}
});
if (words.length) {
var lastEl = document.getElementById("caption-word-" + g + "-" + (words.length - 1));
var lastSpoken = Math.min(end, Number(words[words.length - 1].end) + 0.1);
tl.set(lastEl, { className: "caption-word is-spoken" }, lastSpoken);
}
});
// full-span anchor so the sub-comp timeline spans the whole video
tl.to({}, { duration: DURATION }, 0);
window.__timelines["captions"] = tl;
})();
</script>
File diff suppressed because it is too large Load Diff