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
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:
@@ -0,0 +1,289 @@
|
||||
---
|
||||
version: alpha
|
||||
name: Biennale Yellow — Frame (video / frame layer)
|
||||
description: >
|
||||
Video-first companion to Biennale Yellow's design.md. The unit is the frame (1920×1080). Atoms
|
||||
are identical and sacred — warm parchment grounds, a single deep indigo ink, solar yellow deployed
|
||||
as bloom / panel / tile underprint, Instrument Serif display + Archivo sans + JetBrains Mono data,
|
||||
1px hairline rules as the only border, atmospheric depth (no shadows), and the bottom-right
|
||||
pagenum. Composition + frame scale rewritten. Restraint is the rule; 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:
|
||||
paper: "#E9E5DB"
|
||||
paper-deep: "#DCD6C4"
|
||||
sun: "#F1EE2E"
|
||||
sun-soft: "#F8F39B"
|
||||
haze: "#F0DA7C"
|
||||
ink: "#1B2566"
|
||||
ember: "#E26B4A"
|
||||
|
||||
typography:
|
||||
# — reading + data ramp —
|
||||
body: { fontFamily: "Archivo", cqw: 0.85, weight: 400, lineHeight: 1.5, color: "ink" }
|
||||
body-lede:{ fontFamily: "Archivo", cqw: 0.95, weight: 400, lineHeight: 1.55 }
|
||||
micro-label:{ fontFamily: "Archivo", px: 13, weight: 600, tracking: "0.18em", upper: true }
|
||||
rail-label:{ fontFamily: "Archivo", px: 13, weight: 600, tracking: "0.32em", upper: true }
|
||||
mono-data:{ fontFamily: "JetBrains Mono", cqw: 0.73, weight: 400, tracking: "0.04em" }
|
||||
pagenum: { fontFamily: "JetBrains Mono", px: 13, weight: 400, tracking: "0.08em", opacity: 0.75 }
|
||||
# — display ramp (Instrument Serif 400, tight, negative tracking) —
|
||||
ledger-title:{ fontFamily: "Instrument Serif", cqw: 1.55, weight: 400, lineHeight: 1.15 }
|
||||
strand-title:{ fontFamily: "Instrument Serif", cqw: 1.7, weight: 400, lineHeight: 1.1 }
|
||||
headline-sm:{ fontFamily: "Instrument Serif", cqw: 2.9, weight: 400, lineHeight: 1.0 }
|
||||
headline:{ fontFamily: "Instrument Serif", cqw: 4.6, weight: 400, lineHeight: 1.06, tracking: "-0.005em" }
|
||||
date-rail:{ fontFamily: "Instrument Serif", cqw: 5.0, weight: 400, lineHeight: 0.96, tracking: "-0.005em" }
|
||||
display-it:{ fontFamily: "Instrument Serif", cqw: 7.0, weight: 400, italic: true, lineHeight: 1.04, tracking: "-0.005em" }
|
||||
numeral-md:{ fontFamily: "Instrument Serif", cqw: 7.5, weight: 400, lineHeight: 0.92, tracking: "-0.01em" }
|
||||
display:{ fontFamily: "Instrument Serif", cqw: 14.6, weight: 400, lineHeight: 0.86, tracking: "-0.018em" }
|
||||
numeral-jumbo:{ fontFamily: "Instrument Serif", cqw: 28.0, weight: 400, lineHeight: 0.84, tracking: "-0.04em" }
|
||||
|
||||
spacing:
|
||||
pad-edge: "4cqw"
|
||||
pad-region: "4.2cqw"
|
||||
gap-region: "2.5cqw"
|
||||
|
||||
components:
|
||||
sun-bloom:
|
||||
background: "radial gradient {colors.sun} core → {colors.sun-soft} → {colors.haze} → transparent on {colors.paper}"
|
||||
size: "42–70% of the frame, off-center or behind the focal element"
|
||||
description: "The primary depth layer. One per frame; a flat parchment frame reads as broken."
|
||||
ember-bloom:
|
||||
background: "radial {colors.ember} at 15–22% opacity"
|
||||
placement: "corner opposite the sun-bloom"
|
||||
description: "Subordinate counter-temperature balance; never dominant."
|
||||
block-tile:
|
||||
backgroundColor: "{colors.sun} at 40–70% opacity"
|
||||
layout: "rectangles on an 8×4 grid behind cover/colophon"
|
||||
description: "A layered poster underprint."
|
||||
yellow-panel:
|
||||
backgroundColor: "{colors.sun}"
|
||||
textColor: "{colors.ink}"
|
||||
rounded: "0"
|
||||
border: "none (meets paper directly)"
|
||||
description: "Full-bleed column/third — the strongest color statement, ink on top."
|
||||
hairline-rule:
|
||||
rule: "1px solid {colors.ink} (soft variant: {colors.ink} at 18–20%)"
|
||||
description: "The ONLY border — header underlines, footer tops, ledger separators. No thicker rule exists."
|
||||
strand-row:
|
||||
borderBottom: "1px {colors.ink} 18–20%"
|
||||
typography: "{typography.strand-title} + {typography.body}"
|
||||
description: "Serif numeral + serif title + sans body. Numbered editorial lists."
|
||||
ledger-row:
|
||||
borderBottom: "1px {colors.ink} 18–20%"
|
||||
typography: "{typography.mono-date} · {typography.ledger-title} · {typography.body} · {typography.mono-data}"
|
||||
description: "4-col tabular — mono date · serif title · sans venue · mono duration (right)."
|
||||
footer-band:
|
||||
borderTop: "1px solid {colors.ink} per cell"
|
||||
typography: "{typography.micro-label} + {typography.body-sm}"
|
||||
description: "4-column metadata strip at the foot of cover/colophon."
|
||||
vertical-rail:
|
||||
typography: "{typography.rail-label}"
|
||||
transform: "rotated up the left edge"
|
||||
description: "Section marker on chapter/divider frames."
|
||||
pagenum:
|
||||
typography: "{typography.pagenum}"
|
||||
color: "{colors.ink} at 75%"
|
||||
placement: "bottom-right"
|
||||
description: "The only persistent chrome."
|
||||
---
|
||||
|
||||
# Biennale Yellow — Frame (video / frame layer)
|
||||
|
||||
## Overview
|
||||
|
||||
Biennale Yellow at frame scale is a **literary-editorial system** in the register of an art
|
||||
biennale catalogue: warm parchment, a single deep indigo ink, and solar yellow as atmosphere. No
|
||||
cards, no buttons, no shadows, no rounded corners — the structural vocabulary is just **paper, ink,
|
||||
and yellow.** Depth is delivered by soft radial **sun blooms**, not elevation.
|
||||
|
||||
The voice is three faces in rigid roles: **Instrument Serif** (weight 400, tight line-height,
|
||||
negative tracking) carries every display, numeral, and quote from 40px to 720px+; **Archivo** carries
|
||||
body and the wide-tracked uppercase micro-label; **JetBrains Mono** carries every date, figure, and
|
||||
the pagenum. Text is **always ink** — contrast comes from size and weight, never color. The mood
|
||||
sits between a folded museum brochure and a slow-reading literary quarterly: confident, atmospheric,
|
||||
deeply restrained.
|
||||
|
||||
**Key characteristics at frame scale:**
|
||||
|
||||
- **Warm parchment ground** on every frame; never white, never gray.
|
||||
- **Single ink** (`{colors.ink}`) for all type and all rules; **solar yellow** as bloom / panel / tile.
|
||||
- **Instrument Serif 400** display (tight, negative-tracked); **Archivo** body + micro-labels; **JetBrains Mono** data.
|
||||
- **1px hairline rules** are the only border — no thicker weight exists; **no shadows, no rounded corners**.
|
||||
- **Sun bloom** is the primary depth layer on every frame; an ember counter-bloom adds warm-cool tension.
|
||||
- **Editorial-restrained** — sparse reads as elegant; crowding breaks the catalogue feel.
|
||||
|
||||
## The Frame
|
||||
|
||||
### Frame Craft Bar
|
||||
|
||||
Three eyeball tests gate every frame before any structural check:
|
||||
|
||||
- **Squint** — one Instrument Serif moment dominates at 3–6× its neighbor; the sun bloom centers the eye.
|
||||
- **Silence** — frames read **55–60% empty**; the **ledger is the one dense exception** (density via quiet hairline repetition, not richness).
|
||||
- **Restraint** — **one ink color** for all type and rules; **one sun bloom** per frame (+ optional subordinate ember); never invert (no yellow text on ink).
|
||||
- **Reference** — aim at an **art-biennale catalogue / slow exhibition poster / literary quarterly**; failure looks like a **flat CMS template** (no bloom) or a **bordered-card deck**.
|
||||
|
||||
- **Primary:** 1920×1080 (16:9). Display authored in **`cqw`** (`px ÷ 1920 × 100 = cqw`).
|
||||
- **Vertical:** 1080×1920 (9:16). **Square:** 1080×1080 (1:1).
|
||||
- **Safe area:** `pad-edge` 4cqw (40–76px equivalent) — the elegance depends on edge negative space; only blooms, tiles, and panels bleed.
|
||||
|
||||
**The container law (load-bearing).** Every frame ground sets `container-type: size`; ALL
|
||||
frame-relative units are `cqw`/`cqh` against it — never `vw`. Hairlines stay 1px; bloom sizes scale
|
||||
as `%` of the frame.
|
||||
|
||||
## Colors
|
||||
|
||||
Tokens identical to the source. `{colors.paper}` is the universal ground; `{colors.ink}` (deep
|
||||
indigo navy) is **every line of type and every rule** — there is no secondary text color.
|
||||
`{colors.sun}` deploys three ways: the **bloom** (radial atmosphere), the **yellow-panel**
|
||||
(full-bleed poster fill, ink on top), and the **block-tile** underprint. `{colors.sun-soft}`/`haze`
|
||||
are bloom mid/outer stops. `{colors.ember}` appears **only** as a 15–22% counter-bloom — never a
|
||||
fill, never text. **The system never inverts** — ink on sun is correct; yellow text on ink does not exist.
|
||||
|
||||
## Typography
|
||||
|
||||
Two ramps. The **reading/data ramp** (Archivo body 0.85cqw ink, micro-labels in px, JetBrains Mono
|
||||
data) carries copy + chrome; the **display ramp** (Instrument Serif `ledger-title` 1.55cqw →
|
||||
`numeral-jumbo` 28cqw) carries every headline, numeral, and quote.
|
||||
|
||||
- **Legibility floor:** any load-bearing line ≥ **1.4cqw**; mono/labels in px are chrome only.
|
||||
- **Fit-to-measure:** size the headline to its length. Cap the block at **≤ 78cqw**; ≤3 words → `display`; 4–6 → `headline`; 7+ → `headline-sm`. The jumbo numeral is a divider's whole point — don't shrink it.
|
||||
- **Instrument Serif is weight 400 only** (its contrast is the weight signal), tight line-height (0.84–1.06), negative tracking; **micro-labels uppercase Archivo 600, ≥0.16em**; **mono for all numerals/dates**. No bold serif, no second text color, no mono in body/display.
|
||||
|
||||
## Depth & Surface
|
||||
|
||||
Atmospheric, not structural. Depth from:
|
||||
|
||||
- **Sun bloom** — the primary layer: a layered radial (sun 70–95% core → sun-soft → haze 18–22% → paper 0%), 42–70% of the frame. One per frame.
|
||||
- **Ember bloom** — a 15–22% peach counter-bloom in the opposite corner; always subordinate.
|
||||
- **Block-tile underprint** — translucent yellow rectangles on an 8×4 grid (cover/colophon).
|
||||
- **Yellow panel** — the one "hard" color statement: a flooded column/third, ink on top.
|
||||
- **Hairline rules** — 1px ink for structural separation (soft 18–20% variant for dense rows).
|
||||
|
||||
**Ceiling:** zero box-shadow, zero text-shadow, zero rounded corner, no border thicker than 1px.
|
||||
|
||||
## Shapes
|
||||
|
||||
- **0 radius on everything** — strict rectangles. Blooms are edgeless (they fade into paper).
|
||||
|
||||
## Components
|
||||
|
||||
- **sun-bloom / ember-bloom / block-tile** — the atmospheric depth set. **yellow-panel** — the poster-fill statement.
|
||||
- **hairline-rule** — the only border (1px ink; soft variant for dense rows).
|
||||
- **strand-row / ledger-row / footer-band** — the editorial list, tabular calendar, and metadata strip.
|
||||
- **vertical-rail** — rotated chapter marker. **pagenum** — the bottom-right mono chrome.
|
||||
|
||||
## Frame Treatments
|
||||
|
||||
> Recipe: ground · container · composes · focal · chrome · accent · silence · Fixed/Free · density.
|
||||
> A sun bloom + bottom-right pagenum on every frame; sparse is the default.
|
||||
|
||||
### 1 · Cover (identity · move: display + sun bloom · left)
|
||||
|
||||
**Ground** paper + a large sun-bloom (left-of-center) + an ember counter-bloom (opposite corner).
|
||||
**Composes** micro-label, display, date-rail, footer-band, pagenum. **Focal** a 2-line Instrument
|
||||
Serif `display` (italic key word) in ink, left, under a micro-label. **Chrome** a serif date-rail
|
||||
top-right; a 4-column footer-band at the foot. **Accent** the sun bloom + yellow. **Silence** the
|
||||
bloom holds the open space. **Fixed** ink type, one bloom, 1px footer rules, no shadow. **Free**
|
||||
title, date, footer cells. **Density** low.
|
||||
|
||||
### 2 · Chapter Divider (section · move: jumbo numeral · vertical rail)
|
||||
|
||||
**Ground** paper + a corner-anchored sun-bloom. **Composes** vertical-rail, numeral-jumbo,
|
||||
headline-sm. **Focal** a single huge Instrument Serif `numeral-jumbo` (≈28cqw) dominating, with a
|
||||
serif title beneath. **Chrome** a rotated vertical-rail label up the left edge; pagenum. **Accent**
|
||||
the bloom behind the numeral. **Silence** ~60%. **Fixed** serif 400, jumbo numeral, rail label.
|
||||
**Free** the ordinal, title, rail text. **Density** low.
|
||||
|
||||
### 3 · Ledger (catalog · move: hairline tabular rows · the dense frame)
|
||||
|
||||
**Ground** paper (bloom optional, subtle). **Composes** headline-sm + micro-label topbar, ledger-rows.
|
||||
**Focal** a 4-column tabular calendar — mono date · serif title · sans venue · mono duration —
|
||||
separated by hairline-soft rules under a 1px ink header rule. **Chrome** pagenum. **Accent** none —
|
||||
density through quiet repetition, not color. **Silence** tight — the density exception (achieved by
|
||||
repetition, not richness). **Fixed** hairline rules, mono dates, serif titles. **Free** rows, venues.
|
||||
**Density** dense-exception.
|
||||
|
||||
### 4 · Manifesto / Quote (quote · move: italic serif · centered bloom)
|
||||
|
||||
**Ground** paper + a centered sun-bloom. **Composes** numeral-lg quote mark, display-it, attribution.
|
||||
**Focal** a 2-line Instrument Serif **italic** `display-it` quote in ink, centered, under an
|
||||
oversized serif quote mark. **Chrome** a micro-label attribution. **Accent** the centered bloom.
|
||||
**Silence** ~60% — deliberately open. **Fixed** italic serif, one bloom, ink. **Free** quote,
|
||||
attribution. **Density** low.
|
||||
|
||||
### 5 · Poster Panel (statement · move: yellow panel · split)
|
||||
|
||||
**Ground** paper with a full-bleed `{colors.sun}` `yellow-panel` (column or third). **Composes**
|
||||
yellow-panel, micro-label, headline. **Focal** an Instrument Serif `headline` in ink sitting **on
|
||||
the sun panel** (ink-on-yellow — a signature). **Chrome** micro-label; pagenum. **Accent** the panel
|
||||
itself. **Silence** the paper side stays open. **Fixed** ink-on-sun, panel meets paper directly (no
|
||||
border), no shadow. **Free** headline, panel side/width. **Density** low.
|
||||
|
||||
### 6 · Strand List (programme · move: numbered editorial rows · left)
|
||||
|
||||
**Ground** paper + a subtle sun-bloom. **Composes** micro-label, headline-sm, strand-rows. **Focal**
|
||||
a numbered list — serif numeral + serif title + sans body, hairline-soft separators. **Chrome**
|
||||
micro-label; pagenum. **Accent** the bloom. **Silence** moderate; rows breathe. **Fixed** serif
|
||||
numerals, hairline-soft rules. **Free** items, copy. **Density** standard.
|
||||
|
||||
## Composition Rules
|
||||
|
||||
### Do
|
||||
|
||||
- Start on **warm parchment**; add **one sun bloom** (optionally an ember counter-bloom) — atmosphere is the depth.
|
||||
- Set every line in **ink**; use **Instrument Serif 400** (tight, negative-tracked) for display, **Archivo** body, **JetBrains Mono** for all numerals/dates.
|
||||
- Make every separator a **1px ink hairline** (soft variant for dense rows); flood a **yellow panel** for poster moments (ink on top).
|
||||
- Keep micro-labels **uppercase Archivo 600, 0.16–0.32em**; pin the **pagenum** bottom-right.
|
||||
- Lean sparse; left/asymmetric on cover/chapter/list, centered on manifesto.
|
||||
|
||||
### Don't
|
||||
|
||||
- No drop shadows, no rounded corners, no bordered cards, no border thicker than 1px.
|
||||
- No second text color; no bold Instrument Serif; no inverted ink grounds.
|
||||
- No mono for body/display; no font substitutes.
|
||||
- Don't crowd the canvas — sparse reads as elegant; don't omit the bloom (flat parchment reads as a CMS template).
|
||||
- Don't blow a headline edge-to-edge — step the ramp down.
|
||||
|
||||
## Aspect-Ratio Behavior
|
||||
|
||||
| Treatment | 16:9 | 9:16 | 1:1 |
|
||||
| --------------- | ---------------------------------------------- | -------------------------- | -------------------------- |
|
||||
| Cover | display left, date-rail top-right, footer foot | display top, footer stacks | display upper, footer foot |
|
||||
| Chapter Divider | jumbo numeral, rail left | numeral centered, rail top | numeral centered |
|
||||
| Ledger | 4-col rows | drop venue col → 3-col | 3-col |
|
||||
| Manifesto | centered italic | centered, taller | centered |
|
||||
| Poster Panel | side panel + paper | top/bottom panel band | panel third |
|
||||
| Strand List | numbered rows | rows (tighter) | rows |
|
||||
|
||||
`pad-edge` holds on the short edge; display clamps use the shorter axis so portrait doesn't blow out
|
||||
headlines. Keep load-bearing lines ≥ 1.4cqw. Ledger/strand fixed first columns may tighten on 9:16.
|
||||
|
||||
## Approved Entities
|
||||
|
||||
No real customers, logos, or vendors are defined in the source — render any such mark as a
|
||||
placeholder. Programme titles, venues, and dates are content; the system supplies paper, ink, yellow.
|
||||
|
||||
## Numerals & Claims (hard rule)
|
||||
|
||||
Never invent figures, dates, durations, or counts at frame scale. Render slots as `— figure —`,
|
||||
`{metric}`, `NN JUN`, `N m`. Ledger dates/durations and chart values especially carry placeholders
|
||||
until the script supplies them. Chapter ordinals (01, 02…) are decorative.
|
||||
|
||||
## Pre-Render Self-Audit
|
||||
|
||||
- **Squint** — one serif display moment dominates; the bloom centers the eye.
|
||||
- **Silence** — sparse frames 55–60% open; only the ledger runs dense (via repetition, not richness).
|
||||
- **One color** — ink for all text + rules; sun for bloom/panel/tile; ember counter-bloom only; no inversion.
|
||||
- **Type** — Instrument Serif 400 tight negative-tracked, fit-to-measure; micro-labels uppercase 0.16em+; mono numerals; ≥1.4cqw floor.
|
||||
- **Depth** — 0 shadow, 0 rounded corner, 1px hairlines only; one sun bloom present.
|
||||
- **Anchor** — left on cover/chapter/list, centered on manifesto; pagenum bottom-right.
|
||||
- **Fabrication** — every numeral/date traces to the script, else placeholder.
|
||||
|
||||
## Known Gaps
|
||||
|
||||
- **Motion intentionally out of scope.** frame.md specifies composition only; the source's 280ms crossfade is a deck mechanic.
|
||||
- **Instrument Serif + Archivo + JetBrains Mono via Google Fonts.** CJK: Smiley Sans (display) / Noto Serif SC (body) / Noto Sans SC (labels); italic serif and mono tabular figures have no exact Hanzi equal — keep ledger dates Latin.
|
||||
- **9:16 / 1:1 are guidance**; the `min(vw,vh)` clamp pattern keeps display from blowing out — verify per ratio.
|
||||
- Sun/ember blooms, block tiles, yellow panels, and hairline rules are CSS-only; no external imagery is required.
|
||||
@@ -0,0 +1,221 @@
|
||||
<!--
|
||||
Biennale Yellow — 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 biennale 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 warm-parchment card on a single 1px hairline indigo-ink border — biennale's
|
||||
ONLY border — square corners, NO shadow (biennale's depth is atmospheric, never cast).
|
||||
Instrument Serif, sentence case, ink glyphs. Upcoming words sit in faint ink; the
|
||||
current word gets a solar-yellow underprint (the brand's signature "sun as panel /
|
||||
underprint", a hard square block, no blur) with ink reading on top; spoken words
|
||||
settle to solid ink, underprint 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 parchment card — single 1px hairline ink border (biennale's only border),
|
||||
square corners, NO shadow */
|
||||
.caption-pill {
|
||||
max-width: 78%;
|
||||
padding: 20px 44px 24px;
|
||||
background: var(--cap-canvas, #e9e5db);
|
||||
border: 1px solid var(--cap-ink, #1b2566);
|
||||
border-radius: 0;
|
||||
}
|
||||
.caption-line {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 0.1em 0.34em;
|
||||
font-family: var(--font-display, "Instrument Serif"), Georgia, serif;
|
||||
font-weight: 400;
|
||||
font-size: clamp(40px, 4.4vw, 58px);
|
||||
line-height: 1.16;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
/* upcoming — faint indigo ink, no underprint */
|
||||
.caption-word {
|
||||
display: inline-block;
|
||||
padding: 0 0.06em;
|
||||
color: color-mix(in srgb, var(--cap-ink, #1b2566) 42%, var(--cap-canvas, #e9e5db));
|
||||
}
|
||||
/* current word — THE solar-yellow underprint (box-shadow spread = a hard square
|
||||
block w/o layout shift; no blur), indigo ink reading on top */
|
||||
.caption-word.is-active {
|
||||
color: var(--cap-ink, #1b2566);
|
||||
background: var(--cap-accent, #f1ee2e);
|
||||
box-shadow: 0 0 0 0.06em var(--cap-accent, #f1ee2e);
|
||||
}
|
||||
/* already spoken — solid indigo ink, underprint cleared */
|
||||
.caption-word.is-spoken {
|
||||
color: var(--cap-ink, #1b2566);
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* optional per-word accents (dormant until a word-classer tags them) */
|
||||
/* brand / key term — Instrument Serif italic, biennale's only display inflection */
|
||||
.caption-word.cap-brand {
|
||||
font-style: italic;
|
||||
}
|
||||
/* numerals / data — the mono voice, slightly recessed */
|
||||
.caption-word.cap-num {
|
||||
font-family: var(--font-body, "JetBrains Mono"), ui-monospace, monospace;
|
||||
color: color-mix(in srgb, var(--cap-ink, #1b2566) 72%, var(--cap-canvas, #e9e5db));
|
||||
}
|
||||
/* emphasis — an ember-red hairline underline (the counter-temperature accent) */
|
||||
.caption-word.cap-cta {
|
||||
text-decoration: underline;
|
||||
text-decoration-thickness: 2px;
|
||||
text-decoration-color: var(--cap-accent-2, #e26b4a);
|
||||
text-underline-offset: 0.1em;
|
||||
}
|
||||
|
||||
@media (max-aspect-ratio: 9/16) {
|
||||
.caption-pill {
|
||||
max-width: 92%;
|
||||
}
|
||||
.caption-line {
|
||||
font-size: clamp(34px, 5.4vw, 48px);
|
||||
}
|
||||
}
|
||||
</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.97 }, { scale: 1, duration: 0.14, ease: "power2.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
@@ -0,0 +1,275 @@
|
||||
---
|
||||
version: alpha
|
||||
name: BlockFrame — Frame (video / frame layer)
|
||||
description: >
|
||||
Video-first companion to BlockFrame's design.md. The unit is the frame (1920×1080). Atoms are
|
||||
identical and sacred — 4px black borders + 8px hard offset shadows, the five-pastel candy palette
|
||||
(pink/blue/green/yellow/cream) plus black/white/off-white, Inter 800–900 uppercase display +
|
||||
Space Grotesk label chrome, square corners, label-pills, tilted decorations, star bursts, stripe
|
||||
blocks, dot grids. Composition + frame scale rewritten. 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:
|
||||
black: "#000000"
|
||||
white: "#FFFFFF"
|
||||
offwhite: "#FFFDF5"
|
||||
pink: "#FE90E8"
|
||||
blue: "#C0F7FE"
|
||||
green: "#99E885"
|
||||
yellow: "#F7CB46"
|
||||
cream: "#FFDC8B"
|
||||
|
||||
borders: { primary: "4px solid black", thin: "3px solid black" }
|
||||
shadows: { default: "8px 8px 0 black", small: "4px 4px 0 black", hover: "6px 6px 0 black", close-yellow: "12px 12px 0 yellow", close-white: "6px 6px 0 white" }
|
||||
|
||||
typography:
|
||||
# — reading + chrome ramp —
|
||||
body: { fontFamily: "Inter", cqw: 0.95, weight: 500, lineHeight: 1.6 }
|
||||
card-title:{ fontFamily: "Inter", cqw: 1.15, weight: 700, upper: true, lineHeight: 1.2 }
|
||||
label: { fontFamily: "Space Grotesk", px: 13, weight: 600, tracking: "0.08em", upper: true }
|
||||
counter: { fontFamily: "Space Grotesk", px: 14, weight: 700, tracking: "0.1em", upper: true }
|
||||
# — display ramp (Inter 800–900, uppercase, negative tracking) —
|
||||
heading-md:{ fontFamily: "Inter", cqw: 2.1, weight: 700, lineHeight: 1.1, tracking: "-0.01em", upper: true }
|
||||
quote-text:{ fontFamily: "Inter", cqw: 2.7, weight: 900, lineHeight: 1.15, tracking: "-0.02em", upper: true }
|
||||
stat-number:{ fontFamily: "Inter", cqw: 3.3, weight: 900, lineHeight: 1.0 }
|
||||
heading-lg:{ fontFamily: "Inter", cqw: 3.3, weight: 800, lineHeight: 1.0, tracking: "-0.02em", upper: true }
|
||||
close-title:{ fontFamily: "Inter", cqw: 4.2, weight: 900, lineHeight: 0.95, tracking: "-0.03em", upper: true }
|
||||
heading-xl:{ fontFamily: "Inter", cqw: 5.0, weight: 900, lineHeight: 0.95, tracking: "-0.03em", upper: true }
|
||||
|
||||
spacing:
|
||||
slide-pad: "3.1cqw" # 60px @1920
|
||||
gap-md: "1.7cqw"
|
||||
|
||||
components:
|
||||
card-elevated:
|
||||
backgroundColor: "{colors.white}"
|
||||
border: "0.4cqw solid {colors.black}"
|
||||
rounded: "0"
|
||||
shadow: "0.8cqw 0.8cqw 0 {colors.black}"
|
||||
description: "Primary card. Border/shadow coupled: 4px↔8px, 3px↔4px."
|
||||
card-small:
|
||||
backgroundColor: "{colors.white}"
|
||||
border: "0.3cqw solid {colors.black}"
|
||||
rounded: "0"
|
||||
shadow: "0.4cqw 0.4cqw 0 {colors.black}"
|
||||
description: "Stat cards, team cards, timeline steps."
|
||||
label-pill:
|
||||
border: "0.3cqw solid {colors.black}"
|
||||
backgroundColor: "any pastel / {colors.white}"
|
||||
rounded: "9999px"
|
||||
shadow: "0.4cqw 0.4cqw 0 {colors.black}"
|
||||
typography: "{typography.label}"
|
||||
description: "The universal eyebrow — never plain text."
|
||||
button-primary:
|
||||
backgroundColor: "{colors.yellow}"
|
||||
textColor: "{colors.black}"
|
||||
border: "0.3cqw solid {colors.black}"
|
||||
rounded: "0"
|
||||
shadow: "0.4cqw 0.4cqw 0 {colors.black}"
|
||||
typography: "Inter 700"
|
||||
description: "The CTA."
|
||||
star-burst:
|
||||
backgroundColor: "any pastel"
|
||||
border: "0.3cqw solid {colors.black}"
|
||||
clip: "10-point clip-path star"
|
||||
description: "Corner attention-grabber."
|
||||
stripe-block:
|
||||
backgroundImage: "45° {colors.black} + pastel diagonal stripes"
|
||||
border: "0.3cqw solid {colors.black}"
|
||||
description: "Poster decoration."
|
||||
bg-dot-grid:
|
||||
backgroundImage: "radial-dot ~1.2px dots, 24px grid, {colors.black}"
|
||||
description: "Faint corner/ground overlay."
|
||||
tilt-deco:
|
||||
transform: "rotate(±2°–12°)"
|
||||
description: "Rotated rectangle/badge/star. Stat cards alternate −2/+2°. The grid-puncture signature."
|
||||
stat-deco-dot:
|
||||
backgroundColor: "any pastel"
|
||||
border: "2px solid {colors.black}"
|
||||
rounded: "50%"
|
||||
size: "12px"
|
||||
description: "The ONLY round shape, pinned to stat cards."
|
||||
close-frame:
|
||||
backgroundColor: "{colors.black}"
|
||||
textColor: "{colors.white}"
|
||||
border: "0.4cqw solid {colors.white}"
|
||||
rounded: "0"
|
||||
shadow: "1.2cqw 1.2cqw 0 {colors.yellow}"
|
||||
description: "Inverted closer — the only colored shadow."
|
||||
---
|
||||
|
||||
# BlockFrame — Frame (video / frame layer)
|
||||
|
||||
## Overview
|
||||
|
||||
BlockFrame at frame scale is a **maximalist neobrutalist** system on five laws: every region has a
|
||||
4px black border, every elevated element an 8px hard offset shadow, every corner square, every
|
||||
accent a saturated pastel, and every layout allowed to be a little crooked. The joy is the
|
||||
deliberate collision — bordered cards meeting bordered cards, shadows stacking, tilted decorations
|
||||
puncturing the grid.
|
||||
|
||||
The voice is **Inter** at weight 800–900 in tight uppercase with negative tracking (display) +
|
||||
weight 500 sentence body, and **Space Grotesk** weight 600 uppercase 0.08em as the label/chrome
|
||||
voice. Five candy pastels (pink/blue/green/yellow/cream) cycle as full-bleed grounds across frames
|
||||
— the color cycling is the primary rhythm. Depth is **hard offset shadow** (8px/4px, solid black,
|
||||
zero blur, bottom-right); the close-frame's 12px yellow shadow is the one colored exception.
|
||||
|
||||
**Key characteristics at frame scale:**
|
||||
|
||||
- **4px black borders + 8px hard shadows** on primary cards; 3px + 4px on chrome (weights coupled).
|
||||
- **Five-pastel palette** cycled as full-bleed grounds; black/white/off-white structural.
|
||||
- **Inter 800–900 uppercase** negative-tracked display; **Space Grotesk** label chrome.
|
||||
- **Square corners** everywhere (only the stat-deco dot is round); **tilted decorations** puncture the grid.
|
||||
- **Label-pills** open every region; star bursts, stripe blocks, dot grids are reusable attention units.
|
||||
- **Comfortably dense** — packed reads as authoritative; empty corners read as broken.
|
||||
|
||||
## The Frame
|
||||
|
||||
### Frame Craft Bar
|
||||
|
||||
Three eyeball tests gate every frame before any structural check:
|
||||
|
||||
- **Squint** — one Inter display moment dominates at 3–6× its neighbor; cards read as a system, not rivals.
|
||||
- **Silence** — cover/quote/close keep air (decorations, not content); the **feature-card and stat grids are the dense exception**.
|
||||
- **Restraint** — the 4px→08px / 3px→4px coupling holds; black borders only (white on close); pastel ground **cycles** one per frame; no sixth pastel.
|
||||
- **Reference** — aim at a **zine / 1990s sticker-book / toy-packaging spread** (bordered blocks, tilted decorations); failure looks like a **flat, borderless, blurred-shadow web card grid**.
|
||||
|
||||
- **Primary:** 1920×1080 (16:9). Display authored in **`cqw`** (`px ÷ 1920 × 100 = cqw`).
|
||||
- **Vertical:** 1080×1920 (9:16). **Square:** 1080×1080 (1:1).
|
||||
- **Safe area:** `slide-pad` ~3.1cqw; decorations may bleed off edges.
|
||||
|
||||
**The container law (load-bearing).** Every frame ground sets `container-type: size`; ALL
|
||||
frame-relative units are `cqw`/`cqh` against it — never `vw`. Borders/shadows scale in `cqw` so the
|
||||
4px↔8px coupling holds proportionally; corners stay square.
|
||||
|
||||
## Colors
|
||||
|
||||
Tokens identical to the source. `{colors.offwhite}` is the default ground, but frames **cycle**
|
||||
through `{colors.cream}`/`blue`/`pink`/`green`/`yellow` grounds — the cycle is the rhythm.
|
||||
`{colors.black}` is every border + structural text; `{colors.white}` is card fills. The five pastels
|
||||
are interchangeable with **no semantic meaning** — pair by juxtaposition (pink+blue+green trio,
|
||||
cream+yellow warm pair). `{colors.yellow}` is the CTA + the one colored (close) shadow;
|
||||
`{colors.black}` ground is the close surface. **No sixth pastel.**
|
||||
|
||||
## Typography
|
||||
|
||||
Two ramps. The **reading/chrome ramp** (Inter body 0.95cqw weight 500, Space Grotesk labels in px)
|
||||
carries copy + chrome; the **display ramp** (Inter `heading-md` 2.1cqw → `heading-xl` 5cqw, weight
|
||||
800–900 uppercase) carries every headline + stat.
|
||||
|
||||
- **Legibility floor:** any load-bearing line ≥ **1.4cqw**; px labels are chrome only.
|
||||
- **Fit-to-measure:** size the headline to its length. Cap the block at **≤ 78cqw**; ≤3 words → `heading-xl`; 4–6 → `heading-lg`; 7+ → `heading-md`.
|
||||
- **Inter display is uppercase, weight 800–900, negative-tracked** (−0.01..−0.03em); body weight 500 sentence case; **Space Grotesk labels uppercase 0.08em**. No sentence-case display, no untracked display.
|
||||
|
||||
## Depth & Surface
|
||||
|
||||
Hard offset shadow, solid black, zero blur, bottom-right:
|
||||
|
||||
- **0.8cqw (8px)** primary cards; **0.4cqw (4px)** chrome; **0.6cqw (6px)** hover.
|
||||
- **Border-based depth** — the 4px/3px borders do much of the lift; shadow makes it "elevated."
|
||||
- **Tilt** — ±2°–12° rotation breaks the grid for perceived dimension.
|
||||
- **Inverted close** — 12px YELLOW shadow (and 6px white on close-btn) — the only colored shadows.
|
||||
|
||||
**Ceiling:** no blurred shadow, no rounded corner (save the stat-deco dot), no gradient depth.
|
||||
|
||||
## Shapes
|
||||
|
||||
- **0 radius everywhere** except the 12px stat-deco dot (50%). Square corners are the structural identity.
|
||||
|
||||
## Components
|
||||
|
||||
- **card-elevated / card-small** — the bordered+shadowed content cards (weight-coupled).
|
||||
- **label-pill** — the universal eyebrow (border+shadow+pastel). **button-primary** — yellow CTA.
|
||||
- **star-burst / stripe-block / bg-dot-grid / tilt-deco** — the reusable decoration units (one per frame min).
|
||||
- **stat-deco-dot** — the lone round shape. **close-frame** — the inverted black+white+yellow-shadow closer.
|
||||
|
||||
## Frame Treatments
|
||||
|
||||
> Recipe: ground · container · composes · focal · chrome · accent · silence · Fixed/Free · density.
|
||||
> Cycle the ground color; add ≥1 decoration per frame; open with a label-pill.
|
||||
|
||||
### 1 · Cover (identity · move: decorations puncture · left)
|
||||
|
||||
**Ground** `{colors.cream}` (or offwhite) + faint dot-grid. **Composes** label-pill, heading-xl, tilt-deco rect, star-burst, counter. **Focal** a 2–3 line Inter `heading-xl` uppercase, left, under a label-pill; tilted pastel rects + a star burst puncture the right. **Chrome** counter pill. **Accent** the decorations' pastels. **Silence** right third holds decorations. **Fixed** 4px/8px coupling, uppercase display, square. **Free** title, decoration placement/colors. **Density** comfortable.
|
||||
|
||||
### 2 · Feature Cards (catalog · move: 3-up bordered grid · blue ground)
|
||||
|
||||
**Ground** `{colors.blue}`, `slide-pad`. **Composes** label-pill, heading-lg, 3× card-elevated (icon-square + card-title + body). **Focal** three white bordered+shadowed cards. **Chrome** label-pill eyebrow. **Accent** the pastel icon-squares (pink/green/yellow). **Silence** tight — dense by design. **Fixed** 4px border + 8px shadow, square, uppercase card-titles. **Free** card content, icon hues. **Density** dense-exception.
|
||||
|
||||
### 3 · Stat Grid (data · move: tilted stat cards · green ground)
|
||||
|
||||
**Ground** `{colors.green}`, `slide-pad`. **Composes** label-pill, heading-lg, 3× card-small (tilted −2/+2°, stat-deco dot, stat-number + label). **Focal** three tilted bordered stat cards. **Accent** the deco-dots' pastels + black stat numerals. **Silence** moderate. **Fixed** alternating tilt, 3px+4px, round deco-dot only. **Free** figures (from script), dot hues. **Density** dense-exception.
|
||||
|
||||
### 4 · Closing Plate (closer · move: inverted black · centered)
|
||||
|
||||
**Ground** `{colors.black}`. **Composes** close-frame (4px white border, 12px yellow shadow), label-pill (inverted), close-title, star-burst. **Focal** a white `close-title` inside the white-bordered frame with the yellow offset shadow; a pink star punctures a corner. **Accent** the yellow shadow + pink star. **Silence** ~50%. **Fixed** white-on-black, 12px yellow shadow (only here), square. **Free** sign-off, star placement. **Density** low.
|
||||
|
||||
### 5 · Quote (quote · move: bordered quote frame · pink ground)
|
||||
|
||||
**Ground** `{colors.pink}` (or offwhite). **Composes** label-pill, quote-text in a card-elevated, attribution. **Focal** an Inter `quote-text` (900 uppercase) inside a white bordered+shadowed frame. **Accent** the ground + one decoration. **Silence** moderate. **Fixed** uppercase quote, 4px/8px. **Free** quote, ground. **Density** comfortable.
|
||||
|
||||
### 6 · Timeline (process · move: stepped bordered cards · offwhite)
|
||||
|
||||
**Ground** `{colors.offwhite}`. **Composes** label-pill, heading-lg, 3–4 card-small steps + step-connectors. **Focal** a row of bordered step cards linked by black connector bars, each a pastel + step-num. **Accent** the step pastels. **Silence** moderate. **Fixed** 3px+4px steps, square, connectors. **Free** steps, hues. **Density** standard.
|
||||
|
||||
## Composition Rules
|
||||
|
||||
### Do
|
||||
|
||||
- Pair **4px borders with 8px shadows**, **3px with 4px** — the coupling is non-negotiable.
|
||||
- **Cycle pastel grounds** across frames; keep the deck visually rhythmic.
|
||||
- Set Inter display **uppercase, 800–900, negative-tracked**; open every region with a **label-pill**.
|
||||
- Render shadows **solid black, zero blur, bottom-right**; add **≥1 decoration** (tilt/star/stripe/dots) per frame.
|
||||
- Use **yellow** for CTAs; use the **inverted black + 12px yellow shadow** close-frame for the closer.
|
||||
- Tilt decorations ±2°–12°; lean left on most frames.
|
||||
|
||||
### Don't
|
||||
|
||||
- No rounded corners (save the stat-deco dot); no blurred shadows.
|
||||
- No colored borders (black only, save the close-frame white); no sixth pastel.
|
||||
- No sentence-case Inter display; no untracked display; no label as plain text.
|
||||
- Don't keep everything perfectly aligned (tilt is the signature); don't leave corners empty.
|
||||
- Don't blow a headline edge-to-edge — fit to measure.
|
||||
|
||||
## Aspect-Ratio Behavior
|
||||
|
||||
| Treatment | 16:9 | 9:16 | 1:1 |
|
||||
| ------------- | ----------------------------- | ---------------------------- | ------------------------ |
|
||||
| Cover | title left, decorations right | title top, decorations below | title, fewer decorations |
|
||||
| Feature Cards | 3 across | 3 stacked | 2+1 |
|
||||
| Stat Grid | 3 tilted across | 3 tilted stacked | 2×2 |
|
||||
| Closing | centered close-frame | centered, taller | centered |
|
||||
| Quote | quote frame | quote stacked | centered |
|
||||
| Timeline | horizontal steps | vertical (connectors hidden) | 2×2 |
|
||||
|
||||
`slide-pad` holds on the short edge; re-step display above the 1.4cqw floor. Decoration count drops
|
||||
on tighter ratios so the frame stays bordered, not cluttered.
|
||||
|
||||
## Approved Entities
|
||||
|
||||
No real customers, logos, or vendors are defined in the source — render any such mark as a
|
||||
placeholder. Pastel fills, decorations, and tilts are content-agnostic.
|
||||
|
||||
## Numerals & Claims (hard rule)
|
||||
|
||||
Never invent figures, stats, or counts at frame scale. Render slots as `— figure —`, `{metric}`,
|
||||
`N×`. Stat cards and charts carry placeholders until the script supplies values. Slide counters and
|
||||
list numbers are decorative.
|
||||
|
||||
## Pre-Render Self-Audit
|
||||
|
||||
- **Squint** — one Inter display moment dominates; cards read as a system.
|
||||
- **Silence** — only feature/stat grids run dense; cover/quote/close keep air with decorations.
|
||||
- **Borders/shadows** — 4px↔8px / 3px↔4px coupling holds; solid black, zero blur.
|
||||
- **Color** — pastel ground cycles; black borders only (white on close); no sixth pastel.
|
||||
- **Type** — Inter uppercase 800–900 negative-tracked, fit-to-measure; Space Grotesk labels 0.08em; ≥1.4cqw floor.
|
||||
- **Shape** — square corners (only stat-deco dot round); ≥1 tilt/decoration per frame.
|
||||
- **Fabrication** — every numeral traces to the script, else placeholder.
|
||||
|
||||
## Known Gaps
|
||||
|
||||
- **Motion intentionally out of scope.** frame.md specifies composition only; the source toggles slides via display, no transition.
|
||||
- **Inter + Space Grotesk via Google Fonts.** CJK: Noto Sans SC 900 (sentence case — the uppercase signal drops); lean harder on borders/shadows/decoration to carry the brutalist identity.
|
||||
- **9:16 / 1:1 are guidance**; verify the floor and that decoration count scales down.
|
||||
- Star bursts (clip-path), stripe blocks, dot grids, and tilts are CSS-only; no external imagery is required.
|
||||
@@ -0,0 +1,224 @@
|
||||
<!--
|
||||
BlockFrame — 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 blockframe 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 white card carved by a 4px black border with an 8px hard offset shadow
|
||||
(solid black, zero blur, bottom-right — blockframe's sacred depth), square corners.
|
||||
Inter 900, UPPERCASE, negative-tracked. Upcoming words sit in faded ink; the current
|
||||
word gets a saturated-yellow stamp-block — a hard square highlight (box-shadow spread,
|
||||
no blur) with black ink reading on top, blockframe's signature stamp gesture; spoken
|
||||
words settle to solid black ink, stamp cleared. The word pops in with a snappy
|
||||
back.out overshoot — loud, a little crooked.
|
||||
-->
|
||||
<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 bordered card — 4px black border + 8px hard offset shadow (solid black,
|
||||
zero blur, bottom-right — blockframe's sacred depth), square corners */
|
||||
.caption-pill {
|
||||
max-width: 80%;
|
||||
padding: 18px 40px 22px;
|
||||
background: var(--cap-canvas, #ffffff);
|
||||
border: 4px solid var(--cap-ink, #000000);
|
||||
border-radius: 0;
|
||||
box-shadow: 8px 8px 0 var(--cap-ink, #000000);
|
||||
}
|
||||
.caption-line {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 0.12em 0.32em;
|
||||
font-family: var(--font-display, "Inter"), system-ui, sans-serif;
|
||||
font-weight: 900;
|
||||
text-transform: uppercase;
|
||||
font-size: clamp(38px, 4.2vw, 56px);
|
||||
line-height: 1.12;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
/* upcoming — faded black ink, no stamp */
|
||||
.caption-word {
|
||||
display: inline-block;
|
||||
padding: 0 0.1em;
|
||||
color: color-mix(in srgb, var(--cap-ink, #000000) 40%, var(--cap-canvas, #ffffff));
|
||||
}
|
||||
/* current word — THE yellow stamp-block (box-shadow spread = a hard square block
|
||||
w/o layout shift; no blur), black ink reading on top — blockframe's signature stamp */
|
||||
.caption-word.is-active {
|
||||
color: var(--cap-ink, #000000);
|
||||
background: var(--cap-accent, #f7cb46);
|
||||
box-shadow: 0 0 0 0.08em var(--cap-accent, #f7cb46);
|
||||
}
|
||||
/* already spoken — solid black ink, stamp cleared */
|
||||
.caption-word.is-spoken {
|
||||
color: var(--cap-ink, #000000);
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* optional per-word accents (dormant until a word-classer tags them) */
|
||||
/* brand / key term — heavier ink weight, blockframe's display is already 900 */
|
||||
.caption-word.cap-brand {
|
||||
color: var(--cap-ink, #000000);
|
||||
}
|
||||
/* numerals / data — the body voice, slightly recessed */
|
||||
.caption-word.cap-num {
|
||||
font-family: var(--font-body, "Inter"), ui-monospace, monospace;
|
||||
color: color-mix(in srgb, var(--cap-ink, #000000) 74%, var(--cap-canvas, #ffffff));
|
||||
}
|
||||
/* emphasis — a cream hard underline bar (the second-chroma accent) */
|
||||
.caption-word.cap-cta {
|
||||
text-decoration: underline;
|
||||
text-decoration-thickness: 4px;
|
||||
text-decoration-color: var(--cap-accent-2, #ffdc8b);
|
||||
text-underline-offset: 0.12em;
|
||||
}
|
||||
|
||||
@media (max-aspect-ratio: 9/16) {
|
||||
.caption-pill {
|
||||
max-width: 92%;
|
||||
}
|
||||
.caption-line {
|
||||
font-size: clamp(34px, 5.4vw, 48px);
|
||||
}
|
||||
}
|
||||
</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.86 }, { scale: 1, duration: 0.2, ease: "back.out(2.4)" }, 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
@@ -0,0 +1,307 @@
|
||||
---
|
||||
version: alpha
|
||||
name: Blue Professional — Frame (video / frame layer)
|
||||
description: >
|
||||
Video-first companion to Blue Professional's design.md. The unit is the frame (1920×1080). Atoms
|
||||
are identical and sacred — the warm cream canvas, a single saturated cobalt (#1e2bfa) as the only
|
||||
accent, the three-step gray text ladder, Space Grotesk (display/numerals/chrome) + Inter (body),
|
||||
soft cobalt-tinted cards (4% fill / 20% border / 10–14px radius) with NO shadows, pill chrome, and
|
||||
the cobalt progress bar. Composition + frame scale rewritten. 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:
|
||||
bg: "#fdfae7"
|
||||
primary: "#1e2bfa"
|
||||
text: "#111111"
|
||||
text-muted: "#6b6b6b"
|
||||
text-light: "#9a9a9a"
|
||||
accent-light: "rgba(30,43,250,0.08)"
|
||||
accent-medium: "rgba(30,43,250,0.15)"
|
||||
border: "rgba(30,43,250,0.2)"
|
||||
card-bg: "rgba(30,43,250,0.04)"
|
||||
positive: "#059669"
|
||||
negative: "#dc2626"
|
||||
|
||||
radii:
|
||||
pill: "100px"
|
||||
card-lg: "14px"
|
||||
card-md: "12px"
|
||||
card-sm: "10px"
|
||||
bar: "6px"
|
||||
circle: "50%"
|
||||
|
||||
typography:
|
||||
# — reading ramp (Inter body + Space Grotesk chrome) —
|
||||
body: { fontFamily: "Inter", cqw: 0.85, weight: 400, lineHeight: 1.6, color: "text-muted" }
|
||||
h4-eyebrow:{ fontFamily: "Space Grotesk", cqw: 0.8, weight: 600, tracking: "0.08em", upper: true, color: "primary" }
|
||||
tag: { fontFamily: "Space Grotesk", px: 12, weight: 500, color: "primary" }
|
||||
counter: { fontFamily: "Space Grotesk", px: 13, weight: 500, tracking: "0.05em", color: "text-muted" }
|
||||
# — display / numerical ramp (Space Grotesk, near-black headings / cobalt numerals) —
|
||||
h3: { fontFamily: "Space Grotesk", cqw: 1.25, weight: 500, lineHeight: 1.3, tracking: "-0.02em", color: "text" }
|
||||
stat-num:{ fontFamily: "Space Grotesk", cqw: 1.9, weight: 700, lineHeight: 1.0, color: "primary" }
|
||||
blockquote:{ fontFamily: "Space Grotesk", cqw: 2.4, weight: 500, lineHeight: 1.35, color: "text" }
|
||||
h2: { fontFamily: "Space Grotesk", cqw: 2.6, weight: 600, lineHeight: 1.1, tracking: "-0.02em", color: "text" }
|
||||
metric-value:{ fontFamily: "Space Grotesk", cqw: 3.0, weight: 700, lineHeight: 1.0, color: "primary" }
|
||||
h1: { fontFamily: "Space Grotesk", cqw: 4.2, weight: 700, lineHeight: 1.08, tracking: "-0.02em", color: "text" }
|
||||
quote-mark:{ fontFamily: "Space Grotesk", cqw: 8.0, weight: 700, lineHeight: 0.5, color: "primary", opacity: 0.15 }
|
||||
|
||||
spacing:
|
||||
pad-x: "5cqw"
|
||||
pad-y-top: "5cqw"
|
||||
gap-cards: "1.4cqw"
|
||||
accent-line: "60px × 4px"
|
||||
|
||||
components:
|
||||
card-tinted:
|
||||
backgroundColor: "{colors.card-bg}"
|
||||
border: "1.5px solid {colors.border}"
|
||||
rounded: "{radii.card-lg}"
|
||||
shadow: "none"
|
||||
description: "Universal content card. Never solid-colored, never opaque-bordered, NO shadow."
|
||||
metric-card:
|
||||
backgroundColor: "{colors.card-bg}"
|
||||
border: "1.5px solid {colors.border}"
|
||||
rounded: "{radii.card-lg}"
|
||||
typography: "{typography.metric-value} ({colors.primary}) + {typography.metric-label} + {typography.metric-desc}"
|
||||
description: "+ optional inline ↑/↓ change chip ({colors.positive}/{colors.negative} text, no fill)."
|
||||
tag-pill:
|
||||
backgroundColor: "{colors.accent-light}"
|
||||
textColor: "{colors.primary}"
|
||||
rounded: "{radii.pill}"
|
||||
typography: "{typography.tag}"
|
||||
description: "Top-right of the slide-header."
|
||||
cta-button:
|
||||
backgroundColor: "{colors.primary}"
|
||||
textColor: "{colors.bg}"
|
||||
rounded: "{radii.pill}"
|
||||
typography: "Space Grotesk 600"
|
||||
shadow: "soft cobalt on hover only — the system's only shadow"
|
||||
description: "The one solid element."
|
||||
accent-line:
|
||||
backgroundColor: "{colors.primary}"
|
||||
size: "60×4, 2px radius"
|
||||
description: "Above cover titles / eyebrow separators."
|
||||
bar-track:
|
||||
backgroundColor: "{colors.accent-light}"
|
||||
fill: "{colors.primary} (display:block so width resolves)"
|
||||
rounded: "{radii.bar}"
|
||||
description: "28px track; fill carries the value."
|
||||
step-circle:
|
||||
backgroundColor: "{colors.primary}"
|
||||
textColor: "{colors.bg}"
|
||||
rounded: "50%"
|
||||
size: "56px"
|
||||
description: "Sequential steps fade opacity 1.0→0.85→0.7→0.55."
|
||||
split-highlight:
|
||||
backgroundColor: "{colors.accent-light}"
|
||||
borderLeft: "4px solid {colors.primary}"
|
||||
rounded: "{radii.card-md}"
|
||||
description: "Inline pull-quote callout."
|
||||
slide-header:
|
||||
typography: "{typography.h4-eyebrow} (cobalt) left, tag-pill right; {typography.h2} below"
|
||||
description: "Top band of every content frame."
|
||||
atmosphere:
|
||||
elements: "clipped diagonal cobalt-tint panel, 3×3 cobalt dot grid, concentric closing rings"
|
||||
description: "Cover/closing only. Never on content frames."
|
||||
progress-bar:
|
||||
backgroundColor: "{colors.primary}"
|
||||
size: "3px tall, bottom edge, width grows with index"
|
||||
description: "Persistent progress strip."
|
||||
---
|
||||
|
||||
# Blue Professional — Frame (video / frame layer)
|
||||
|
||||
## Overview
|
||||
|
||||
Blue Professional at frame scale is a **consulting-grade system: restraint with one strong
|
||||
commitment.** A warm cream canvas and a single saturated cobalt that carries every accent —
|
||||
eyebrow, metric, CTA, chart fill, progress bar. No secondary brand color, no pastels, just cream,
|
||||
cobalt, and a tight ladder of grays. The register is investment-research / McKinsey briefing:
|
||||
measured, data-dense without crowding, executive-readable at distance.
|
||||
|
||||
The voice is two faces in fixed roles: **Space Grotesk** (display, every numeral, all chrome —
|
||||
eyebrows uppercase 0.08em) and **Inter** (body, muted gray, line 1.6). Headlines are near-black;
|
||||
cobalt is reserved for accent moments. Depth is **soft and tinted** — 4% cobalt card fills with 20%
|
||||
cobalt borders and 10–14px radii — never shadowed. The lack of harsh shadows is the premium signal.
|
||||
|
||||
**Key characteristics at frame scale:**
|
||||
|
||||
- **Warm cream ground** on every frame; **single cobalt** as the only accent.
|
||||
- **Space Grotesk** (display/numerals/chrome) + **Inter** (body) — near-black headlines, cobalt numerals.
|
||||
- **Tinted cards** — cobalt 4% fill, cobalt 20% 1.5px border, 10–14px radius, **no shadow**.
|
||||
- **Pill chrome** (100px) — tag pills + the one solid cobalt CTA; cobalt **progress bar**.
|
||||
- **Soft rounded corners everywhere** (no square corners save the progress bar).
|
||||
- **Atmosphere** (diagonal panel, dot grid, concentric rings) on cover/closing only.
|
||||
|
||||
## The Frame
|
||||
|
||||
### Frame Craft Bar
|
||||
|
||||
Three eyeball tests gate every frame before any structural check:
|
||||
|
||||
- **Squint** — one **near-black headline or cobalt numeral** dominates at 3–6× its neighbor.
|
||||
- **Silence** — content frames read **balanced, not crowded**; the **dashboard is the one dense exception**.
|
||||
- **Restraint** — a **single cobalt accent** carries everything; headlines stay near-black (never cobalt); no shadows (tinted cards do the lift); positive/negative inline-text only.
|
||||
- **Reference** — aim at an **investment-research / McKinsey quarterly briefing**; failure looks like a **heavy-outlined, multi-color dashboard**.
|
||||
|
||||
- **Primary:** 1920×1080 (16:9). Display authored in **`cqw`** (`px ÷ 1920 × 100 = cqw`).
|
||||
- **Vertical:** 1080×1920 (9:16). **Square:** 1080×1080 (1:1).
|
||||
- **Safe area:** `pad-x` 5cqw; bottom reserves room for the counter + progress bar.
|
||||
|
||||
**The container law (load-bearing).** Every frame ground sets `container-type: size`; ALL
|
||||
frame-relative units are `cqw`/`cqh` against it — never `vw`. Card radii stay px (10–14px); the
|
||||
pill radius stays 100px; borders stay 1–1.5px.
|
||||
|
||||
## Colors
|
||||
|
||||
Tokens identical to the source. `{colors.bg}` cream is the universal ground; `{colors.primary}`
|
||||
cobalt is the **only** accent — every eyebrow, numeral, CTA, chart fill, progress bar, and the 4px
|
||||
highlight left-rule. Headlines are `{colors.text}` near-black (never cobalt); body is
|
||||
`{colors.text-muted}`; tertiary is `{colors.text-light}`. Cards fill `{colors.card-bg}` (4%) with
|
||||
`{colors.border}` (20%) borders. `{colors.positive}`/`{colors.negative}` appear **only inline** on
|
||||
directional change chips — never as fills. **No second accent color.**
|
||||
|
||||
## Typography
|
||||
|
||||
Two ramps. The **reading ramp** (Inter body 0.85cqw muted; Space Grotesk eyebrow uppercase 0.08em
|
||||
cobalt) carries copy + chrome; the **display/numerical ramp** (Space Grotesk `h3` 1.25cqw → `h1`
|
||||
4.2cqw near-black; numerals `stat-num`/`metric-value` in cobalt) carries headings and figures.
|
||||
|
||||
- **Legibility floor:** any load-bearing line ≥ **1.4cqw**; px chrome (tag/counter) is colophon only.
|
||||
- **Fit-to-measure:** size the headline to its length. Cap the block at **≤ 78cqw**; ≤3 words → `h1`; 4–6 → `h2`; 7+ → `h3`. Cobalt numerals scale `metric-value`→`stat-num` by card size.
|
||||
- **Headlines near-black, −0.02em**; **eyebrows cobalt, uppercase, 0.08em**; **numerals cobalt 600–700**; **body Inter 400 muted, line 1.6**. No italic, no uppercase body, no cobalt headline.
|
||||
|
||||
## Depth & Surface
|
||||
|
||||
Soft and tinted — never offset. Depth from:
|
||||
|
||||
- **Tinted cards** — 4% cobalt fill + 20% cobalt 1.5px border + 10–14px radius reads as lifted without offset.
|
||||
- **Border-left accent** — the 4px cobalt rule on split-highlight blocks pulls a callout forward.
|
||||
- **Rounded corners** — the 10–14px radius is part of the softness; square corners break it.
|
||||
|
||||
**Ceiling:** zero box-shadow on content (the only shadow is a soft cobalt CTA _hover_); no opaque cobalt borders; no harsh outlines.
|
||||
|
||||
## Shapes
|
||||
|
||||
- **100px** — tag pills, CTA, nav buttons (pill chrome).
|
||||
- **14/12/10px** — cards by size (large metric / standard stat / detail + mini).
|
||||
- **6px** — bar tracks + fills. **50%** — step circles, nav circles, dots, closing rings.
|
||||
- **0** — only the progress bar. No square-cornered content.
|
||||
|
||||
## Components
|
||||
|
||||
- **card-tinted / metric-card** — the universal soft-tint content cards (no shadow).
|
||||
- **tag-pill / cta-button / accent-line** — the cobalt pill chrome + the one solid CTA + the 60×4 rule.
|
||||
- **bar-track / step-circle / split-highlight** — cobalt data + sequence + callout patterns.
|
||||
- **slide-header** (eyebrow + tag pill) — the structural rhythm; **atmosphere** (diagonal/dots/rings) on cover/closing only; **progress-bar** on every frame.
|
||||
|
||||
## Frame Treatments
|
||||
|
||||
> Recipe: ground · container · composes · focal · chrome · accent · silence · Fixed/Free · density.
|
||||
> Atmosphere only on cover/closing; content frames carry the slide-header rhythm.
|
||||
|
||||
### 1 · Cover (identity · move: diagonal accent · left)
|
||||
|
||||
**Ground** cream + the clipped diagonal cobalt-tint panel (right ~36%) + a 3×3 cobalt dot grid.
|
||||
**Composes** accent-line, meta, h1, body sub. **Focal** a 2-line Space Grotesk `h1` near-black, left,
|
||||
under a cobalt accent-line + meta. **Chrome** counter + progress bar. **Accent** the cobalt line +
|
||||
diagonal panel. **Silence** the diagonal panel holds the right third. **Fixed** near-black h1, cobalt
|
||||
accents, atmosphere here only. **Free** title, meta. **Density** low.
|
||||
|
||||
### 2 · Dashboard (data · move: 3-up metric grid · the dense frame)
|
||||
|
||||
**Ground** cream, `pad-x`. **Composes** slide-header (eyebrow + tag-pill), h2, 3× metric/tinted card.
|
||||
**Focal** a row of tinted cards — cobalt `metric-value` + Inter label + muted desc + optional
|
||||
green/red change chip. **Chrome** eyebrow left, tag-pill right; progress bar. **Accent** the cobalt
|
||||
numerals. **Silence** tight — the density exception. **Fixed** 4% tint cards, 20% borders, no shadow,
|
||||
cobalt numerals. **Free** figures (from script), labels. **Density** dense-exception.
|
||||
|
||||
### 3 · Bar Ranking (data · move: cobalt bars · left)
|
||||
|
||||
**Ground** cream, `pad-x`. **Composes** eyebrow, h2, bar-track rows. **Focal** 3–5 labeled
|
||||
cobalt-fill bars on cobalt-8% tracks with cobalt percentages. **Chrome** eyebrow; progress bar.
|
||||
**Accent** the cobalt fills + figures. **Silence** moderate. **Fixed** 6px tracks, cobalt fills.
|
||||
**Free** rows, values (from script). **Density** standard.
|
||||
|
||||
### 4 · Pull Quote (quote · move: concentric rings · centered)
|
||||
|
||||
**Ground** cream, centered, with faint concentric closing-rings behind. **Composes** quote-mark,
|
||||
blockquote, cite. **Focal** a Space Grotesk `blockquote` near-black under a 15%-opacity cobalt
|
||||
quote-mark; an uppercase cobalt-muted cite beneath. **Accent** the faint rings + quote-mark. **Silence**
|
||||
~55%. **Fixed** near-black quote, soft rings. **Free** quote, cite. **Density** low.
|
||||
|
||||
### 5 · Split + Highlight (content · move: asymmetric split · left)
|
||||
|
||||
**Ground** cream, two columns. **Composes** eyebrow, h2, body, split-highlight block. **Focal** an
|
||||
Inter body column beside a cobalt-8% highlight block (4px cobalt left rule) carrying an inline pull
|
||||
quote. **Accent** the highlight's left rule. **Silence** generous gutter. **Fixed** tinted highlight,
|
||||
4px cobalt rule. **Free** body, callout. **Density** standard.
|
||||
|
||||
### 6 · Closing / CTA (closer · move: centered rings + CTA)
|
||||
|
||||
**Ground** cream + concentric closing-rings. **Composes** accent-line, h1, body, cta-button. **Focal**
|
||||
a Space Grotesk `h1` near-black, centered, with the one solid cobalt `cta-button` pill below. **Accent**
|
||||
the CTA + rings. **Silence** ~60%. **Fixed** one CTA, near-black h1, soft rings. **Free** sign-off, CTA
|
||||
label. **Density** low.
|
||||
|
||||
## Composition Rules
|
||||
|
||||
### Do
|
||||
|
||||
- Start every frame on **warm cream**; let **cobalt carry every accent** (eyebrow, numeral, CTA, bar, progress).
|
||||
- Set headlines **near-black, −0.02em**; eyebrows **cobalt uppercase 0.08em**; numerals **cobalt 600–700**.
|
||||
- Use **tinted cards** (4% fill, 20% border, 10–14px radius, no shadow); body Inter 400 muted, line 1.6.
|
||||
- Keep all chrome **pill-shaped (100px)**; one solid cobalt CTA per closing frame.
|
||||
- Reserve **atmosphere** (diagonal panel, dots, rings) for cover/closing; content frames keep the slide-header rhythm.
|
||||
- Lean left on cover/dashboard/split, centered on quote/closer.
|
||||
|
||||
### Don't
|
||||
|
||||
- No second accent color; no cobalt headlines.
|
||||
- No drop shadows on content (only the soft cobalt CTA hover); no opaque cobalt borders.
|
||||
- No square corners (save the progress bar); no font substitutes; no uppercase body.
|
||||
- Don't use the green/red change chips as general accents — directional comparisons only.
|
||||
- Don't fill space with heavier borders — add substance; don't blow a headline edge-to-edge.
|
||||
|
||||
## Aspect-Ratio Behavior
|
||||
|
||||
| Treatment | 16:9 | 9:16 | 1:1 |
|
||||
| ----------------- | -------------------------- | ------------------------------ | ------------------------ |
|
||||
| Cover | title left, diagonal right | title top, diagonal band below | title upper, dots corner |
|
||||
| Dashboard | 3 cards across | 3 stacked | 2×2 |
|
||||
| Bar Ranking | 3–5 bars | 3–5 bars (tighter) | 3 bars |
|
||||
| Pull Quote | centered, rings behind | centered, taller | centered |
|
||||
| Split + Highlight | side-by-side | stacked | stacked |
|
||||
| Closing / CTA | centered + CTA | centered + CTA | centered + CTA |
|
||||
|
||||
`pad-x` holds on the short edge; re-step display above the 1.4cqw floor. The diagonal cover panel
|
||||
becomes a top/bottom band on 9:16. Numerals stay Latin Arabic digits in CJK builds.
|
||||
|
||||
## Approved Entities
|
||||
|
||||
No real customers, logos, or vendors are defined in the source — render any such mark as a
|
||||
placeholder. Figures, metrics, and quotes are content; the system supplies cream + cobalt + grays.
|
||||
|
||||
## Numerals & Claims (hard rule)
|
||||
|
||||
Never invent figures, financials, percentages, or dates at frame scale. Render slots as `— figure —`,
|
||||
`{metric}`, `+NN%`, `↑ —`. Metric cards, bar values, and stat cells especially carry placeholders
|
||||
until the script supplies them. Directional chips require a real comparison from the script.
|
||||
|
||||
## Pre-Render Self-Audit
|
||||
|
||||
- **Squint** — one near-black headline or cobalt numeral dominates per frame.
|
||||
- **Silence** — content frames balanced, not crowded; only the dashboard runs dense.
|
||||
- **Single accent** — cobalt only; headlines near-black; positive/negative inline only.
|
||||
- **Type** — Space Grotesk headings −0.02em near-black, cobalt eyebrows 0.08em + numerals; Inter body muted line 1.6; ≥1.4cqw floor.
|
||||
- **Depth** — tinted cards (no shadow), soft rounded corners, 20% cobalt borders; no square content corners.
|
||||
- **Anchor** — left on cover/dashboard/split, centered on quote/closer; atmosphere on cover/closing only.
|
||||
- **Fabrication** — every numeral traces to the script, else placeholder.
|
||||
|
||||
## Known Gaps
|
||||
|
||||
- **Motion intentionally out of scope.** frame.md specifies composition only; the source's 500ms translateX transitions + bar-fill animations are deck mechanics.
|
||||
- **Space Grotesk + Inter via Google Fonts.** CJK pairing (Noto Sans SC 700 display / Noto Serif SC 400 body) carries over; the eyebrow's uppercase+tracking signal weakens in CJK — pair it with the accent-line.
|
||||
- **9:16 / 1:1 are guidance**; verify the floor and that the diagonal panel reflows to a band.
|
||||
- Diagonal panel (clip-path), dot grid, concentric rings, and bars are CSS-only; no external imagery is required.
|
||||
@@ -0,0 +1,223 @@
|
||||
<!--
|
||||
Blue Professional — 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 blue-professional
|
||||
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 soft cobalt-tinted card on warm cream — a 4% cobalt fill inside a 1.5px
|
||||
20%-cobalt border, soft 14px rounded corners, NO shadow (the system's premium signal
|
||||
is the absence of cast depth; the tinted card does the lift). Space Grotesk, sentence
|
||||
case, near-black glyphs. Upcoming words sit in muted gray; the current word becomes a
|
||||
solid cobalt chip — the brand's signature single-accent gesture, a pill-rounded fill
|
||||
with cream reading on top; spoken words settle to near-black ink, chip 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 tinted card — 4% cobalt fill, 1.5px 20%-cobalt border, soft 14px corners,
|
||||
NO shadow (the tinted card does the lift; cast depth is forbidden) */
|
||||
.caption-pill {
|
||||
max-width: 78%;
|
||||
padding: 20px 44px 24px;
|
||||
background: color-mix(in srgb, var(--cap-accent, #1e2bfa) 4%, var(--cap-canvas, #fdfae7));
|
||||
border: 1.5px solid color-mix(in srgb, var(--cap-accent, #1e2bfa) 20%, transparent);
|
||||
border-radius: 14px;
|
||||
}
|
||||
.caption-line {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 0.1em 0.34em;
|
||||
font-family: var(--font-display, "Space Grotesk"), system-ui, sans-serif;
|
||||
font-weight: 600;
|
||||
font-size: clamp(40px, 4.4vw, 58px);
|
||||
line-height: 1.16;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
/* upcoming — muted gray, no chip */
|
||||
.caption-word {
|
||||
display: inline-block;
|
||||
padding: 0 0.18em;
|
||||
border-radius: 0.32em;
|
||||
color: color-mix(in srgb, var(--cap-ink, #111111) 52%, var(--cap-canvas, #fdfae7));
|
||||
}
|
||||
/* current word — THE solid cobalt chip (the single-accent gesture; pill-rounded
|
||||
fill, NO shadow), cream reading on top */
|
||||
.caption-word.is-active {
|
||||
color: var(--cap-canvas, #fdfae7);
|
||||
background: var(--cap-accent, #1e2bfa);
|
||||
box-shadow: none;
|
||||
}
|
||||
/* already spoken — solid near-black ink, chip cleared */
|
||||
.caption-word.is-spoken {
|
||||
color: var(--cap-ink, #111111);
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* optional per-word accents (dormant until a word-classer tags them) */
|
||||
/* brand / key term — cobalt, the system's one accent on a key word */
|
||||
.caption-word.cap-brand {
|
||||
color: var(--cap-accent, #1e2bfa);
|
||||
}
|
||||
/* numerals / data — cobalt 700, the figure voice */
|
||||
.caption-word.cap-num {
|
||||
font-family: var(--font-display, "Space Grotesk"), system-ui, sans-serif;
|
||||
font-weight: 700;
|
||||
color: var(--cap-accent, #1e2bfa);
|
||||
}
|
||||
/* emphasis — a directional-red hairline underline (inline only, never a fill) */
|
||||
.caption-word.cap-cta {
|
||||
text-decoration: underline;
|
||||
text-decoration-thickness: 2px;
|
||||
text-decoration-color: var(--cap-accent-2, #dc2626);
|
||||
text-underline-offset: 0.1em;
|
||||
}
|
||||
|
||||
@media (max-aspect-ratio: 9/16) {
|
||||
.caption-pill {
|
||||
max-width: 92%;
|
||||
}
|
||||
.caption-line {
|
||||
font-size: clamp(34px, 5.4vw, 48px);
|
||||
}
|
||||
}
|
||||
</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.985 }, { scale: 1, duration: 0.18, ease: "power1.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
@@ -0,0 +1,275 @@
|
||||
---
|
||||
version: alpha
|
||||
name: Bold Poster — Frame (video / frame layer)
|
||||
description: >
|
||||
Video-first companion to Bold Poster's design.md. The unit is the frame (1920×1080). Atoms are
|
||||
identical and sacred — the four-color palette (white / brown-black ink / tomato red / off-white),
|
||||
the three-face stack (Shrikhand display tilted at poster scale, Libre Baskerville serif body,
|
||||
Space Grotesk mono chrome), the stacked text-shadow on red display, the 3px+1.5px double-border
|
||||
grid, red leftbar cards, red em-dash bullets, and the red progress bar. Composition + frame scale
|
||||
rewritten. 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:
|
||||
bg: "#FFFFFF"
|
||||
dark: "#1C1410"
|
||||
red: "#D8000F"
|
||||
light: "#F5F2EF"
|
||||
|
||||
typography:
|
||||
# — reading + chrome ramp —
|
||||
body: { fontFamily: "Libre Baskerville", cqw: 0.85, weight: 400, lineHeight: 1.75, color: "dark" }
|
||||
body-cell:{ fontFamily: "Libre Baskerville", cqw: 0.7, weight: 400, lineHeight: 1.55 }
|
||||
label: { fontFamily: "Space Grotesk", px: 10, weight: 600, tracking: "2px", upper: true, color: "red" }
|
||||
bullet-body:{ fontFamily: "Space Grotesk", cqw: 0.62, weight: 400, lineHeight: 1.45 }
|
||||
# — display / hero ramp (Shrikhand 400, tilted) —
|
||||
card-title:{ fontFamily: "Shrikhand", cqw: 1.9, weight: 400, lineHeight: 1.1, color: "dark" }
|
||||
cell-number:{ fontFamily: "Shrikhand", cqw: 2.7, weight: 400, lineHeight: 1.0, color: "red" }
|
||||
section-header:{ fontFamily: "Shrikhand", cqw: 3.3, weight: 400, lineHeight: 1.0, color: "dark" }
|
||||
red-quote:{ fontFamily: "Shrikhand", cqw: 4.7, weight: 400, lineHeight: 1.15, color: "bg", shadow: "stacked" }
|
||||
hero-title-bottom:{ fontFamily: "Shrikhand", cqw: 10.4, weight: 400, lineHeight: 0.9, color: "dark", rotate: "2deg" }
|
||||
hero-title:{ fontFamily: "Shrikhand", cqw: 11.5, weight: 400, lineHeight: 0.88, color: "dark" }
|
||||
hero-title-red:{ fontFamily: "Shrikhand", cqw: 13.5, weight: 400, lineHeight: 0.85, color: "red", rotate: "-4deg" }
|
||||
close-big:{ fontFamily: "Shrikhand", cqw: 13.5, weight: 400, lineHeight: 0.88, color: "red", rotate: "-5deg" }
|
||||
stat-big:{ fontFamily: "Shrikhand", cqw: 22.0, weight: 400, lineHeight: 0.82, color: "red", rotate: "-6deg" }
|
||||
|
||||
spacing:
|
||||
pad-slide: "3cqw 3.6cqw"
|
||||
gap-grid: "1.5cqw 2cqw"
|
||||
|
||||
components:
|
||||
progress-bar:
|
||||
backgroundColor: "{colors.red}"
|
||||
size: "0.5cqw tall, bottom edge, width grows with index"
|
||||
description: "The most prominent chrome."
|
||||
hero-title-stack:
|
||||
typography: "{typography.hero-title} + {typography.hero-title-red} + {typography.hero-title-bottom}"
|
||||
transform: "≥4 tilt (−4°/+2°), ≥1 line in {colors.red}"
|
||||
description: "A 3-line Shrikhand composition — the signature opener."
|
||||
stat-big:
|
||||
typography: "{typography.stat-big}"
|
||||
color: "{colors.red} (or {colors.bg} on red with stacked shadow)"
|
||||
transform: "rotate(-6deg)"
|
||||
description: "Hero numeral at poster scale."
|
||||
fin-grid:
|
||||
border: "0.3cqw solid {colors.dark} outer"
|
||||
rule: "0.15cqw solid {colors.dark} inner (touching at intersections)"
|
||||
rounded: "0"
|
||||
typography: "{typography.cell-number} ({colors.red}) + {typography.label} + {typography.body-cell}"
|
||||
description: "The double-border tabular signature."
|
||||
red-leftbar-card:
|
||||
borderLeft: "4px solid {colors.red}"
|
||||
padding: "0 0 0 ~1cqw"
|
||||
rounded: "0"
|
||||
shadow: "none"
|
||||
typography: "{typography.card-title} + {typography.body} + red em-dash bullets"
|
||||
description: "Editorial card cantilevered off a red left rule — no outline."
|
||||
red-panel:
|
||||
backgroundColor: "{colors.red}"
|
||||
textColor: "{colors.bg}"
|
||||
description: "Full-bleed statement surface; display carries the stacked text-shadow."
|
||||
dark-panel:
|
||||
backgroundColor: "{colors.dark}"
|
||||
textColor: "{colors.bg}"
|
||||
description: "Full-bleed dark statement surface; red accents."
|
||||
stacked-text-shadow:
|
||||
shadow: "2px 2px 0 rgba(28,20,16,.25), 4px 4px 0 rgba(28,20,16,.2), 6px 6px 0 rgba(28,20,16,.15)"
|
||||
appliesTo: "red display text on red panels only"
|
||||
description: "The ONLY shadow in the system — text-shadow, not box-shadow."
|
||||
bullet:
|
||||
marker: "red em-dash (—) or round (•) glyph at absolute-left"
|
||||
color: "{colors.red}"
|
||||
description: "No default disc bullets; capped at three."
|
||||
---
|
||||
|
||||
# Bold Poster — Frame (video / frame layer)
|
||||
|
||||
## Overview
|
||||
|
||||
Bold Poster at frame scale is a **populist editorial poster** — vintage Italian sports-magazine
|
||||
display, classical serif body, one saturated tomato red, grids ruled in ink. Every frame should
|
||||
feel _printed_: heavy display type at poster scale, locked to one red accent, on a white/off-white
|
||||
sheet (or a full red/dark statement panel), with decoration kept to a strict minimum.
|
||||
|
||||
The voice is a three-face stack: **Shrikhand** (heavy slab-script, weight 400 only, routinely
|
||||
tilted −6°..+2°) carries every hero title, section header, stat, and card title; **Libre
|
||||
Baskerville** (literary serif) carries every body paragraph — it's what makes the system feel
|
||||
printed; **Space Grotesk** (uppercase, 2–3px tracked) is chrome only — labels, eyebrows, counters,
|
||||
bullet bodies. The plane is flat; the _only_ shadow is the stacked text-shadow on red display.
|
||||
|
||||
**Key characteristics at frame scale:**
|
||||
|
||||
- **Four colors only** — white / brown-black ink / tomato red / off-white. Red is the lone accent.
|
||||
- **Shrikhand display, tilted** (−6° stat, −5° close, −4° hero-red, +2° hero-bottom) — the signature movement.
|
||||
- **Libre Baskerville serif body** (line 1.75); **Space Grotesk** chrome (uppercase tracked).
|
||||
- **Double-border grids** (3px outer + 1.5px inner ink); **red leftbar cards**; **red em-dash bullets** (max 3).
|
||||
- **Stacked text-shadow** on red display — the only shadow; flat plane otherwise; square corners.
|
||||
- **Red progress bar** at the bottom edge of every frame.
|
||||
|
||||
## The Frame
|
||||
|
||||
### Frame Craft Bar
|
||||
|
||||
Three eyeball tests gate every frame before any structural check:
|
||||
|
||||
- **Squint** — **one display moment dominates** at 3–6× everything else (the hero stack, `stat-big`, or `close-big`); nothing competes.
|
||||
- **Silence** — statement frames reserve **~50–60% negative space**; the **financial grid is the one dense exception**.
|
||||
- **Restraint** — **four colors only**, red the lone accent; one display moment per frame; the stacked text-shadow on red is the only shadow; bullets capped at three.
|
||||
- **Reference** — aim at a **vintage Italian sports-magazine cover / mid-century European annual report**; failure looks like a **rounded, soft-shadowed multi-accent slide**.
|
||||
|
||||
- **Primary:** 1920×1080 (16:9). Display authored in **`cqw`** (`px ÷ 1920 × 100 = cqw`).
|
||||
- **Vertical:** 1080×1920 (9:16). **Square:** 1080×1080 (1:1).
|
||||
- **Safe area:** `pad-slide` ~3cqw — deliberately tight so the poster type crowds the frame.
|
||||
|
||||
**The container law (load-bearing).** Every frame ground sets `container-type: size`; ALL
|
||||
frame-relative units are `cqw`/`cqh` against it — never `vw`. Borders stay px; rotation transforms hold.
|
||||
|
||||
## Colors
|
||||
|
||||
Tokens identical to the source. `{colors.bg}` white is the default ground; `{colors.dark}` is body,
|
||||
borders, headers; `{colors.red}` is the only accent — every numeral, section rule, eyebrow,
|
||||
leftbar, bullet, progress bar, and the full statement-panel ground. `{colors.light}` off-white
|
||||
stripes alternating panels. **No fifth color** (no green/blue/yellow); categorical difference comes
|
||||
from position, label, and tilt. Red is never body text, never a tint, never an untexted fill.
|
||||
|
||||
## Typography
|
||||
|
||||
Two ramps. The **reading/chrome ramp** (Baskerville body 0.85cqw, Space Grotesk labels in px)
|
||||
carries copy + chrome; the **display ramp** (Shrikhand `section-header` 3.3cqw → `stat-big` 22cqw)
|
||||
carries every statement and numeral.
|
||||
|
||||
- **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 → `stat-big`/`hero-title-red`; 3–4 → `hero-title`; 5+ → `section-header`. The hero is a stacked 3-line composition.
|
||||
- **Shrikhand is weight 400, tilted on statement/hero elements, red on numerals**; **Baskerville body at line ≥1.5**; **Space Grotesk chrome uppercase, 2–3px**. Inline `<strong>` switches face to Space Grotesk 600. No italic display, no untilted red hero.
|
||||
|
||||
## Depth & Surface
|
||||
|
||||
Flat plane. Depth from:
|
||||
|
||||
- **Heavy borders** — 3px+1.5px double-border grids, 2px global-card outline, 4px red leftbar rules.
|
||||
- **Surface inversion** — full-bleed red or dark statement panels.
|
||||
- **Tilt** — rotated Shrikhand breaks the baseline for perceived dimension.
|
||||
- **The single shadow** — stacked text-shadow on red display only (text-shadow, three steps).
|
||||
|
||||
**Ceiling:** no box-shadow, no rounded surface (square corners; only the hint pill is 4px), no gradient.
|
||||
|
||||
## Shapes
|
||||
|
||||
- **0 radius everywhere** except the hint pill (4px). Cards, cells, panels, callouts — sharp rectangles.
|
||||
|
||||
## Components
|
||||
|
||||
- **hero-title-stack** — the 3-line tilted opener. **stat-big** — the poster numeral.
|
||||
- **fin-grid** — the double-border tabular signature. **red-leftbar-card** — the cantilevered editorial card.
|
||||
- **red-panel / dark-panel** — statement surfaces. **stacked-text-shadow** — the one shadow.
|
||||
- **bullet** — red em-dash (max 3). **progress-bar** — the red bottom strip.
|
||||
|
||||
## Frame Treatments
|
||||
|
||||
> Recipe: ground · register · composes · focal · chrome · accent · silence · Fixed/Free · density.
|
||||
> One display moment per frame; statement frames reserve massive negative space.
|
||||
|
||||
### 1 · Hero Stack (identity · move: 3-line tilted stack · left)
|
||||
|
||||
**Ground** white. **Composes** hero-title-stack, label, body tagline, progress-bar. **Focal** a
|
||||
3-line Shrikhand stack (e.g. ink / red-tilted / paper) — one red line, one+ tilted. **Chrome** mono
|
||||
eyebrow + Baskerville tagline; bottom progress bar + counter. **Accent** the red line. **Silence**
|
||||
right half open. **Fixed** Shrikhand 400, ≥1 tilt + ≥1 red, square. **Free** the words, line sizes.
|
||||
**Density** low.
|
||||
|
||||
### 2 · Hero Stat (statement · move: poster numeral · red panel · centered)
|
||||
|
||||
**Ground** full `{colors.red}`. **Composes** stat-big, label, body sub. **Focal** a `stat-big`
|
||||
numeral rotated −6° in white with the stacked shadow, centered, with a mono label above + Baskerville
|
||||
sub below. **Accent** the ink stacked-shadow on white. **Silence** ~60%. **Fixed** white-on-red +
|
||||
stacked shadow, −6° tilt. **Free** the figure (from script), label. **Density** low.
|
||||
|
||||
### 3 · Financial Grid (data · move: double-border matrix · the dense frame)
|
||||
|
||||
**Ground** white, `pad-slide`. **Composes** label, section-header, fin-grid. **Focal** a 3px-outer/
|
||||
1.5px-inner ink grid of cells (red Shrikhand numeral + mono label + Baskerville body). **Chrome** mono
|
||||
eyebrow; progress bar. **Accent** the red numerals. **Silence** tight — the density exception.
|
||||
**Fixed** double-border, red numerals, serif body. **Free** figures (from script), labels. **Density** dense-exception.
|
||||
|
||||
### 4 · Pull Quote (quote · move: tilted/stacked display · red panel · left)
|
||||
|
||||
**Ground** full `{colors.red}`. **Composes** red-quote, Baskerville cite. **Focal** a 2-line Shrikhand
|
||||
quote in white with the stacked shadow; a Baskerville cite beneath. **Accent** the stacked shadow.
|
||||
**Silence** ~50%. **Fixed** white-on-red + stacked shadow. **Free** quote, cite. **Density** low.
|
||||
|
||||
### 5 · Editorial Cards (content · move: red leftbar cards · left)
|
||||
|
||||
**Ground** white (or alternating off-white stripes). **Composes** label, section-header, 2–3×
|
||||
red-leftbar-card. **Focal** cards cantilevered off 4px red rules — Shrikhand title + Baskerville body
|
||||
|
||||
- red em-dash bullets (max 3). **Accent** the red left rules + bullets. **Silence** moderate. **Fixed**
|
||||
4px red leftbar, em-dash bullets, no outline. **Free** card content. **Density** standard.
|
||||
|
||||
### 6 · Closing Statement (closer · move: tilted close-big · centered/left)
|
||||
|
||||
**Ground** white or `{colors.dark}`. **Composes** close-big, label, body sub, progress-bar. **Focal**
|
||||
a Shrikhand `close-big` (rotated −5°, red) sign-off; mono eyebrow + Baskerville contact line. **Accent**
|
||||
the red tilted title. **Silence** ~60%. **Fixed** −5° tilt, red close-big. **Free** sign-off, contact.
|
||||
**Density** low.
|
||||
|
||||
## Composition Rules
|
||||
|
||||
### Do
|
||||
|
||||
- Stack hero titles in 3 Shrikhand lines — at least one tilted, at least one red.
|
||||
- Make every numeral **red Shrikhand**; tilt statement display **−5° to −6°**.
|
||||
- Set eyebrows in **Space Grotesk 600 uppercase, 2–3px**, red; body in **Baskerville, line 1.75**.
|
||||
- Build data grids with the **3px outer + 1.5px inner double border**; use red leftbar cards elsewhere.
|
||||
- Use **red em-dash bullets, capped at three**; apply the stacked text-shadow on red display.
|
||||
- One display moment per frame; reserve negative space on statement frames.
|
||||
|
||||
### Don't
|
||||
|
||||
- No second accent color; no rounded corners (square only, save the hint pill).
|
||||
- No drop shadow — the stacked text-shadow on red is the only one.
|
||||
- No font substitutes; no Shrikhand body, no Baskerville labels, no Space Grotesk headlines.
|
||||
- No default disc bullets; no untilted red statement display.
|
||||
- Don't crowd a statement frame; don't blow a long line edge-to-edge (step down).
|
||||
|
||||
## Aspect-Ratio Behavior
|
||||
|
||||
| Treatment | 16:9 | 9:16 | 1:1 |
|
||||
| --------------- | ------------------------- | ------------------------ | -------------------- |
|
||||
| Hero Stack | stack left, tagline below | stack centered, taller | stack, tagline below |
|
||||
| Hero Stat | numeral centered | numeral centered, taller | centered |
|
||||
| Financial Grid | 3 cells across | 2 across / stacked | 2×2 |
|
||||
| Pull Quote | quote left | quote stacked | centered |
|
||||
| Editorial Cards | 2–3 across | stacked | stacked |
|
||||
| Closing | close-big centered | close-big stacked | centered |
|
||||
|
||||
`pad-slide` stays tight on the short edge; re-step display so the line stays ≤78cqw above the floor.
|
||||
Tilts hold; the progress bar spans the bottom on every ratio.
|
||||
|
||||
## Approved Entities
|
||||
|
||||
No real customers, logos, or vendors are defined in the source — render any such mark as a
|
||||
placeholder. The system supplies type, one red, and ink rules, not brands.
|
||||
|
||||
## Numerals & Claims (hard rule)
|
||||
|
||||
Never invent figures, financials, percentages, or dates at frame scale. Render slots as `— figure —`,
|
||||
`{metric}`, `+NN%`. Fin-grid cells and stat-big especially carry placeholders until the script
|
||||
supplies them. Catalogue numbers / progress are decorative.
|
||||
|
||||
## Pre-Render Self-Audit
|
||||
|
||||
- **Squint** — one display moment dominates; nothing competes.
|
||||
- **Silence** — statement frames reserve ~50–60% negative space; only the fin-grid runs dense.
|
||||
- **Color** — four colors only; red is the lone accent; no red body text.
|
||||
- **Type** — Shrikhand 400 tilted on statements, red numerals, fit-to-measure; Baskerville body line 1.75; mono chrome uppercase 2–3px; ≥1.4cqw floor.
|
||||
- **Depth** — flat; the stacked text-shadow on red display is the only shadow; square corners.
|
||||
- **Bullets** — red em-dash, capped at three.
|
||||
- **Fabrication** — every numeral traces to the script, else placeholder.
|
||||
|
||||
## Known Gaps
|
||||
|
||||
- **Motion intentionally out of scope.** frame.md specifies composition only; the source's 550ms slide transitions are deck mechanics.
|
||||
- **Shrikhand + Libre Baskerville + Space Grotesk via Google Fonts.** CJK: Noto Serif SC 900/400 + Noto Sans SC; Shrikhand's slab-script has no Hanzi equal — keep tilts + red + double-borders to carry the identity.
|
||||
- **9:16 / 1:1 are guidance**; verify the one big line ≤78cqw per ratio and that tilts don't clip.
|
||||
- Grids, leftbar cards, and the stacked shadow are CSS-only; no external imagery is required.
|
||||
@@ -0,0 +1,230 @@
|
||||
<!--
|
||||
Bold Poster — 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 bold-poster
|
||||
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 fresh-paper card with bold poster framing — a heavy 1.5px brown-black ink
|
||||
rule plus a 4px tomato-red leftbar (the brand's cantilevered-card signature), SQUARE
|
||||
corners, NO drop shadow (the system's only shadow is the stacked text-shadow on red
|
||||
display). Serif body face, ink glyphs. Upcoming words sit in faint ink; the current
|
||||
word slams into a tomato-red panel (the brand's signature surface inversion, a hard
|
||||
square block on paper-white reading, no blur) with the stacked text-shadow on it;
|
||||
spoken words settle to solid ink, panel 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 paper card — heavy 1.5px brown-black ink rule + a 4px tomato-red leftbar
|
||||
(bold poster's cantilevered red-leftbar-card signature), square corners, NO
|
||||
drop shadow (the only shadow in the system is the stacked text-shadow on red) */
|
||||
.caption-pill {
|
||||
max-width: 78%;
|
||||
padding: 20px 44px 24px;
|
||||
background: var(--cap-canvas, #ffffff);
|
||||
border: 1.5px solid var(--cap-ink, #1c1410);
|
||||
border-left: 4px solid var(--cap-accent, #d8000f);
|
||||
border-radius: 0;
|
||||
}
|
||||
.caption-line {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 0.1em 0.34em;
|
||||
font-family: var(--font-display, "Shrikhand"), Georgia, serif;
|
||||
font-weight: 400;
|
||||
font-size: clamp(40px, 4.4vw, 58px);
|
||||
line-height: 1.16;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
/* upcoming — faint brown-black ink, no panel */
|
||||
.caption-word {
|
||||
display: inline-block;
|
||||
padding: 0 0.06em;
|
||||
color: color-mix(in srgb, var(--cap-ink, #1c1410) 42%, var(--cap-canvas, #ffffff));
|
||||
}
|
||||
/* current word — THE tomato-red panel (box-shadow spread = a hard square block
|
||||
w/o layout shift; no blur), paper-white reading on top carrying the stacked
|
||||
text-shadow — bold poster's surface-inversion + only-shadow signatures */
|
||||
.caption-word.is-active {
|
||||
color: var(--cap-canvas, #ffffff);
|
||||
background: var(--cap-accent, #d8000f);
|
||||
box-shadow: 0 0 0 0.06em var(--cap-accent, #d8000f);
|
||||
text-shadow:
|
||||
0.03em 0.03em 0 rgba(28, 20, 16, 0.25),
|
||||
0.06em 0.06em 0 rgba(28, 20, 16, 0.2),
|
||||
0.09em 0.09em 0 rgba(28, 20, 16, 0.15);
|
||||
}
|
||||
/* already spoken — solid brown-black ink, panel cleared */
|
||||
.caption-word.is-spoken {
|
||||
color: var(--cap-ink, #1c1410);
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
/* optional per-word accents (dormant until a word-classer tags them) */
|
||||
/* brand / key term — Shrikhand display, bold poster's display face inflection */
|
||||
.caption-word.cap-brand {
|
||||
font-family: var(--font-display, "Shrikhand"), Georgia, serif;
|
||||
}
|
||||
/* numerals / data — the mono chrome voice, slightly recessed */
|
||||
.caption-word.cap-num {
|
||||
font-family: var(--font-body, "Libre Baskerville"), ui-monospace, monospace;
|
||||
color: color-mix(in srgb, var(--cap-ink, #1c1410) 72%, var(--cap-canvas, #ffffff));
|
||||
}
|
||||
/* emphasis — a tomato-red em-dash-weight underline (the lone accent) */
|
||||
.caption-word.cap-cta {
|
||||
text-decoration: underline;
|
||||
text-decoration-thickness: 2px;
|
||||
text-decoration-color: var(--cap-accent-2, #f5f2ef);
|
||||
text-underline-offset: 0.1em;
|
||||
}
|
||||
|
||||
@media (max-aspect-ratio: 9/16) {
|
||||
.caption-pill {
|
||||
max-width: 92%;
|
||||
}
|
||||
.caption-line {
|
||||
font-size: clamp(34px, 5.4vw, 48px);
|
||||
}
|
||||
}
|
||||
</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.9 }, { scale: 1, duration: 0.16, ease: "back.out(2.2)" }, 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
@@ -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 (400–900)
|
||||
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 3–6× everything else; nothing competes.
|
||||
- **Silence** — declarative frames read **45–55% 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 700–900) 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`; 3–4 → `h1`; 5+ → `h2`. Broadside packs only ONE display moment per frame.
|
||||
- **Barlow display is lowercase, weight 700–900, 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 1–2 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 2–4 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 ~45–55% 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
@@ -0,0 +1,284 @@
|
||||
---
|
||||
version: alpha
|
||||
name: Capsule — Frame (video / frame layer)
|
||||
description: >
|
||||
Video-first companion to Capsule's design.md. The unit is the frame (1920×1080). Atoms are
|
||||
identical and sacred — the pill geometry (9999px small / 2rem cards) with a 2px ink outline on
|
||||
everything, the sun-bleached cream canvas, the nine-color candy palette, Bodoni Moda + Space
|
||||
Grotesk, soft hard-offset shadows (4/6/8/12px in 8% ink), floating decorative-pill wallpaper,
|
||||
radial accent glows, and the grain overlay. Composition + frame scale rewritten. 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:
|
||||
cream: "#F5F5F0"
|
||||
ink: "#1A1A1A"
|
||||
outline: "#1E1E1E"
|
||||
white: "#FFFFFF"
|
||||
coral: "#E85D4E"
|
||||
lime: "#C4D94E"
|
||||
lavender: "#C5B5E0"
|
||||
sky: "#8BB4F7"
|
||||
violet: "#A06CE8"
|
||||
yellow: "#F2D160"
|
||||
peach: "#F5B895"
|
||||
mint: "#A8E6CF"
|
||||
shadow: "rgba(26,26,26,0.08)"
|
||||
|
||||
typography:
|
||||
# — reading ramp (Space Grotesk) —
|
||||
body: { fontFamily: "Space Grotesk", cqw: 0.85, weight: 400, lineHeight: 1.6 }
|
||||
subtitle: { fontFamily: "Space Grotesk", cqw: 0.95, weight: 400, tracking: "0.18em", upper: true }
|
||||
pill-text: { fontFamily: "Space Grotesk", cqw: 0.75, weight: 600, tracking: "0.12em", upper: true }
|
||||
label: { fontFamily: "Space Grotesk", px: 14, weight: 500, tracking: "0.1em", upper: true }
|
||||
# — display / hero ramp (Bodoni Moda, ink, sentence case) —
|
||||
card-headline:{ fontFamily: "Bodoni Moda", cqw: 1.9, weight: 700, lineHeight: 1.1 }
|
||||
orbit-numeral:{ fontFamily: "Bodoni Moda", cqw: 2.6, weight: 700, lineHeight: 1.0 }
|
||||
quote-display:{ fontFamily: "Bodoni Moda", cqw: 4.2, weight: 600, lineHeight: 1.3, tracking: "-0.01em" }
|
||||
stat-number: { fontFamily: "Bodoni Moda", cqw: 3.6, weight: 800, lineHeight: 1.0, tracking: "-0.03em" }
|
||||
section-headline:{ fontFamily: "Bodoni Moda", cqw: 4.0, weight: 700, lineHeight: 1.05, tracking: "-0.01em" }
|
||||
headline: { fontFamily: "Bodoni Moda", cqw: 5.0, weight: 700, lineHeight: 1.05, tracking: "-0.02em" }
|
||||
closing-display:{ fontFamily: "Bodoni Moda", cqw: 8.5, weight: 800, lineHeight: 0.95, tracking: "-0.03em" }
|
||||
display: { fontFamily: "Bodoni Moda", cqw: 12.0, weight: 800, lineHeight: 0.88, tracking: "-0.03em" }
|
||||
|
||||
spacing:
|
||||
pad: "5cqw"
|
||||
gap-md: "2cqw"
|
||||
card-pad: "2cqw 1.6cqw"
|
||||
|
||||
components:
|
||||
pill:
|
||||
rounded: "9999px (small) / 2rem (cards)"
|
||||
border: "0.2cqw solid {colors.outline}"
|
||||
backgroundColor: "any candy or {colors.white}"
|
||||
description: "The universal container — every chip/button/label/stat tile/node/bar. No unstroked pill exists."
|
||||
pill-card:
|
||||
backgroundColor: "{colors.white}"
|
||||
border: "0.2cqw solid {colors.outline}"
|
||||
rounded: "2rem"
|
||||
shadow: "0.4cqw 0.4cqw 0 {colors.shadow}"
|
||||
typography: "{typography.card-headline} + {typography.body}"
|
||||
description: "Holds a circular card-icon, Bodoni headline, Space Grotesk body."
|
||||
stat-pill:
|
||||
backgroundColor: "{colors.white}"
|
||||
border: "0.2cqw solid {colors.outline}"
|
||||
rounded: "2rem"
|
||||
shadow: "0.3cqw 0.3cqw 0 {colors.shadow}"
|
||||
typography: "{typography.stat-number} (COLORED) + {typography.pill-text}"
|
||||
description: "The one place color touches a numeral; + a 40px accent bar."
|
||||
title-pill:
|
||||
backgroundColor: "{colors.yellow}"
|
||||
border: "0.2cqw solid {colors.outline}"
|
||||
rounded: "9999px"
|
||||
shadow: "0.3cqw 0.3cqw 0 {colors.shadow}"
|
||||
typography: "{typography.pill-text}"
|
||||
description: "Sits above the display headline on cover/closing."
|
||||
card-icon:
|
||||
backgroundColor: "any candy"
|
||||
border: "0.2cqw solid {colors.outline}"
|
||||
rounded: "50%"
|
||||
size: "60px"
|
||||
typography: "Bodoni numeral/letter in {colors.ink}"
|
||||
description: "Card mark."
|
||||
floating-pill:
|
||||
backgroundColor: "any candy"
|
||||
border: "0.2cqw solid {colors.outline}"
|
||||
rounded: "9999px / 50%"
|
||||
transform: "rotate(−20°..+25°)"
|
||||
shadow: "none"
|
||||
typography: "{typography.pill-text}"
|
||||
description: "Decorative wallpaper confetti, 5–8 per declarative frame. Flat — no shadow."
|
||||
quote-highlight:
|
||||
backgroundColor: "{colors.lime} / {colors.sky}"
|
||||
border: "0.2cqw solid {colors.outline}"
|
||||
rounded: "9999px"
|
||||
description: "Inline candy pill wrapping a phrase inside a Bodoni quote — the emphasis mechanism, replacing bold/italic."
|
||||
bar-track:
|
||||
backgroundColor: "{colors.cream}"
|
||||
border: "0.2cqw solid {colors.outline}"
|
||||
rounded: "9999px"
|
||||
fill: "child candy pill, value at right edge"
|
||||
description: "36px pill chart track."
|
||||
accent-line:
|
||||
backgroundColor: "{colors.coral}"
|
||||
rounded: "9999px"
|
||||
size: "60×4 (80×4 closing)"
|
||||
description: "Coral pill rule."
|
||||
atmosphere:
|
||||
background: "1–3 radial candy glows (6–15% opacity) over {colors.cream} + 4% grain overlay"
|
||||
description: "Baseline canvas layers on every frame. Never absent."
|
||||
---
|
||||
|
||||
# Capsule — Frame (video / frame layer)
|
||||
|
||||
## Overview
|
||||
|
||||
Capsule at frame scale is a **playful editorial system where every container is a pill.** The
|
||||
`border-radius: 9999px` (small) / `2rem` (cards) rule plus a 2px ink outline wraps every chip,
|
||||
card, icon, bar, and node — inflated, friendly, graphically distinct. The canvas is sun-bleached
|
||||
cream warmed by soft radial candy glows and a permanent 4% grain overlay.
|
||||
|
||||
The voice is a two-face conversation: **Bodoni Moda** (didone serif, weight 700–800, always ink,
|
||||
always sentence case) carries every headline, stat, and quote; **Space Grotesk** carries body and
|
||||
all uppercase tracked pill/label text. Nine candy accents fill pills interchangeably with no
|
||||
semantic meaning. Depth is a **soft hard-offset shadow** (4/6/8/12px in 8% ink) — lifted, not
|
||||
stamped — reserved for content-bearing containers; decorative floating pills are flat.
|
||||
|
||||
**Key characteristics at frame scale:**
|
||||
|
||||
- **Pill geometry everywhere** — 9999px small, 2rem cards — each wrapped in a 2px `{colors.outline}` stroke.
|
||||
- **Bodoni Moda** display (ink, sentence case) + **Space Grotesk** body/pills (uppercase, tracked).
|
||||
- **Nine candy accents**, interchangeable; color touches stat numerals + pill fills, never headlines.
|
||||
- **Soft offset shadows** (4/6/8/12px, 8% ink) on content containers only; floating pills are flat.
|
||||
- **Floating decorative-pill wallpaper** (5–8, tilted) + radial glows + 4% grain on declarative frames.
|
||||
- **Cream canvas** warmed by atmosphere — bare corners read as broken.
|
||||
|
||||
## The Frame
|
||||
|
||||
### Frame Craft Bar
|
||||
|
||||
Three eyeball tests gate every frame before any structural check:
|
||||
|
||||
- **Squint** — one Bodoni headline or stat dominates at **3–6× its nearest neighbor**.
|
||||
- **Silence** — declarative frames carry **atmosphere, not clutter** (pills as wallpaper, not content); the **pillar-cards and stat grid are the dense exception**.
|
||||
- **Restraint** — every pill carries the 2px outline; color touches **fills + stat numerals only** (never a headline); soft shadow on content-bearing pills only (floating pills are flat); no tenth accent.
|
||||
- **Reference** — aim at a **Memphis / ice-cream-parlor editorial spread**; failure looks like a **flat, sticker-less SaaS card grid**.
|
||||
|
||||
- **Primary:** 1920×1080 (16:9). Display authored in **`cqw`** (`px ÷ 1920 × 100 = cqw`).
|
||||
- **Vertical:** 1080×1920 (9:16). **Square:** 1080×1080 (1:1).
|
||||
- **Safe area:** `pad` (5cqw); floating pills may bleed off edges as wallpaper.
|
||||
|
||||
**The container law (load-bearing).** Every frame ground sets `container-type: size` AND carries
|
||||
the radial-glow + grain atmosphere; ALL frame-relative units are `cqw`/`cqh` against it — never
|
||||
`vw`. Pill radii stay `9999px`/`2rem` (shape, not scale); shadow offsets scale in `cqw`.
|
||||
|
||||
## Colors
|
||||
|
||||
Tokens identical to the source. `{colors.cream}` is the ground; `{colors.ink}`/`{colors.outline}`
|
||||
is type + the universal 2px stroke; the nine candy accents fill pills with **no semantic mapping**
|
||||
— pair a warm (coral/yellow/peach) with a cool (sky/lavender/violet/mint) with a neutral-bright
|
||||
(lime); never two same-family adjacent. Color appears on **stat numerals and pill fills only** —
|
||||
never on a Bodoni headline. Shadow is always `{colors.shadow}` (8% ink). No tenth color.
|
||||
|
||||
## Typography
|
||||
|
||||
Two ramps. The **reading ramp** (Space Grotesk body 0.85cqw, uppercase tracked pill/label text)
|
||||
carries copy + chips; the **display ramp** (Bodoni `card-headline` 1.9cqw → `display` 12cqw, all
|
||||
700–800) carries every headline, stat, and quote.
|
||||
|
||||
- **Legibility floor:** any load-bearing line ≥ **1.4cqw**; px labels are chrome only.
|
||||
- **Fit-to-measure:** size the headline to its length. Cap the block at **≤ 78cqw**; ≤3 words → `display`/`closing-display`; 4–6 → `headline`; 7+ → `section-headline`.
|
||||
- **Bodoni 700+ in ink, sentence case** (italic via `<em>` only, color allowed on the italic key word as a candy hue); **Space Grotesk pills/labels uppercase, 0.08em+ tracked**; negative-track Bodoni display.
|
||||
|
||||
## Depth & Surface
|
||||
|
||||
Soft hard-offset shadow is the only depth, in `{colors.shadow}` (8% ink), bottom-right:
|
||||
|
||||
- **0.2cqw (4px)** small nodes; **0.3cqw (6px)** stat-pills, orbit pills, diagram nodes; **0.4cqw (8px)** pillar-cards; **0.6cqw (12px)** the visual frame.
|
||||
- The **2px outline** does most of the lift against cream; the shadow adds the float.
|
||||
- **Decorative floating pills cast no shadow** — this separates content from atmosphere at a glance.
|
||||
|
||||
**Ceiling:** no blurred shadow, no re-colored shadow, no gradient depth.
|
||||
|
||||
## Shapes
|
||||
|
||||
- **9999px** — all small pills (chips, buttons, bars, nodes, floating pills, quote highlights, accent lines).
|
||||
- **2rem** — larger cards (pillar-card, stat-pill, chart container, visual frame).
|
||||
- **50%** — circular pills (card-icon 60px, step-node 56px, orbit-center 160px, nav dots).
|
||||
- **0** — only the grain overlay and the gradient region inside a visual frame. No sharp-cornered text container exists.
|
||||
|
||||
## Components
|
||||
|
||||
- **pill** — the universal 2px-outlined container. **pill-card / stat-pill / title-pill** — the white/yellow content pills with soft shadows.
|
||||
- **card-icon** — circular candy mark; **floating-pill** — flat tilted wallpaper confetti; **quote-highlight** — inline candy emphasis pill.
|
||||
- **bar-track** — pill-shaped chart bar; **accent-line** — coral pill rule; **atmosphere** — the baseline glow + grain on every frame.
|
||||
|
||||
## Frame Treatments
|
||||
|
||||
> Recipe: ground · container · composes · focal · chrome · accent · silence · Fixed/Free · density.
|
||||
> Atmosphere (glow + grain) on every frame; declarative frames carry floating-pill wallpaper.
|
||||
|
||||
### 1 · Cover (identity · move: title pill + display · centered)
|
||||
|
||||
**Ground** cream + atmosphere, floating-pill wallpaper. **Composes** title-pill, display, accent-line, floating-pills (5–8). **Focal** a 1–2 line Bodoni `display` headline in ink (italic key word in a candy hue), centered, under a yellow title-pill; a coral accent-line below. **Chrome** uppercase Space Grotesk sub. **Accent** the italic word + candy floating pills. **Silence** content centered; pills fill the edges. **Fixed** pill outlines, Bodoni ink, flat floating pills. **Free** title, which candy hues, pill words/positions. **Density** medium-atmospheric.
|
||||
|
||||
### 2 · Pillar Cards (catalog · move: 3-up grid · left — the dense frame)
|
||||
|
||||
**Ground** cream + atmosphere. **Composes** title-pill (lavender), section-headline, 3× pill-card. **Focal** three white 2rem pill-cards (circular candy card-icon, Bodoni card-headline, Space Grotesk body) with 0.4cqw shadows, under a Bodoni section-headline. **Chrome** a lavender header tag-pill. **Accent** the three card-icon fills (coral/sky/lime sequence). **Silence** tight — the density exception (no floating pills here). **Fixed** 2px outlines, soft shadows, ink headlines. **Free** card content, icon hues. **Density** dense-exception.
|
||||
|
||||
### 3 · Stat Grid (data · move: stat pills · centered head)
|
||||
|
||||
**Ground** cream + atmosphere. **Composes** section-headline, 3–4× stat-pill. **Focal** a row of white stat-pills, each a COLORED Bodoni stat-number + uppercase label + accent bar. **Accent** the colored numerals + bars (the one place color meets type). **Silence** moderate. **Fixed** 2rem pills, colored numerals only, soft shadow. **Free** figures (from script), hues. **Density** standard.
|
||||
|
||||
### 4 · Pull Quote (quote · move: highlight pill · left)
|
||||
|
||||
**Ground** cream + atmosphere, a few floating pills. **Composes** quote-display, quote-highlight, accent-line. **Focal** a Bodoni quote in ink with one phrase wrapped in a lime/sky `quote-highlight` pill (the emphasis mechanism — never bold). **Chrome** uppercase attribution. **Accent** the highlight pill. **Silence** ~50%. **Fixed** Bodoni 600, highlight-pill emphasis. **Free** quote, highlight color/phrase. **Density** sparse.
|
||||
|
||||
### 5 · Orbit (concept · move: gravitational pills · centered)
|
||||
|
||||
**Ground** cream + atmosphere. **Composes** orbit-center (160px lime circle, Bodoni ordinal), 4–6 orbit-pill satellites (candy, tilted, soft shadow). **Focal** the lime center anchored by orbiting candy pills. **Accent** the satellite hues. **Silence** moderate. **Fixed** circular center, outlined satellites with shadow. **Free** ordinal, satellite words/positions. **Density** medium.
|
||||
|
||||
### 6 · Closing Plate (closer · move: title pill + display · centered)
|
||||
|
||||
**Ground** cream + atmosphere, floating-pill wallpaper. **Composes** title-pill (yellow), closing-display, accent-line, floating-pills. **Focal** a Bodoni `closing-display` sign-off in ink (italic word in violet/candy), centered. **Accent** the italic word + pills. **Silence** content centered. **Fixed** Bodoni ink, flat pills. **Free** sign-off, pill words. **Density** medium-atmospheric.
|
||||
|
||||
## Composition Rules
|
||||
|
||||
### Do
|
||||
|
||||
- Make every text container a **pill** (9999px / 2rem) with the **2px ink outline**.
|
||||
- Set **Bodoni headlines in ink, sentence case**; color lives on stat numerals + pill fills.
|
||||
- Use **soft offset shadows** (4/6/8/12px, 8% ink) on content containers; keep floating pills flat.
|
||||
- Float **5–8 candy pills** + radial glows + grain on declarative frames; bare corners read as broken.
|
||||
- Wrap inline emphasis in a **candy quote-highlight pill**, never bold/italic-alone.
|
||||
- Pair warm+cool+neutral-bright accents; lean centered on cover/closer, left on cards/quote.
|
||||
|
||||
### Don't
|
||||
|
||||
- No sharp-cornered text container; no unstroked pill.
|
||||
- No colored Bodoni headline; no uppercase Bodoni.
|
||||
- No blurred or re-colored shadow; no tenth accent color.
|
||||
- No shadow on decorative floating pills.
|
||||
- Don't blow a headline edge-to-edge — fit to measure.
|
||||
|
||||
## Aspect-Ratio Behavior
|
||||
|
||||
| Treatment | 16:9 | 9:16 | 1:1 |
|
||||
| ------------ | ------------------------------ | --------------------------- | --------------------- |
|
||||
| Cover | display centered, pills around | display top, pills band | centered, fewer pills |
|
||||
| Pillar Cards | head + 3-up | head + 3 stacked | head + 2+1 |
|
||||
| Stat Grid | 3–4 across | 2×2 | 2×2 |
|
||||
| Pull Quote | quote left | quote top, highlight inline | centered |
|
||||
| Orbit | center + 4–6 satellites | center + 3 satellites | center + 4 |
|
||||
| Closing | centered + pills | centered, pills band | centered |
|
||||
|
||||
`pad` holds on the short edge; re-step display above the 1.4cqw floor. Floating-pill count drops
|
||||
on tighter ratios so they stay wallpaper, not clutter.
|
||||
|
||||
## Approved Entities
|
||||
|
||||
No real customers, logos, or vendors are defined in the source — render any such mark as a
|
||||
placeholder. Floating-pill words are neutral atmospheres ("VISION", "FUTURE", "NEXT"), never content-specific.
|
||||
|
||||
## Numerals & Claims (hard rule)
|
||||
|
||||
Never invent figures, stats, or counts at frame scale. Render slots as `— figure —`, `{metric}`,
|
||||
`N%`. Stat-pill numbers and bar-track widths carry placeholders until the script supplies values.
|
||||
|
||||
## Pre-Render Self-Audit
|
||||
|
||||
- **Squint** — one Bodoni element dominates at 3–5× its neighbor.
|
||||
- **Silence** — declarative frames carry atmosphere not clutter; only pillar/stat grids run dense.
|
||||
- **Pills** — every container is a pill with a 2px outline; no sharp text container.
|
||||
- **Color** — candy on fills + stat numerals only; headlines ink; no tenth hue.
|
||||
- **Depth** — soft offset shadow on content only; floating pills flat; no blur.
|
||||
- **Type** — Bodoni ink sentence-case, fit-to-measure; pills uppercase tracked; ≥1.4cqw floor.
|
||||
- **Anchor** — centered on cover/closer/orbit, left on cards/quote; no 3 consecutive alike.
|
||||
- **Fabrication** — every numeral traces to the script, else placeholder.
|
||||
|
||||
## Known Gaps
|
||||
|
||||
- **Motion intentionally out of scope.** frame.md specifies composition only; the 0.6s fade in the source is a deck mechanic.
|
||||
- **Bodoni Moda + Space Grotesk via Google Fonts.** CJK pairing (ZCOOL XiaoWei / Yozai) carries over from the source.
|
||||
- **9:16 / 1:1 are guidance**; verify the floor and that floating-pill count scales down.
|
||||
- The grain overlay, radial glows, floating pills, and bar fills are CSS-only; no external imagery is required.
|
||||
@@ -0,0 +1,224 @@
|
||||
<!--
|
||||
Capsule — 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 capsule 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 cream caption pill — fully-rounded (9999px), wrapped in capsule's universal
|
||||
2px ink outline, lifted on a soft hard-offset shadow (no blur), Bodoni Moda glyphs in
|
||||
ink, sentence case. Upcoming words sit in faint ink; the current word lights up as a
|
||||
rounded candy highlight — a candy fill plus a box-shadow spread halo that follows the
|
||||
pill radius (a soft candy capsule around the word, no blur, NO layout shift) with ink
|
||||
reading on top; spoken words settle to solid ink, the candy highlight 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 caption capsule — cream pill, fully-rounded (9999px), capsule's universal 2px
|
||||
ink outline, lifted on a soft hard-offset shadow (no blur, 8% ink) */
|
||||
.caption-pill {
|
||||
max-width: 80%;
|
||||
padding: 18px 46px 20px;
|
||||
background: var(--cap-canvas, #f5f5f0);
|
||||
border: 2px solid var(--cap-ink, #1a1a1a);
|
||||
border-radius: 9999px;
|
||||
box-shadow: 8px 8px 0 color-mix(in srgb, var(--cap-ink, #1a1a1a) 8%, transparent);
|
||||
}
|
||||
.caption-line {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 0.12em 0.36em;
|
||||
font-family: var(--font-display, "Bodoni Moda"), Georgia, serif;
|
||||
font-weight: 700;
|
||||
font-size: clamp(38px, 4.2vw, 56px);
|
||||
line-height: 1.16;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
/* upcoming — faint ink, no highlight */
|
||||
.caption-word {
|
||||
display: inline-block;
|
||||
padding: 0 0.1em;
|
||||
border-radius: 9999px;
|
||||
color: color-mix(in srgb, var(--cap-ink, #1a1a1a) 42%, var(--cap-canvas, #f5f5f0));
|
||||
}
|
||||
/* current word — THE rounded candy highlight: a candy fill + a box-shadow spread
|
||||
halo that follows the pill radius (a soft candy capsule around the word, no blur,
|
||||
no layout shift), ink reading on top */
|
||||
.caption-word.is-active {
|
||||
color: var(--cap-ink, #1a1a1a);
|
||||
background: var(--cap-accent, #e85d4e);
|
||||
box-shadow: 0 0 0 0.14em var(--cap-accent, #e85d4e);
|
||||
}
|
||||
/* already spoken — solid ink, candy highlight cleared */
|
||||
.caption-word.is-spoken {
|
||||
color: var(--cap-ink, #1a1a1a);
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* optional per-word accents (dormant until a word-classer tags them) */
|
||||
/* brand / key term — Bodoni Moda italic, capsule's display inflection */
|
||||
.caption-word.cap-brand {
|
||||
font-style: italic;
|
||||
}
|
||||
/* numerals / data — the Space Grotesk voice, slightly recessed */
|
||||
.caption-word.cap-num {
|
||||
font-family: var(--font-body, "Space Grotesk"), ui-sans-serif, sans-serif;
|
||||
color: color-mix(in srgb, var(--cap-ink, #1a1a1a) 72%, var(--cap-canvas, #f5f5f0));
|
||||
}
|
||||
/* emphasis — a second-candy rounded underline (the counter-accent) */
|
||||
.caption-word.cap-cta {
|
||||
text-decoration: underline;
|
||||
text-decoration-thickness: 3px;
|
||||
text-decoration-color: var(--cap-accent-2, #f2d160);
|
||||
text-underline-offset: 0.12em;
|
||||
}
|
||||
|
||||
@media (max-aspect-ratio: 9/16) {
|
||||
.caption-pill {
|
||||
max-width: 92%;
|
||||
}
|
||||
.caption-line {
|
||||
font-size: clamp(34px, 5.4vw, 48px);
|
||||
}
|
||||
}
|
||||
</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.18, ease: "back.out(2)" }, 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
@@ -0,0 +1,278 @@
|
||||
---
|
||||
version: alpha
|
||||
name: Cartesian — Frame (video / frame layer)
|
||||
description: >
|
||||
Video-first companion to Cartesian's design.md. The unit is the frame (1920×1080). Atoms are
|
||||
identical and sacred — the five-tone warm-stone palette, Playfair Display 400 + Inter, the
|
||||
universal 1px taupe hairline as the only structural device, compass-drafted geometric rings,
|
||||
and zero shadow / zero fill. Composition, frame scale, and aspect-ratio behavior are rewritten
|
||||
for the frame. Restraint is the rule; motion is 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:
|
||||
bg-primary: "#EDE8E0"
|
||||
bg-secondary: "#E2DBD1"
|
||||
text-primary: "#1A1A1A"
|
||||
text-secondary: "#5A5A5A"
|
||||
accent: "#8A8178"
|
||||
line: "#B8B0A4"
|
||||
white-overlay: "rgba(255,255,255,0.3)"
|
||||
|
||||
typography:
|
||||
# — reading ramp (Inter) —
|
||||
body: { fontFamily: "Inter", cqw: 1.0, weight: 400, lineHeight: 1.6, color: "text-secondary" }
|
||||
body-sm: { fontFamily: "Inter", cqw: 0.85, weight: 400, lineHeight: 1.6 }
|
||||
subtitle: { fontFamily: "Inter", cqw: 1.3, weight: 400, lineHeight: 1.5 }
|
||||
label: { fontFamily: "Inter", px: 14, weight: 500, tracking: "3px", upper: true, color: "accent" }
|
||||
attribution: { fontFamily: "Inter", px: 15, weight: 400, tracking: "2px", upper: true, color: "accent" }
|
||||
micro: { fontFamily: "Inter", px: 12, weight: 400, tracking: "2px", upper: true, color: "accent" }
|
||||
# — display / hero ramp (Playfair Display 400, sentence case) —
|
||||
h3: { fontFamily: "Playfair Display", cqw: 1.8, weight: 400, lineHeight: 1.1 }
|
||||
timeline-headline:{ fontFamily: "Playfair Display", cqw: 1.9, weight: 400, lineHeight: 1.1 }
|
||||
card-headline:{ fontFamily: "Playfair Display", cqw: 2.0, weight: 400, lineHeight: 1.15 }
|
||||
stat-figure: { fontFamily: "Playfair Display", cqw: 3.0, weight: 400, lineHeight: 1.0 }
|
||||
quote-mark: { fontFamily: "Playfair Display", cqw: 9.0, weight: 400, lineHeight: 0.5, color: "line" }
|
||||
h2: { fontFamily: "Playfair Display", cqw: 4.0, weight: 400, lineHeight: 1.1 }
|
||||
h1: { fontFamily: "Playfair Display", cqw: 6.2, weight: 400, lineHeight: 1.06 }
|
||||
display: { fontFamily: "Playfair Display", cqw: 8.0, weight: 400, lineHeight: 1.04 }
|
||||
|
||||
spacing:
|
||||
pad-x: "7cqw"
|
||||
pad-y: "5cqw"
|
||||
gap-xl: "6cqw"
|
||||
gap-lg: "5cqw"
|
||||
|
||||
components:
|
||||
hairline:
|
||||
rule: "0.07cqw solid {colors.line}"
|
||||
description: "The universal structural device — every separator (agenda rule, timeline connector, card border, stats top) is this 1px taupe line. No thick borders exist."
|
||||
card:
|
||||
backgroundColor: "{colors.white-overlay}"
|
||||
border: "0.07cqw solid {colors.line}"
|
||||
rounded: "0"
|
||||
shadow: "none"
|
||||
description: "Faint white-overlay fill (canvas bleeds through) is what makes a card distinct from a bare region."
|
||||
card-icon:
|
||||
border: "0.07cqw solid {colors.line}"
|
||||
rounded: "50%"
|
||||
size: "40px circle"
|
||||
textColor: "{colors.accent}"
|
||||
typography: "Playfair numeral/letter"
|
||||
description: "Ringed circle mark."
|
||||
agenda-row:
|
||||
borderBottom: "0.07cqw solid {colors.line}"
|
||||
typography: "{typography.h3} (numeral {colors.accent}, label {colors.text-primary})"
|
||||
description: "Numeral left, label right over a taupe rule."
|
||||
timeline:
|
||||
borderTop: "0.07cqw solid {colors.line}"
|
||||
typography: "{typography.timeline-headline} + {typography.body-sm}"
|
||||
description: "A single taupe top rule across items — no nodes, no dots."
|
||||
stats-cluster:
|
||||
borderTop: "0.07cqw solid {colors.line}"
|
||||
typography: "{typography.stat-figure} + uppercase {colors.accent} labels"
|
||||
description: "Inline modest stat figures (no hero numeral)."
|
||||
geo-ring:
|
||||
border: "1px solid {colors.line} (inner dashed ::before ~70–80%)"
|
||||
rounded: "50%"
|
||||
size: "10–50cqw"
|
||||
opacity: "0.2–0.5"
|
||||
description: "Compass-construction rings behind content. 1–2 per frame, never more."
|
||||
horizontal-accent:
|
||||
backgroundColor: "{colors.text-primary}"
|
||||
size: "~18cqw × 1px"
|
||||
description: "The system's only INK-BLACK rule — a strong terminal accent on cover/closing, sparingly."
|
||||
vertical-line:
|
||||
backgroundColor: "{colors.line}"
|
||||
size: "1px × full height, ~5cqw from edge"
|
||||
opacity: "0.3–0.4"
|
||||
description: "Drafting-paper alignment guide. Optional."
|
||||
image-placeholder:
|
||||
backgroundColor: "{colors.bg-secondary}"
|
||||
mark: "crossed +30°/−30° 1px taupe diagonals (an X)"
|
||||
typography: "small uppercase {typography.micro}"
|
||||
description: "The signature image-not-wired mark."
|
||||
team-photo:
|
||||
backgroundColor: "{colors.bg-secondary}"
|
||||
border: "0.07cqw solid {colors.line}"
|
||||
rounded: "50%"
|
||||
typography: "Playfair initial in {colors.accent}"
|
||||
description: "Circular portrait frame."
|
||||
---
|
||||
|
||||
# Cartesian — Frame (video / frame layer)
|
||||
|
||||
## Overview
|
||||
|
||||
Cartesian at frame scale is a **quiet museum-catalog editorial system** — restraint through 1px
|
||||
lines. Every structural separator is a single 1px taupe hairline; there are no thick borders, no
|
||||
fills (save the faint white-overlay card), no shadows, no rounded rectangles. Hierarchy comes from
|
||||
**type contrast and negative space**, and atmosphere from **compass-drafted geometric rings**
|
||||
drifting behind content.
|
||||
|
||||
The voice is a literary pairing: **Playfair Display** at weight 400 (the thin-stroke didone, never
|
||||
bold, always sentence case) carries every headline, numeral, and quote mark in ink; **Inter**
|
||||
carries body in warm gray and labels in uppercase taupe with 2–3px tracking. The palette is five
|
||||
warm stones plus ink — no populist accent color exists. The correct density is **sparse and
|
||||
breathing**: one clear idea, well-framed, on stone paper.
|
||||
|
||||
**Key characteristics at frame scale:**
|
||||
|
||||
- **1px taupe hairline** as the universal structural device — every separator is this one line.
|
||||
- **Playfair Display 400** (ink, sentence case) for display; **Inter** body (gray) + labels (taupe, tracked).
|
||||
- **Five warm stones + ink** — no red/blue/green; the only "color" is type contrast.
|
||||
- **Compass-drafted geometric rings** (solid + dashed, 20–50% opacity) behind content for mood.
|
||||
- **Flat** — zero shadow, zero rounded rectangle (circles only); the lone ink line is the `horizontal-accent`.
|
||||
- **Sparse and breathing** — generous negative space; crowding reads as broken.
|
||||
|
||||
## The Frame
|
||||
|
||||
### Frame Craft Bar
|
||||
|
||||
Three eyeball tests gate every frame before any structural check:
|
||||
|
||||
- **Squint** — one Playfair element dominates at **3–6× its nearest neighbor**; the serif/sans + size contrast carries hierarchy, not weight.
|
||||
- **Silence** — declarative frames read **55–60% empty**; Cartesian has **no dense frame** — even the agenda/index breathes (the system breaks when crowded).
|
||||
- **Restraint** — **at most two geo rings** per frame; the single INK-BLACK `horizontal-accent` rule used sparingly; no populist accent color ever.
|
||||
- **Reference** — aim at a **Vignelli editorial / Cooper Hewitt catalogue / pencil-and-tracing-paper plan**; failure looks like a **shadowed, rounded-card SaaS deck**.
|
||||
|
||||
- **Primary:** 1920×1080 (16:9). Display authored in **`cqw`** (`px ÷ 1920 × 100 = cqw`).
|
||||
- **Vertical:** 1080×1920 (9:16). **Square:** 1080×1080 (1:1).
|
||||
- **Safe area:** `pad-x` (7cqw) generous gutters; geometry may bleed off an edge.
|
||||
|
||||
**The container law (load-bearing).** Every frame ground sets `container-type: size`; ALL
|
||||
frame-relative units are `cqw`/`cqh` against it — never `vw`. The 1px hairlines and geo rings hold
|
||||
their proportion against the frame at any render size.
|
||||
|
||||
## Colors
|
||||
|
||||
Tokens identical to the source. `{colors.bg-primary}` is the ground; `{colors.text-primary}` ink is
|
||||
headlines and the one black accent rule; `{colors.text-secondary}` gray is body; `{colors.accent}`
|
||||
taupe is labels, numerals, small text; `{colors.line}` taupe is every 1px structural border.
|
||||
`{colors.bg-secondary}` is the only secondary fill (placeholders, photo frames). **No populist
|
||||
accent** — when emphasis is needed, grow the type, switch sans→serif, or add a single
|
||||
`horizontal-accent` ink line. Headlines are never taupe; small text is never ink.
|
||||
|
||||
## Typography
|
||||
|
||||
Two ramps. The **reading ramp** (Inter body 1.0cqw gray, labels in px taupe) carries copy + chrome;
|
||||
the **display ramp** (Playfair `h3` 1.8cqw → `display` 8.0cqw, all weight 400) carries every headline.
|
||||
|
||||
- **Legibility floor:** any load-bearing line ≥ **1.4cqw**; px labels are chrome only.
|
||||
- **Fit-to-measure:** size the headline to its length. Cap the block at **≤ 78cqw**; ≤3 words → `display`/`h1`; 4–6 → `h2`; 7+ → `h3`. Cartesian has no hero-stat numeral — stats stay modest (`stat-figure` 3cqw).
|
||||
- **Playfair at 400, ink, sentence case** — never bold, never uppercase, never taupe. **Inter labels uppercase, 2–3px tracked, taupe.** Italic via Playfair italic for emphasis only.
|
||||
|
||||
## Depth & Surface
|
||||
|
||||
The flat plane is the only technique. Hierarchy from:
|
||||
|
||||
- **Type contrast** — Playfair serif vs Inter sans; the 8cqw→0.7cqw scale.
|
||||
- **1px taupe hairlines** — every divider, card outline, timeline rule, photo ring.
|
||||
- **Tone** — ink vs gray vs taupe.
|
||||
- **Negative space** — generous padding.
|
||||
- **Geometric atmosphere** — compass rings that suggest depth without creating it.
|
||||
|
||||
**Ceiling:** no box-shadow, no elevated card, no gradient, no rounded rectangle. The single ink line
|
||||
(`horizontal-accent`) is the only non-taupe rule.
|
||||
|
||||
## Shapes
|
||||
|
||||
- **50% (circle)** — card-icon, team-photo, nav-dot, every geo ring.
|
||||
- **0** — everything else; soft-rounded corners do not exist.
|
||||
|
||||
## Components
|
||||
|
||||
- **hairline** — the universal 1px taupe separator (the identity).
|
||||
- **card** (1px taupe + white-overlay) / **card-icon** (ringed circle) / **agenda-row** / **timeline** (line, no nodes) / **stats-cluster** — all built on the hairline.
|
||||
- **geo-ring** — compass decoration (solid + dashed), 1–2 per frame. **horizontal-accent** — the one ink line, sparingly. **vertical-line** — drafting guide.
|
||||
- **image-placeholder** (crossed-X) / **team-photo** (ringed initial) — the stone-fill placeholders.
|
||||
|
||||
## Frame Treatments
|
||||
|
||||
> Recipe: ground · container · composes · focal · chrome · accent · silence · Fixed/Free · density.
|
||||
> Every frame is sparse and breathing; 1–2 geo decorations max.
|
||||
|
||||
### 1 · Cover (identity · move: serif + compass ring · left)
|
||||
|
||||
**Ground** `{colors.bg-primary}`, `pad-x`. **Composes** geo-ring (right, ~34cqw, solid+dashed), vertical-line (left), label, display/h1. **Focal** a 2–3 line Playfair `display`/`h1` headline in ink (italic on the key word), left-anchored, with a taupe `label` above and an Inter subtitle below. **Chrome** optional bottom meta row (Playfair value + taupe label). **Accent** the geo ring; optionally one `horizontal-accent` ink line (if it clears the meta). **Silence** ~55% empty. **Fixed** Playfair 400 ink sentence-case, ≤2 geo elements, 1px lines. **Free** title, ring placement, meta. **Density** sparse.
|
||||
|
||||
### 2 · Agenda / Index (index · move: hairline list · left)
|
||||
|
||||
**Ground** `{colors.bg-primary}`, `pad-x`. **Composes** label, h2, agenda-rows. **Focal** a Playfair `h2` over 4–6 agenda rows (Playfair numeral in taupe + Playfair label in ink), each on a 1px taupe rule. **Chrome** taupe `label` eyebrow. **Accent** none — taupe numerals carry it. **Silence** moderate; rows generously spaced. **Fixed** 1px taupe rules, Playfair 400. **Free** items, count. **Density** standard (sparse rows).
|
||||
|
||||
### 3 · Pull Quote (quote · move: centered statement · compass ring)
|
||||
|
||||
**Ground** `{colors.bg-primary}`, centered. **Composes** geo-ring (centered dashed, ~26cqw), quote-mark, h2/display-quote, attribution. **Focal** a 2-line Playfair quote in ink, centered, under a 50%-taupe Playfair quote-mark; a taupe uppercase attribution beneath. **Accent** the faint centered ring. **Silence** ~60% — deliberately open. **Fixed** Playfair 400, one ring, centered. **Free** quote, attribution. **Density** sparse.
|
||||
|
||||
### 4 · Closing Plate (closer · move: centered ring · centered)
|
||||
|
||||
**Ground** `{colors.bg-primary}`, centered. **Composes** geo-ring (centered, ~40cqw, solid+dashed), label, h1/display, horizontal-accent. **Focal** a 1–2 line Playfair sign-off in ink (italic key word), centered inside the largest compass ring; a short ink `horizontal-accent` beneath. **Accent** the ring + the one ink line. **Silence** ~60%. **Fixed** Playfair 400, centered, ≤2 geo. **Free** sign-off, ring scale. **Density** sparse.
|
||||
|
||||
### 5 · Two-Column Editorial (content · move: asymmetric split · left)
|
||||
|
||||
**Ground** `{colors.bg-primary}`, `pad-x`, two columns with `gap-xl`. **Composes** label, h2, body, image-placeholder (crossed-X) or card. **Focal** a Playfair `h2` + Inter body in the text column; an image-placeholder or card in the other. **Accent** none. **Silence** generous gutter. **Fixed** 1px taupe card/placeholder borders, white-overlay fill. **Free** which side is text, body copy. **Density** standard.
|
||||
|
||||
### 6 · Stats / Timeline (data · move: hairline rail · left)
|
||||
|
||||
**Ground** `{colors.bg-primary}`, `pad-x`. **Composes** label, h3, stats-cluster or timeline. **Focal** a modest Playfair stat row (or a 1px-rule timeline with year + headline + body, no nodes), framed by a 1px taupe top rule. **Accent** none. **Silence** moderate. **Fixed** modest stat scale, hairline rule, no nodes. **Free** figures, phases. **Density** standard.
|
||||
|
||||
## Composition Rules
|
||||
|
||||
### Do
|
||||
|
||||
- Use a **single 1px taupe line** for every separator — the hairline is the identity.
|
||||
- Set every **Playfair headline at 400, ink, sentence case**; render labels taupe, uppercase, 2–3px tracked.
|
||||
- Layer **one or two compass rings** (solid + dashed, 20–50% opacity) behind content for atmosphere.
|
||||
- Let frames **breathe** — sparse, generous negative space; 55–60% empty on declarative frames.
|
||||
- Lean centered on quote/closer; asymmetric/left on cover/agenda/editorial. Use `bg-secondary` for placeholder fills.
|
||||
|
||||
### Don't
|
||||
|
||||
- Don't introduce a populist accent color — stone and ink only.
|
||||
- Don't bold Playfair, render headlines in taupe, or use thick (2px+) borders.
|
||||
- Don't add shadows, elevated cards, or rounded rectangles (circles only).
|
||||
- Don't crowd the frame — packed layouts read as broken.
|
||||
- Never more than two geo decorations per frame; don't blow a headline edge-to-edge.
|
||||
|
||||
## Aspect-Ratio Behavior
|
||||
|
||||
| Treatment | 16:9 | 9:16 | 1:1 |
|
||||
| -------------------- | -------------------------- | ----------------------------------- | --------------------------- |
|
||||
| Cover | headline left, ring right | headline top, ring below | headline upper, ring behind |
|
||||
| Agenda / Index | h2 + rows | h2 + rows (tighter) | h2 + rows |
|
||||
| Pull Quote | centered, ring behind | centered, taller | centered |
|
||||
| Closing Plate | centered in ring | centered, ring scaled | centered |
|
||||
| Two-Column Editorial | text + visual side-by-side | stacked (collapse) | stacked |
|
||||
| Stats / Timeline | horizontal rail | vertical stack (drop timeline rule) | compact |
|
||||
|
||||
Generous `pad-x` holds on the short edge; re-step display per ratio above the 1.4cqw floor. On
|
||||
9:16, the timeline rule loses meaning when stacked — switch to a vertical list (per source).
|
||||
|
||||
## Approved Entities
|
||||
|
||||
No real customers, logos, or vendors are defined in the source — render any such mark as a
|
||||
placeholder (the crossed-X image-placeholder, or a ringed initial for portraits).
|
||||
|
||||
## Numerals & Claims (hard rule)
|
||||
|
||||
Never invent figures, dates, or counts at frame scale. Render slots as `— figure —`, `{metric}`.
|
||||
Stats and timeline years carry placeholders until the script supplies them. Agenda ordinals
|
||||
(01, 02…) are decorative and may be sequential.
|
||||
|
||||
## Pre-Render Self-Audit
|
||||
|
||||
- **Squint** — one Playfair element dominates; the serif/sans contrast carries hierarchy.
|
||||
- **Silence** — declarative frames 55–60% empty; nothing is crowded.
|
||||
- **Palette** — five stones + ink only; no populist accent; headlines ink, labels taupe.
|
||||
- **Lines** — every separator is a 1px taupe hairline; the only ink line is `horizontal-accent`.
|
||||
- **Type** — Playfair 400 sentence-case, fit-to-measure; labels uppercase 2–3px; ≥1.4cqw floor.
|
||||
- **Depth** — 0 shadow, 0 rounded rectangle; ≤2 geo rings per frame.
|
||||
- **Anchor** — centered on quote/closer, left/asymmetric on cover/agenda/editorial; no 3 in a row alike.
|
||||
- **Fabrication** — every numeral traces to the script, else placeholder.
|
||||
|
||||
## Known Gaps
|
||||
|
||||
- **Motion intentionally out of scope.** frame.md specifies composition only; the 0.6s fade in the source is a deck mechanic.
|
||||
- **Playfair Display + Inter via Google Fonts.** CJK pairing (Noto Serif SC 700/400) carries over; Playfair has no Hanzi italic — substitute weight/taupe for emphasis.
|
||||
- **9:16 / 1:1 are guidance**; verify the legibility floor and that the timeline collapses to a vertical list.
|
||||
- Geo rings, the crossed-X placeholder, and the dashed inner ring are CSS-only; no external imagery is required.
|
||||
@@ -0,0 +1,222 @@
|
||||
<!--
|
||||
Cartesian — 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 cartesian 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 museum-catalog lower-third — a warm-stone card on a single 1px taupe
|
||||
hairline border (cartesian's universal structural device), SHARP corners, ZERO
|
||||
shadow, ZERO fill beyond the stone canvas. Playfair Display 400, sentence case, ink
|
||||
glyphs. Upcoming words sit in faint ink; the current word is marked QUIETLY — a faint
|
||||
stone wash with a 1px taupe hairline underline (restraint, not a loud block); spoken
|
||||
words settle to solid ink, the wash and underline 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 stone card — single 1px taupe hairline border (cartesian's universal
|
||||
structural device), SHARP corners, ZERO shadow, ZERO fill beyond the stone canvas */
|
||||
.caption-pill {
|
||||
max-width: 78%;
|
||||
padding: 20px 44px 24px;
|
||||
background: var(--cap-canvas, #ede8e0);
|
||||
border: 1px solid var(--cap-accent-2, #b8b0a4);
|
||||
border-radius: 0;
|
||||
}
|
||||
.caption-line {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 0.1em 0.34em;
|
||||
font-family: var(--font-display, "Playfair Display"), Georgia, serif;
|
||||
font-weight: 400;
|
||||
font-size: clamp(40px, 4.4vw, 58px);
|
||||
line-height: 1.16;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
/* upcoming — faint ink, no mark */
|
||||
.caption-word {
|
||||
display: inline-block;
|
||||
padding: 0 0.06em;
|
||||
border-bottom: 1px solid transparent;
|
||||
color: color-mix(in srgb, var(--cap-ink, #1a1a1a) 42%, var(--cap-canvas, #ede8e0));
|
||||
}
|
||||
/* current word — a QUIET marker: a faint warm-stone wash behind solid ink, plus a
|
||||
1px taupe hairline underline (restraint, not a loud block; SHARP, NO shadow) */
|
||||
.caption-word.is-active {
|
||||
color: var(--cap-ink, #1a1a1a);
|
||||
background: color-mix(in srgb, var(--cap-accent, #8a8178) 18%, var(--cap-canvas, #ede8e0));
|
||||
border-bottom: 1px solid var(--cap-accent, #8a8178);
|
||||
}
|
||||
/* already spoken — solid ink, wash + underline cleared */
|
||||
.caption-word.is-spoken {
|
||||
color: var(--cap-ink, #1a1a1a);
|
||||
background: transparent;
|
||||
border-bottom: 1px solid transparent;
|
||||
}
|
||||
|
||||
/* optional per-word accents (dormant until a word-classer tags them) */
|
||||
/* brand / key term — Playfair Display italic, cartesian's only display inflection */
|
||||
.caption-word.cap-brand {
|
||||
font-style: italic;
|
||||
}
|
||||
/* numerals / data — the body sans voice, slightly recessed */
|
||||
.caption-word.cap-num {
|
||||
font-family: var(--font-body, "Inter"), ui-sans-serif, sans-serif;
|
||||
color: color-mix(in srgb, var(--cap-ink, #1a1a1a) 72%, var(--cap-canvas, #ede8e0));
|
||||
}
|
||||
/* emphasis — a 1px taupe hairline underline (no second color; stone-and-ink only) */
|
||||
.caption-word.cap-cta {
|
||||
text-decoration: underline;
|
||||
text-decoration-thickness: 1px;
|
||||
text-decoration-color: var(--cap-accent, #8a8178);
|
||||
text-underline-offset: 0.12em;
|
||||
}
|
||||
|
||||
@media (max-aspect-ratio: 9/16) {
|
||||
.caption-pill {
|
||||
max-width: 92%;
|
||||
}
|
||||
.caption-line {
|
||||
font-size: clamp(34px, 5.4vw, 48px);
|
||||
}
|
||||
}
|
||||
</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.985 }, { scale: 1, duration: 0.2, ease: "power1.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
@@ -0,0 +1,277 @@
|
||||
---
|
||||
version: alpha
|
||||
name: Claude — Frame (video / frame layer)
|
||||
description: >
|
||||
Video-first companion to Claude's design.md. The unit is the frame (1920×1080). Atoms are
|
||||
identical and sacred — warm cream paper (never pure white, never cool), a single terracotta coral
|
||||
as scarce "voltage", hairline ink elevation (no heavy shadow), EB Garamond for all
|
||||
display + Inter body + JetBrains Mono for the index/code voice on a warm-navy code surface,
|
||||
sentence-case display, and the ✱ coral spike mark. Composition + frame scale rewritten. 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: "#141413"
|
||||
cream: "#FAF9F5"
|
||||
tile: "#EFE9DE"
|
||||
tile-strong: "#ECE3D4"
|
||||
coral: "#CC785C"
|
||||
navy: "#181715"
|
||||
navy-soft: "#1F1E1B"
|
||||
navy-elev: "#252320"
|
||||
|
||||
borders: { hairline: "1px solid ink@12%", hairline-strong: "1px solid ink@20%", dark: "1px solid cream@14% (on navy)" }
|
||||
shadows: { card: "0 1px 3px ink@8%, 0 4px 16px ink@4%", none: "none" }
|
||||
|
||||
typography:
|
||||
# — reading + chrome ramp —
|
||||
body: { fontFamily: "Inter", cqw: 1.5, weight: 400, lineHeight: 1.5 }
|
||||
lead: { fontFamily: "Inter", cqw: 2.08, weight: 400, lineHeight: 1.5 }
|
||||
card-title:{ fontFamily: "Inter", cqw: 2.3, weight: 500, lineHeight: 1.25, tracking: "-0.005em" }
|
||||
button: { fontFamily: "Inter", cqw: 1.46, weight: 500, lineHeight: 1.0 }
|
||||
tag-upper:{ fontFamily: "Inter", cqw: 1.35, weight: 500, tracking: "0.18em", upper: true }
|
||||
kicker: { fontFamily: "JetBrains Mono", px: 28, cqw: 1.46, weight: 500, tracking: "0.16em", upper: true }
|
||||
mono-label:{ fontFamily: "JetBrains Mono", px: 26, cqw: 1.35, weight: 500, tracking: "0.02em" }
|
||||
code: { fontFamily: "JetBrains Mono", cqw: 1.67, weight: 400, lineHeight: 1.6 }
|
||||
# — display ramp (EB Garamond 400, sentence case, negative tracking. Renderer embeds only 400/700 — author at 400; italic is the synthesized slant) —
|
||||
headline:{ fontFamily: "EB Garamond", cqw: 4.6, weight: 400, lineHeight: 1.06, tracking: "-0.018em" }
|
||||
quote-pull:{ fontFamily: "EB Garamond", cqw: 5.0, weight: 400, lineHeight: 1.12, tracking: "-0.012em", italic: true }
|
||||
display-italic:{ fontFamily: "EB Garamond", cqw: 6.7, weight: 400, lineHeight: 1.05, tracking: "-0.012em", italic: true }
|
||||
display:{ fontFamily: "EB Garamond", cqw: 7.3, weight: 400, lineHeight: 1.02, tracking: "-0.022em" }
|
||||
number-hero:{ fontFamily: "EB Garamond", cqw: 9.4, weight: 400, lineHeight: 0.95, tracking: "-0.025em" }
|
||||
display-cover:{ fontFamily: "EB Garamond", cqw: 9.9, weight: 400, lineHeight: 0.98, tracking: "-0.028em" }
|
||||
number-unit:{ fontFamily: "JetBrains Mono", cqw: 2.08, weight: 500, lineHeight: 1.0 }
|
||||
|
||||
spacing:
|
||||
slide-pad: "4.2cqw" # ~80px @1920
|
||||
gap-md: "1.7cqw"
|
||||
hairline: "1px"
|
||||
radius-sm: "6px"
|
||||
radius-md: "8px"
|
||||
radius-lg: "12px"
|
||||
radius-pill: "9999px"
|
||||
|
||||
components:
|
||||
card-hairline:
|
||||
backgroundColor: "{colors.cream} or {colors.tile}"
|
||||
border: "1px solid {colors.ink}@12%"
|
||||
rounded: "{spacing.radius-lg}"
|
||||
shadow: "{shadows.card}"
|
||||
typography: "{typography.card-title} + {typography.body}"
|
||||
description: "The editorial content card. Elevation is the hairline + ONE soft warm shadow — never a heavy drop, glow, or gradient."
|
||||
kicker-spike:
|
||||
typography: "{typography.kicker}"
|
||||
mark: "✱ coral spike prefix"
|
||||
description: "The eyebrow — JetBrains Mono uppercase, indexical (2–5 words), prefixed with the coral ✱. Never plain text, never a sentence."
|
||||
coral-callout:
|
||||
backgroundColor: "{colors.coral} (full-bleed) or {colors.cream} with a coral edge"
|
||||
textColor: "{colors.cream} on coral"
|
||||
rounded: "{spacing.radius-md}"
|
||||
typography: "{typography.button} / {typography.h2}"
|
||||
description: "The ONE voltage moment per frame — the CTA, the single inline link, OR the full-bleed band. Never two corals in one frame."
|
||||
number-lockup:
|
||||
typography: "{typography.number-hero} figure + {typography.number-unit} unit"
|
||||
description: "Hero stat — a EB Garamond figure paired with a JetBrains Mono unit (200K, +1,204, −318, 17 files). Figure is serif; the unit is ALWAYS mono, never the serif."
|
||||
pull-quote:
|
||||
typography: "{typography.quote-pull} (EB Garamond italic) + {typography.tag-upper} cite"
|
||||
description: "A commit message, a reviewer line, or the thesis. EB Garamond italic, small Inter uppercase cite beneath."
|
||||
section-rule:
|
||||
rule: "1px solid {colors.ink}@12% (or {colors.cream}@14% on navy)"
|
||||
description: "The only separator. A coral 1px rule may draw on to introduce a section. Never 2px+, never a heavy divider."
|
||||
code-surface:
|
||||
backgroundColor: "{colors.navy} body / {colors.navy-elev} title bar + status strip"
|
||||
textColor: "{colors.cream} (JetBrains Mono); syntax in coral (keywords) / teal #5DB8A6 (strings) / amber #E8A55A (numbers)"
|
||||
border: "1px solid {colors.cream}@14%"
|
||||
rounded: "{spacing.radius-md}"
|
||||
description: "The warm-navy code / terminal surface. The CODE ITSELF is rendered by the code-* registry blocks (code-diff / code-typing / code-snippet-*); this preset owns the surrounding surface, title bar, status strip, and mono chrome — not the code rendering."
|
||||
spike-mark:
|
||||
glyph: "✱ (U+2731), always {colors.coral}"
|
||||
description: "The brand mark. Fades + scales 0.92→1 on a single emphasis beat; never spins."
|
||||
---
|
||||
|
||||
# Claude — Frame (video / frame layer)
|
||||
|
||||
## Overview
|
||||
|
||||
Claude at frame scale is a **warm-editorial brand book come to life** — the register of a literary
|
||||
imprint or a research note. The thesis is three colors: **cream is the ground, ink is the voice,
|
||||
coral is the voltage** — and a fourth (warm navy) only where code shows itself. Every surface is
|
||||
**warm cream** (never pure white, never cool gray); content gathers on a **tile** surface half a
|
||||
step darker — the demarcation is a half-step, never a hard contrast. Elevation is a **1px hairline**
|
||||
ink border at low alpha plus, rarely, one soft warm shadow. There are no heavy drops, no glows, no
|
||||
gradients on content.
|
||||
|
||||
Three editorial voices, each in its own face: **EB Garamond** carries every display moment — covers,
|
||||
headlines, pull-quotes, big stat numerals — at large display sizes with gentle negative tracking;
|
||||
its **italic** is the expressive register. **Inter** carries body, leads, card titles, buttons, and
|
||||
UI chrome. **JetBrains Mono** carries the indexical layer — kickers, technical labels, the code
|
||||
window, status strips. Switching a voice's face collapses the register: a sans headline or a serif
|
||||
label reads as a different brand.
|
||||
|
||||
**Key characteristics at frame scale:**
|
||||
|
||||
- **Cream / ink / coral trinity** + a warm-navy code surface; cream is the default ground, ink the voice, coral the scarce voltage.
|
||||
- **EB Garamond** (sentence case, negative-tracked) for all display; **Inter** body/chrome; **JetBrains Mono** index + code.
|
||||
- **Hairline elevation** — a 1px low-alpha ink border + at most one soft warm shadow. No heavy drop, glow, or gradient.
|
||||
- **Coral is rationed** — at most ONE coral moment per frame (CTA, inline link, OR full-bleed band); coral never sets a headline or a body run.
|
||||
- **Density is free** — fill the frame as the content wants; a frame may stand on a single focal or carry a dense, layered composition.
|
||||
- **The ✱ coral spike** opens kickers; warm navy is reserved for the code/terminal surface.
|
||||
|
||||
## The Frame
|
||||
|
||||
### Frame Craft Bar
|
||||
|
||||
Eyeball tests gate every frame before any structural check:
|
||||
|
||||
- **Squint** — one EB Garamond display moment dominates at 3–6× its neighbor.
|
||||
- **Trinity** — cream/tile ground, ink text, coral exactly **once**; warm navy only on the code surface; no cool gray / pure white / fourth hue.
|
||||
- **Type** — EB Garamond sentence-case display (negative-tracked); Inter 400 body; JetBrains Mono kickers (uppercase 0.16em, coral ✱) + code.
|
||||
|
||||
- **Primary:** 1920×1080 (16:9). Display authored in **`cqw`** (`px ÷ 1920 × 100 = cqw`).
|
||||
- **Vertical:** 1080×1920 (9:16). **Square:** 1080×1080 (1:1).
|
||||
- **Safe area:** `slide-pad` ~4.2cqw; the kicker/mono chrome sit inside it.
|
||||
|
||||
**The container law (load-bearing).** Every frame ground sets `container-type: size`; ALL
|
||||
frame-relative units are `cqw`/`cqh` against it — never `vw`. Hairlines stay 1px; card radii stay
|
||||
6/8/12px; the warm-paper reading must survive every ratio.
|
||||
|
||||
## Colors
|
||||
|
||||
Tokens identical to the source. Default ground `{colors.cream}`; content gathers on
|
||||
`{colors.tile}` / `{colors.tile-strong}` (half-step warm steps, never a hard contrast).
|
||||
**Headlines & body:** `{colors.ink}` on cream/tile; `{colors.cream}` on navy. **Coral**
|
||||
(`{colors.coral}`) is the scarce voltage — one moment per frame (CTA, inline link, or full-bleed
|
||||
band), never body text, never a card fill. **Warm navy** (`{colors.navy}` / `navy-soft` /
|
||||
`navy-elev`) is the code / terminal / dark-card surface — a structural anchor, not a fourth brand
|
||||
hue. **No cool grays, no pure white, no pure black.**
|
||||
|
||||
**Fixed syntax colors (decoration, NOT remixable brand hues).** When a code line is hand-set rather
|
||||
than rendered by a `code-*` block, keywords are coral, strings are **teal `#5DB8A6`**, numbers are
|
||||
**amber `#E8A55A`**; status reads success `#5DB872` / warn `#C64545`. These track the code surface,
|
||||
not the brand trinity — keep them out of the brand palette.
|
||||
|
||||
## Typography
|
||||
|
||||
Two ramps. The **reading/chrome ramp** (Inter `body` 1.5cqw / `lead` 2.08cqw weight 400; JetBrains
|
||||
Mono `kicker`/`mono-label` in px) carries copy + chrome; the **display ramp** (EB Garamond `headline` 4.6cqw
|
||||
→ `display-cover` 9.9cqw, weight 400, negative-tracked) carries every headline + stat.
|
||||
|
||||
- **Legibility floor:** any load-bearing line ≥ **1.4cqw**; mono px labels are chrome only.
|
||||
- **Fit-to-measure:** size the headline to its length. Cap the block at **≤ 78cqw**; ≤3 words → `display-cover`; 4–6 → `display`; 7+ → `headline`. Reserve the 7.3–9.9cqw tier for cover / statement / stat.
|
||||
- **EB Garamond display is sentence case** (NOT title case, NOT uppercase), weight 400, negative-tracked (−0.012..−0.028em); reach for **italic** when the line is a stance or a definition. **Inter body** sentence case weight 400. **JetBrains Mono** kickers UPPERCASE 0.16em with the coral ✱. No uppercase serif, no sans headline, no serif label.
|
||||
|
||||
## Depth & Surface
|
||||
|
||||
Hairline elevation:
|
||||
|
||||
- **1px hairline** ink border at ~12% alpha is the primary lift (cream@14% on navy).
|
||||
- **One soft warm shadow** (`0 1px 3px ink@8%, 0 4px 16px ink@4%`) — used rarely, never heavy.
|
||||
- **Half-step surface** — a `{colors.tile}` block on cream reads elevated by the warm step, not by a cast shadow.
|
||||
|
||||
**Ceiling:** no heavy drop shadow, no glow, no gradient on content, no tilt. The system has no light
|
||||
to emit; it reads by warmth and hairline.
|
||||
|
||||
## Shapes
|
||||
|
||||
- **6px** small chrome, **8px** cards / code surface, **12px** large cards / quote frames, **9999px** true pills only. No square corners, no heavy rounding; the editorial register is gently rounded, never hard.
|
||||
|
||||
## Components
|
||||
|
||||
- **card-hairline** — the editorial content card (hairline + one soft shadow). **section-rule** — the only separator (1px, coral may draw on).
|
||||
- **kicker-spike** — the ✱ coral eyebrow. **coral-callout** — the one voltage moment (CTA / inline link / full-bleed band).
|
||||
- **number-lockup** — EB Garamond figure + mono unit (the PR `+N / −M`, `200K`, file counts). **pull-quote** — EB Garamond italic + cite (a commit message / reviewer line).
|
||||
- **code-surface** — the warm-navy code / terminal surface; the code itself comes from the **`code-*` registry blocks**, this owns the surface + mono chrome.
|
||||
- **spike-mark** — the ✱ brand glyph, always coral.
|
||||
|
||||
## Frame Treatments
|
||||
|
||||
> Recipe: ground · container · composes · focal · chrome · accent · Fixed/Free · density.
|
||||
> One coral moment per frame; open with a kicker-spike.
|
||||
|
||||
### 1 · Cover (identity · move: oversized EB Garamond · cream)
|
||||
|
||||
**Ground** `{colors.cream}`, `slide-pad`. **Composes** kicker-spike, display-cover, lead, mono-label index. **Focal** a 2–3 line EB Garamond `display-cover` (sentence case, ink) under a coral ✱ kicker. **Chrome** mono index strip (repo · branch). **Accent** the single coral ✱. **Fixed** EB Garamond 400 sentence case, hairline, cream ground. **Free** title, the mono index, layout + how full the frame runs. **Density** free.
|
||||
|
||||
### 2 · Statement (statement · move: single EB Garamond line · cream or navy)
|
||||
|
||||
**Ground** `{colors.cream}` (or `{colors.navy}` for gravity). **Composes** kicker-spike, display, optional lead. **Focal** one 2-line EB Garamond `display` carrying the change in a sentence — reach for **italic** if it's a stance. **Chrome** mono kicker. **Accent** none — the serif carries it (or one coral word). **Fixed** sentence-case serif. **Free** the line, ground, layout + density. **Density** free.
|
||||
|
||||
### 3 · Code Surface (code · move: warm-navy code window · the PR-critical frame)
|
||||
|
||||
**Ground** `{colors.cream}` framing a `{colors.navy}` **code-surface** (8px, cream@14% hairline, `navy-elev` title bar + filename in mono). **Composes** mono-label filename, the **`code-*` block** (code-diff / code-typing / code-snippet-_), optional `section-rule`. **Focal** the code panel — the diff / before→after / typed-on snippet. **Chrome** mono filename + status strip. **Accent** syntax coral/teal/amber inside the panel; one coral marker outside (e.g. a `+`/`−` gutter cue). **Fixed** warm-navy surface, mono code, hairline. **Free** which code-_ block, the code (from the diff), how large the panel runs. **Density** dense.
|
||||
|
||||
### 4 · Number / Impact (data · move: oversized figure · cream)
|
||||
|
||||
**Ground** `{colors.cream}`, `slide-pad`. **Composes** kicker-spike, number-lockup, lead/caption, optional `section-rule`. **Focal** a EB Garamond `number-hero` figure with a mono unit over a 1px rule — the PR impact (`+1,204 / −318`, `17 files`, `2.1× faster`). **Chrome** mono tag. **Accent** the figure in ink; at most one coral unit. **Fixed** serif figure + mono unit, hairline rule. **Free** the figures (from the script), tag, layout + density. **Density** free.
|
||||
|
||||
### 5 · Pull-quote (quote · move: EB Garamond italic · cream)
|
||||
|
||||
**Ground** `{colors.cream}`. **Composes** kicker-spike, pull-quote, tag-upper cite. **Focal** a EB Garamond **italic** `quote-pull` — a commit message, a reviewer line, or the thesis — with a small Inter uppercase cite (author · role) beneath. **Chrome** mono kicker. **Accent** none, or one coral mark. **Fixed** EB Garamond italic quote + uppercase cite. **Free** quote, attribution, layout + density. **Density** free.
|
||||
|
||||
### 6 · Closing / CTA (closer · move: coral voltage · cream or navy)
|
||||
|
||||
**Ground** `{colors.cream}` (or `{colors.navy}`). **Composes** display sign-off, coral-callout, optional contributor row (`assets/<login>.png` avatars + mono names). **Focal** a short EB Garamond sign-off with the one **coral-callout** (the CTA or full-bleed band) and, for a "shipped-by" close, a row of hairline-ringed avatar chips. **Chrome** mono index. **Accent** the single coral voltage. **Fixed** one coral moment, hairline avatar rings, sentence-case serif. **Free** sign-off, who ships, layout + density. **Density** free.
|
||||
|
||||
## Composition Rules
|
||||
|
||||
### Do
|
||||
|
||||
- Stand every frame on the **warm cream floor**; gather content on a **half-step tile** surface.
|
||||
- Set all display in **EB Garamond, sentence case**, negative-tracked; **Inter 400** body; **JetBrains Mono** kickers (uppercase, 0.16em, coral ✱) + code.
|
||||
- Ration **coral to one moment per frame** — CTA, inline link, OR full-bleed band.
|
||||
- Elevate with a **1px hairline** + at most one soft warm shadow; reserve **warm navy** for the code/terminal surface.
|
||||
- Lead with **one clear focal**; open regions with a **kicker-spike**. Fill the frame as the content wants.
|
||||
- Pair a EB Garamond figure with a **mono unit** for every stat; render code via the **`code-*` blocks** on the navy surface.
|
||||
|
||||
### Don't
|
||||
|
||||
- No pure white, no cool gray, no pure black; no fourth brand hue (navy is structural, syntax colors are decoration).
|
||||
- No heavy drop shadow, glow, gradient on content, or tilt — hairline elevation only.
|
||||
- No uppercase or title-case EB Garamond display; no sans headline; no serif label; no serif-set numeric unit.
|
||||
- No two coral moments in one frame; coral never sets a headline or body run.
|
||||
- Don't blow a headline past the measure — step the ramp down.
|
||||
|
||||
## Aspect-Ratio Behavior
|
||||
|
||||
| Treatment | 16:9 | 9:16 | 1:1 |
|
||||
| ------------- | ----------------------- | --------------------------- | ---------------------- |
|
||||
| Cover | display left, index top | display top, index below | display, index corner |
|
||||
| Statement | line left/centered | line stacked | centered |
|
||||
| Code Surface | panel framed in cream | panel taller, fewer lines | panel centered, square |
|
||||
| Number/Impact | figure left | figure centered, taller | centered |
|
||||
| Pull-quote | quote left | quote stacked | centered |
|
||||
| Closing/CTA | sign-off + avatar row | sign-off top, avatars below | centered, avatars wrap |
|
||||
|
||||
`slide-pad` holds on the short edge; re-step display above the 1.4cqw floor. The code surface keeps
|
||||
its hairline + mono chrome on every ratio; the avatar row wraps rather than shrinks below legibility.
|
||||
|
||||
## Approved Real Entities
|
||||
|
||||
No real customers, logos, or vendors are defined in the source — render any such mark as a
|
||||
placeholder. Contributor avatars come from the project's `assets/<login>.png` (staged from the PR's
|
||||
people graph); the ✱ spike, hairlines, and the code surface are CSS-only and need no external imagery.
|
||||
|
||||
## Numerals & Claims (hard rule)
|
||||
|
||||
Never invent figures, stats, diffs, or counts at frame scale. Render slots as `— figure —`,
|
||||
`{metric}`, `+N / −M`. Number-lockups, code panels, and impact stats carry placeholders until the
|
||||
script (from the PR ingest) supplies real values. Branch names, file counts, and `+/−` totals trace
|
||||
to the diff; commit/issue numbers are chrome.
|
||||
|
||||
## Pre-Render Self-Audit
|
||||
|
||||
- **Squint** — one EB Garamond display moment dominates.
|
||||
- **Trinity** — cream floor + tile step + ink voice; coral appears exactly once; warm navy only on the code surface; no cool gray / pure white / fourth hue.
|
||||
- **Type** — EB Garamond sentence-case display (negative-tracked); Inter 400 body; JetBrains Mono kickers (uppercase 0.16em, coral ✱) + code; ≥1.4cqw floor.
|
||||
- **Depth** — 1px hairline + at most one soft warm shadow; no heavy drop / glow / gradient / tilt; 6/8/12px radii.
|
||||
- **Code** — code rendered by a `code-*` block on the warm-navy surface; syntax coral/teal/amber; figures paired with a mono unit.
|
||||
- **Fabrication** — every numeral / diff traces to the PR, else placeholder.
|
||||
|
||||
## Known Gaps
|
||||
|
||||
- **Motion intentionally out of scope.** frame.md specifies composition only. Claude's motion register — short cross-dissolves, no overshoot/bounce/elastic, coral the only "draw-on", numbers count up, code types on line by line — lives in the workflow's `motion-language.md` + `hyperframes-animation`, not here.
|
||||
- **EB Garamond + Inter + JetBrains Mono are all bundled in the HyperFrames renderer (`@fontsource` embedded data) — they resolve offline by name, with no Google Fonts dependency.** This is deliberate: every face here renders deterministically on a clean machine or AWS Lambda, so a frame needs no captured `.woff2` or `@font-face` for these three. The embedded set ships **weights 400 + 700 only and no true italic** — so author display at **weight 400** (700 reads as a heavy bold, off-register), and treat italic as the browser-synthesized slant (acceptable for the pull-quote register; ship a real EB Garamond italic `.woff2` + `@font-face` only if a project leans hard on it). EB Garamond is a warm old-style serif (low contrast, humanist); if it ever fails, fall to Georgia or another old-style serif — never to a sans. CJK: Noto Serif SC (display) / Noto Sans SC (body) / Noto Sans Mono CJK (code); the sentence-case warmth carries when the serif drops.
|
||||
- **Syntax colors (teal `#5DB8A6` / amber `#E8A55A` / status) are fixed decoration**, declared in §Colors — they are NOT in the remixable `colors:` block, so a brand remix never repaints them.
|
||||
- **The code itself is the `code-*` registry blocks**, not this preset — this preset owns only the surrounding warm-navy surface + mono chrome.
|
||||
- **9:16 / 1:1 are guidance**; verify the legibility floor and that the cream/tile warmth + one-coral discipline hold per ratio.
|
||||
@@ -0,0 +1,224 @@
|
||||
<!--
|
||||
Claude — caption skin (preset-local source).
|
||||
|
||||
This is the preset's own lower-third karaoke caption look. product-launch-video's
|
||||
(and pr-to-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 claude 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 warm cream card lifted off the page on a 1px hairline ink border + one soft
|
||||
warm shadow — claude's only elevation language, no tilt, editorial and precise.
|
||||
EB Garamond sentence case, warm-ink glyphs. Upcoming words sit in faint ink; the current
|
||||
word reads in full ink under a 3px coral "voltage" underline (the brand's inline-link
|
||||
gesture, border-bottom = underline without layout shift); spoken words settle to solid
|
||||
ink, underline 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 lifted cream card — warm cream fill, 1px hairline ink border, one soft warm
|
||||
shadow (claude's only elevation language), 12px radius, no tilt */
|
||||
.caption-pill {
|
||||
max-width: 78%;
|
||||
padding: 22px 44px 26px;
|
||||
background: var(--cap-canvas, #faf9f5);
|
||||
border: 1px solid color-mix(in srgb, var(--cap-ink, #141413) 12%, transparent);
|
||||
border-radius: 12px;
|
||||
box-shadow:
|
||||
0 1px 3px color-mix(in srgb, var(--cap-ink, #141413) 8%, transparent),
|
||||
0 4px 16px color-mix(in srgb, var(--cap-ink, #141413) 4%, transparent);
|
||||
}
|
||||
.caption-line {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 0.12em 0.3em;
|
||||
font-family: var(--font-display, "EB Garamond"), Georgia, serif;
|
||||
font-weight: 500;
|
||||
font-size: clamp(40px, 4.4vw, 56px);
|
||||
line-height: 1.24;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
/* upcoming — faint warm ink, no underline */
|
||||
.caption-word {
|
||||
display: inline-block;
|
||||
padding: 0 0.02em 0.06em;
|
||||
color: color-mix(in srgb, var(--cap-ink, #141413) 40%, var(--cap-canvas, #faf9f5));
|
||||
border-bottom: 3px solid transparent;
|
||||
}
|
||||
/* current word — full ink under the coral voltage underline (the brand's signature
|
||||
inline-link gesture; border-bottom = underline without layout shift) */
|
||||
.caption-word.is-active {
|
||||
color: var(--cap-ink, #141413);
|
||||
border-bottom: 3px solid var(--cap-accent, #cc785c);
|
||||
}
|
||||
/* already spoken — solid warm ink, underline cleared */
|
||||
.caption-word.is-spoken {
|
||||
color: var(--cap-ink, #141413);
|
||||
border-bottom: 3px solid transparent;
|
||||
}
|
||||
|
||||
/* optional per-word accents (dormant until a word-classer tags them) */
|
||||
/* brand / key term — the expressive EB Garamond italic */
|
||||
.caption-word.cap-brand {
|
||||
font-style: italic;
|
||||
color: var(--cap-ink, #141413);
|
||||
}
|
||||
.caption-word.cap-brand.is-active {
|
||||
border-bottom: 3px solid var(--cap-accent, #cc785c);
|
||||
}
|
||||
/* numerals / data — slightly recessed warm ink */
|
||||
.caption-word.cap-num {
|
||||
color: color-mix(in srgb, var(--cap-ink, #141413) 72%, var(--cap-canvas, #faf9f5));
|
||||
}
|
||||
/* emphasis — a persistent coral voltage underline */
|
||||
.caption-word.cap-cta {
|
||||
border-bottom: 3px solid var(--cap-accent-2, #b5654a);
|
||||
color: var(--cap-ink, #141413);
|
||||
}
|
||||
|
||||
@media (max-aspect-ratio: 9/16) {
|
||||
.caption-pill {
|
||||
max-width: 92%;
|
||||
}
|
||||
.caption-line {
|
||||
font-size: clamp(34px, 5.4vw, 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.98 }, { scale: 1, duration: 0.16, ease: "power1.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
@@ -0,0 +1,265 @@
|
||||
---
|
||||
version: alpha
|
||||
name: Cobalt Grid — Frame (video / frame layer)
|
||||
description: >
|
||||
Video-first companion to Cobalt Grid's design.md. The unit is the frame (1920×1080). Atoms
|
||||
are identical and sacred — warm cream paper, electric cobalt ink (the only ink), the permanent
|
||||
graph-paper grid, Newsreader serif 400 + Hanken Grotesk + DM Mono, the top/bottom cobalt
|
||||
hairlines, and the pixel-glitch + QR-block signatures. Composition, frame scale, and
|
||||
aspect-ratio behavior are rewritten for the frame. Motion is 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:
|
||||
paper: "#F0EBDE"
|
||||
paper-2: "#E6E0CE"
|
||||
ink: "#1F2BE0"
|
||||
ink-soft: "#5560E5"
|
||||
grid: "rgba(31,43,224,0.10)"
|
||||
ink-faint: "rgba(31,43,224,0.18)"
|
||||
|
||||
typography:
|
||||
# — reading ramp —
|
||||
body: { fontFamily: "Hanken Grotesk", cqw: 0.83, weight: 400, lineHeight: 1.5 }
|
||||
body-lede: { fontFamily: "Hanken Grotesk", cqw: 0.95, weight: 400, lineHeight: 1.5 }
|
||||
micro: { fontFamily: "Hanken Grotesk", px: 13, weight: 600, tracking: "0.16em", upper: true }
|
||||
micro-strong:{ fontFamily: "Hanken Grotesk", px: 16, weight: 600, tracking: "0.18em", upper: true }
|
||||
mono-tag: { fontFamily: "DM Mono", cqw: 0.78, weight: 400, tracking: "0.05em" }
|
||||
mono-chrome: { fontFamily: "DM Mono", px: 13, weight: 400, tracking: "0.06em" }
|
||||
# — display / hero ramp (Newsreader 400, negative tracking) —
|
||||
table-name: { fontFamily: "Newsreader", cqw: 1.5, weight: 400, lineHeight: 1.15 }
|
||||
row-headline:{ fontFamily: "Newsreader", cqw: 2.1, weight: 400, lineHeight: 1.05 }
|
||||
ed-callout: { fontFamily: "Newsreader", cqw: 2.6, weight: 400, lineHeight: 1.1, italic: true }
|
||||
headline: { fontFamily: "Newsreader", cqw: 4.6, weight: 400, lineHeight: 0.95 }
|
||||
headline-index:{ fontFamily: "Newsreader", cqw: 5.0, weight: 400, lineHeight: 0.95 }
|
||||
display-quote:{ fontFamily: "Newsreader", cqw: 5.7, weight: 400, lineHeight: 1.05, tracking: "-0.005em" }
|
||||
display-chapter:{ fontFamily: "Newsreader", cqw: 6.8, weight: 400, lineHeight: 1.0, tracking: "-0.005em" }
|
||||
display-closing:{ fontFamily: "Newsreader", cqw: 9.4, weight: 400, lineHeight: 0.96, tracking: "-0.005em" }
|
||||
display-hero:{ fontFamily: "Newsreader", cqw: 10.4, weight: 400, lineHeight: 0.9, tracking: "-0.008em" }
|
||||
vbig-numeral:{ fontFamily: "Newsreader", cqw: 12.5, weight: 400, lineHeight: 0.9, tracking: "-0.015em" }
|
||||
|
||||
spacing:
|
||||
edge: "4cqw" # standard frame edge inset (~80px@1920)
|
||||
pad-top: "7cqw"
|
||||
pad-bottom: "6cqw"
|
||||
gap-md: "2cqw"
|
||||
|
||||
components:
|
||||
graph-grid:
|
||||
backgroundImage: "linear-gradient grid, ~2cqw cells, 10% {colors.ink} ({colors.grid})"
|
||||
placement: "behind EVERY frame on the ground"
|
||||
description: "Permanent graph-paper grid — never disabled; the canvas tone."
|
||||
hairlines:
|
||||
rule: "0.12cqw solid {colors.ink}"
|
||||
placement: "≈3cqw from top + bottom, inset {spacing.edge}"
|
||||
description: "Two persistent cobalt rules framing every composition."
|
||||
page-chrome:
|
||||
typography: "{typography.pagenum}"
|
||||
color: "{colors.ink}"
|
||||
placement: "page number bottom-right, nav/meta hint bottom-left, above the bottom hairline"
|
||||
description: "The only persistent chrome."
|
||||
pixel-glitch:
|
||||
backgroundColor: "{colors.paper}"
|
||||
fill: "repeating-linear-gradient(90deg, {colors.ink} 0 2px, transparent 2px 8px)"
|
||||
size: "14–30cqw wide, full height, stair-stepped"
|
||||
placement: "right on cover/data, left on chapter/colophon; z above grid, below headline"
|
||||
description: "Stair-stepped scanline column. Decorative."
|
||||
qr-block:
|
||||
backgroundColor: "{colors.paper}"
|
||||
cells: "8×8, {colors.ink} on / {colors.grid} off"
|
||||
size: "3–9cqw square"
|
||||
shadow: "0 0 0 0.12cqw {colors.paper} (anti-shadow for readability, not elevation)"
|
||||
description: "QR mosaic patch — corner punctuation."
|
||||
topbar-rule:
|
||||
typography: "{typography.headline-index} + {typography.mono-tag}"
|
||||
borderBottom: "0.12cqw solid {colors.ink}"
|
||||
description: "Section header on index/data/table frames."
|
||||
ledger-row:
|
||||
layout: "grid: mono num · Newsreader name · Hanken desc · mono delta"
|
||||
borderBottom: "0.06cqw solid {colors.ink-faint} (header 0.12cqw solid {colors.ink})"
|
||||
typography: "{typography.mono-tag} + {typography.table-name} + {typography.body}"
|
||||
description: "Dense matrix row with ↑/↓/— delta."
|
||||
pixel-stack-bar:
|
||||
cells: "column-reverse, {colors.grid} off / {colors.ink} on"
|
||||
baseline: "0.12cqw solid {colors.ink} + {typography.mono-tick} ticks"
|
||||
description: "Data as grid-unit cells; echoes the glitch."
|
||||
vstack-label:
|
||||
typography: "{typography.mono-tick}"
|
||||
transform: "writing-mode: vertical-rl"
|
||||
description: "Catalogue chrome along a frame edge."
|
||||
---
|
||||
|
||||
# Cobalt Grid — Frame (video / frame layer)
|
||||
|
||||
## Overview
|
||||
|
||||
Cobalt Grid at frame scale is a **two-color risograph trend-report** — warm cream paper, electric
|
||||
cobalt ink, and a **permanent graph-paper grid** behind every frame. Cobalt is the _only_ ink:
|
||||
headlines, body, rules, the grid, the pixel-glitch decoration, the QR patches. There is no accent
|
||||
color and no second surface.
|
||||
|
||||
The voice is a three-face conversation: **Newsreader** serif at weight 400 (size, not weight,
|
||||
makes hierarchy) carries every display and headline; **Hanken Grotesk** carries body and
|
||||
uppercase tracked labels; **DM Mono** carries all chrome — page numbers, tags, ticks, vertical
|
||||
stacks. Every frame is framed by top + bottom cobalt hairlines; declarative frames carry the
|
||||
**pixel-glitch column** and a **QR-block** patch.
|
||||
|
||||
**Key characteristics at frame scale:**
|
||||
|
||||
- **Strictly two-color** — cream paper + cobalt ink; no accent, no second surface.
|
||||
- **Permanent ~2cqw graph-paper grid** (10% cobalt) behind every frame; cannot be disabled.
|
||||
- **Top + bottom 1.5px cobalt hairlines** frame every composition, inset by `edge`.
|
||||
- **Newsreader 400** for all display (negative tracking); **Hanken 600** uppercase 0.16em labels; **DM Mono** chrome.
|
||||
- **Pixel-glitch column + QR-block** as the signature decorative patches on declarative frames.
|
||||
- **Pixel-stack bars** render data as grid-unit cells (cobalt on / 10% off).
|
||||
|
||||
## The Frame
|
||||
|
||||
### Frame Craft Bar
|
||||
|
||||
Three eyeball tests gate every frame before any structural check:
|
||||
|
||||
- **Squint** — one Newsreader element dominates at **3–6× its nearest neighbor** (`display-hero`/`vbig-numeral`); hierarchy is size, never weight.
|
||||
- **Silence** — declarative frames (cover, chapter, quote, colophon) read **45–60% empty** with the grid showing; the **index ledger and data frame are the one dense exception** (density via quiet repetition, not richness).
|
||||
- **Restraint** — **two-color only** (cream + cobalt, never a second hue); one pixel-glitch column and at most one QR-block per declarative frame.
|
||||
- **Reference** — aim at a **WIRED Japan / Shift two-color risograph monograph**; failure looks like a **colorful dashboard with a second accent hue**.
|
||||
|
||||
- **Primary:** 1920×1080 (16:9). Display authored in **`cqw`** (`px ÷ 1920 × 100 = cqw`).
|
||||
- **Vertical:** 1080×1920 (9:16). **Square:** 1080×1080 (1:1).
|
||||
- **Safe area:** `edge` (4cqw) inset; hairlines + page chrome live on the safe line.
|
||||
|
||||
**The container law (load-bearing).** Every frame ground sets `container-type: size` AND carries
|
||||
the graph-paper `background-image`; ALL frame-relative units are `cqw`/`cqh` against it — never
|
||||
`vw`. The grid `background-size` is `~2cqw 2cqw` so cell density holds at any render size.
|
||||
|
||||
## Colors
|
||||
|
||||
Tokens identical to the source. `{colors.paper}` is the ground; `{colors.ink}` cobalt is the only
|
||||
ink (type, rules, grid lines, glitch, QR). `{colors.ink-soft}` is a secondary cobalt for editorial
|
||||
subtitles only; `{colors.grid}` (10%) is the permanent grid + chart "off" cells; `{colors.ink-faint}`
|
||||
(18%) is faint row dividers. **Never a second hue** — emphasis comes from size, from switching
|
||||
Hanken→Newsreader, from a mono delta arrow, or from opacity, never from color.
|
||||
|
||||
## Typography
|
||||
|
||||
Two ramps. The **reading ramp** (Hanken body 0.83cqw, mono chrome in px) carries copy + labels;
|
||||
the **display/hero ramp** (Newsreader `headline` 4.6cqw → `vbig-numeral` 12.5cqw) carries every
|
||||
statement and figure.
|
||||
|
||||
- **Legibility floor:** any load-bearing line ≥ **1.4cqw**; mono chrome (px) is colophon only.
|
||||
- **Fit-to-measure:** size the headline to its line length. Cap the block at **≤ 78cqw**; ≤3 words → `display-hero`/`vbig-numeral`; 4–6 → `display-chapter`; 7+ → `headline`.
|
||||
- **Newsreader at 400 only**, in cobalt, with negative tracking (hero −0.008em, numeral −0.015em). **Hanken labels uppercase, 0.16–0.18em.** **DM Mono 0.04–0.08em.** No bold serif.
|
||||
|
||||
## Depth & Surface
|
||||
|
||||
Flat. Depth is structural only:
|
||||
|
||||
- **1.5px cobalt rules** — slide hairlines, topbar rule, chart baseline.
|
||||
- **1px ink-faint dividers** — dense list / ledger rows.
|
||||
- **The graph-paper grid** — measured-plane tone behind everything.
|
||||
- **Pixel-glitch + QR** — texture and graphic punctuation, no z-axis.
|
||||
|
||||
**Ceiling:** no drop shadow (the QR's 1.5px paper outset is an anti-shadow for readability, not elevation), no gradient, no rounded corner, no second color.
|
||||
|
||||
## Shapes
|
||||
|
||||
- **0 radius everywhere** — frames, ledger rows, QR cells, glitch blocks, charts. Zero circular elements; this squareness is part of the identity.
|
||||
|
||||
## Components
|
||||
|
||||
- **graph-grid / hairlines / page-chrome** — the permanent frame furniture, inherited by every composition.
|
||||
- **pixel-glitch** (edge column) + **qr-block** (corner patch) — the signature decoration on declarative frames.
|
||||
- **topbar-rule** — the index/data/table section header. **ledger-row** — dense matrix row with delta arrows.
|
||||
- **pixel-stack-bar** — data as grid-unit cells. **vstack-label** — vertical mono catalogue chrome.
|
||||
|
||||
## Frame Treatments
|
||||
|
||||
> Recipe: ground · container · composes · focal · chrome · accent · silence · Fixed/Free · density.
|
||||
> The grid + hairlines are present on every frame. Choose density by type: declarative = sparse, index/data = dense.
|
||||
|
||||
### 1 · Hero Cover (identity · move: serif + glitch · left)
|
||||
|
||||
**Ground** paper + grid, hairlines. **Composes** pixel-glitch (right, ~26cqw), qr-block (top-right), display-hero, ed-callout. **Focal** a 1–2 line `display-hero` Newsreader headline in cobalt, left-anchored, with an italic `ed-callout` subtitle in `{colors.ink-soft}`. **Chrome** Hanken kicker; mono meta top + page number. **Accent** none (cobalt is the only ink). **Silence** ~45% paper. **Fixed** Newsreader 400, glitch + QR present, grid + hairlines. **Free** title, glitch step pattern, QR placement. **Density** sparse.
|
||||
|
||||
### 2 · Index Ledger (catalog · move: dense matrix · left — the dense frame)
|
||||
|
||||
**Ground** paper + grid, hairlines, `pad-top`/`pad-bottom`. **Composes** topbar-rule, ledger-rows. **Focal** a topbar (`headline-index` + mono lab-tag, 1.5px rule) over 4–6 ledger rows (mono num · Newsreader name · Hanken desc), ink-faint dividers. **Chrome** page number. **Accent** mono delta arrows. **Silence** tight — the density exception (the grid wants filling). **Fixed** 1.5px topbar rule, 1px ink-faint dividers, Newsreader names. **Free** rows, lab-tag, deltas. **Density** dense-exception.
|
||||
|
||||
### 3 · Chapter Opener (section · move: scale · sparse · left)
|
||||
|
||||
**Ground** paper + grid, hairlines. **Composes** pixel-glitch (left, ~14cqw, low opacity), mono index, display-chapter, body-lede. **Focal** a `display-chapter` Newsreader title, with a small mono `CHAPTER NN` index above and an optional Hanken lede ≤42cqw. **Accent** none. **Silence** ~60% — let the grid breathe. **Fixed** Newsreader 400, glitch low-opacity, grid showing. **Free** title, lede, glitch side. **Density** sparse.
|
||||
|
||||
### 4 · Data Frame (chart · move: pixel-stack · left)
|
||||
|
||||
**Ground** paper + grid, hairlines, `pad-top`. **Composes** topbar-rule, pixel-stack-bar row. **Focal** a row of 6–8 pixel-stack bars (cobalt on / 10% off) over a 1.5px cobalt baseline with mono ticks. **Chrome** topbar headline + mono fig-tag; page number. **Accent** none — data is cobalt cells. **Silence** moderate. **Fixed** grid-unit cells, cobalt baseline, mono ticks. **Free** bar values (from script), tick labels. **Density** standard/dense.
|
||||
|
||||
### 5 · Manifesto / Quote (quote · move: centered statement · sparse)
|
||||
|
||||
**Ground** paper + grid, hairlines. **Composes** display-quote (or display-manifesto), attribution rule, optional compact glitch. **Focal** a 2–3 line Newsreader pull in cobalt, with a Hanken kicker above and a 1px cobalt attribution rule + mono byline beneath. **Accent** none. **Silence** ~55% — deliberately open. **Fixed** Newsreader 400, attribution rule, grid showing. **Free** quote, byline. **Density** sparse.
|
||||
|
||||
### 6 · Colophon (closer · move: right-aligned close · sparse)
|
||||
|
||||
**Ground** paper + grid, hairlines. **Composes** pixel-glitch (left edge, mirroring the cover), display-closing, mono credit columns. **Focal** a right-aligned `display-closing` Newsreader title with a Hanken kicker above. **Chrome** 3–4 column mono credit grid at the foot; page number. **Accent** none. **Silence** ~50%. **Fixed** glitch on left, Newsreader 400. **Free** closing line, credits. **Density** sparse.
|
||||
|
||||
## Composition Rules
|
||||
|
||||
### Do
|
||||
|
||||
- Keep the **graph-paper grid + top/bottom hairlines** on every frame — they are the system.
|
||||
- Set **Newsreader at 400 in cobalt**; make hierarchy with size, not weight; negative-track display.
|
||||
- Track **Hanken labels uppercase 0.16em**; **DM Mono chrome 0.04–0.08em**.
|
||||
- Render data as **pixel-stack cells** (cobalt on / 10% off); echo the glitch language.
|
||||
- Use the **pixel-glitch column + QR patch** on declarative frames (cover, chapter, quote, colophon).
|
||||
- Lean: declarative frames sparse and breathing; index/data dense. Vary anchor (left for index/chapter, centered for quote/closer).
|
||||
|
||||
### Don't
|
||||
|
||||
- Don't introduce a second ink color — cream + cobalt only.
|
||||
- Don't bold Newsreader, round any corner, or add a drop shadow (QR outset is an anti-shadow only).
|
||||
- Don't disable the grid or suppress the hairlines.
|
||||
- Don't crowd chapter / quote / colophon — those let the grid show.
|
||||
- Don't blow a serif headline edge-to-edge — fit to measure.
|
||||
|
||||
## Aspect-Ratio Behavior
|
||||
|
||||
| Treatment | 16:9 | 9:16 | 1:1 |
|
||||
| ----------------- | --------------------------- | ------------------------------------ | ---------------------------- |
|
||||
| Hero Cover | headline left, glitch right | headline top, glitch full-width band | headline upper, glitch lower |
|
||||
| Index Ledger | topbar + rows | topbar + fewer rows | 2-col compresses to 1 |
|
||||
| Chapter Opener | title left, glitch left | title top, glitch side | centered title |
|
||||
| Data Frame | 6–8 bars | 4–5 bars taller | square chart |
|
||||
| Manifesto / Quote | centered pull | centered, taller | centered |
|
||||
| Colophon | right-aligned, glitch left | stacked, glitch top | centered close |
|
||||
|
||||
Grid + hairlines hold on the short edge for every ratio; re-step display so no load-bearing line
|
||||
drops below 1.4cqw. 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. Trend names, signals, and figures are content; the system supplies the grid and chrome.
|
||||
|
||||
## Numerals & Claims (hard rule)
|
||||
|
||||
Never invent figures, deltas, dates, or signal counts at frame scale. Render slots as `— figure —`,
|
||||
`{metric}`, `↑ —`. Pixel-stack bar heights and ledger deltas especially carry placeholders until the
|
||||
script supplies values. Mono catalogue ordinals (001, 002…) are decorative and may be sequential.
|
||||
|
||||
## Pre-Render Self-Audit
|
||||
|
||||
- **Squint** — one Newsreader element dominates at 3–5× its neighbor.
|
||||
- **Silence** — declarative frames 45–60% empty; only index/data run dense.
|
||||
- **Two-color** — cream + cobalt only; no second hue anywhere.
|
||||
- **Furniture** — grid present, top/bottom hairlines present, page chrome above the hairline.
|
||||
- **Type** — Newsreader 400 negative-tracked, fit-to-measure; Hanken labels 0.16em; ≥1.4cqw floor.
|
||||
- **Depth** — 0 shadow (QR outset excepted), 0 rounded corner.
|
||||
- **Anchor** — left on index/chapter/cover, centered on quote/closer; no 3 consecutive frames share an anchor.
|
||||
- **Fabrication** — every numeral/delta traces to the script, else placeholder.
|
||||
|
||||
## Known Gaps
|
||||
|
||||
- **Motion intentionally out of scope.** frame.md specifies composition only; the 280ms cross-fade in the source is a deck mechanic.
|
||||
- **Pixel-glitch is rendered in CSS here** (stacked scanline blocks) rather than the source's inline SVG, to keep the showcase SVG-free; fidelity is preserved.
|
||||
- **Three Google Fonts** (Newsreader, Hanken Grotesk, DM Mono); CJK pairing (Noto Serif SC 700/400) carries over from the source.
|
||||
- **9:16 / 1:1 are guidance**; verify the legibility floor and grid density per ratio.
|
||||
- The QR mosaic and glitch step patterns are hand-authored; there is no generative layer.
|
||||
@@ -0,0 +1,238 @@
|
||||
<!--
|
||||
Cobalt Grid — 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 cobalt 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 cream-paper card carrying the permanent graph-paper grid behind the line,
|
||||
framed by top + bottom 1.5px cobalt hairlines (cobalt's signature frame furniture) —
|
||||
square corners, NO drop shadow (cobalt's depth is structural and flat, never cast).
|
||||
Newsreader serif 400, sentence case, cobalt glyphs. Upcoming words sit in faint
|
||||
cobalt; the current word is a hard COBALT FLAT BLOCK — a risograph knockout: a solid
|
||||
cobalt fill (box-shadow spread = a square block, no blur, no layout shift) with the
|
||||
cream paper reading on top. Spoken words settle to solid cobalt ink, the block cleared.
|
||||
Strictly two-color throughout — cream + cobalt, never a second hue.
|
||||
-->
|
||||
<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 cream card — graph-paper grid behind the line + top/bottom 1.5px cobalt
|
||||
hairlines (cobalt's frame furniture), square corners, NO drop shadow */
|
||||
.caption-pill {
|
||||
position: relative;
|
||||
max-width: 78%;
|
||||
padding: 20px 44px 24px;
|
||||
background: var(--cap-canvas, #f0ebde);
|
||||
background-image:
|
||||
linear-gradient(
|
||||
to right,
|
||||
color-mix(in srgb, var(--cap-ink, #1f2be0) 10%, transparent) 1px,
|
||||
transparent 1px
|
||||
),
|
||||
linear-gradient(
|
||||
to bottom,
|
||||
color-mix(in srgb, var(--cap-ink, #1f2be0) 10%, transparent) 1px,
|
||||
transparent 1px
|
||||
);
|
||||
background-size: 16px 16px;
|
||||
border-top: 1.5px solid var(--cap-ink, #1f2be0);
|
||||
border-bottom: 1.5px solid var(--cap-ink, #1f2be0);
|
||||
border-radius: 0;
|
||||
}
|
||||
.caption-line {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 0.1em 0.34em;
|
||||
font-family: var(--font-display, "Newsreader"), Georgia, serif;
|
||||
font-weight: 400;
|
||||
font-size: clamp(40px, 4.4vw, 58px);
|
||||
line-height: 1.16;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
/* upcoming — faint cobalt, no block */
|
||||
.caption-word {
|
||||
display: inline-block;
|
||||
padding: 0 0.06em;
|
||||
color: color-mix(in srgb, var(--cap-ink, #1f2be0) 42%, var(--cap-canvas, #f0ebde));
|
||||
}
|
||||
/* current word — THE cobalt flat block (risograph knockout): a solid cobalt fill
|
||||
via box-shadow spread = a hard square block w/o layout shift, no blur; the cream
|
||||
paper reads on top */
|
||||
.caption-word.is-active {
|
||||
color: var(--cap-canvas, #f0ebde);
|
||||
background: var(--cap-ink, #1f2be0);
|
||||
box-shadow: 0 0 0 0.06em var(--cap-ink, #1f2be0);
|
||||
}
|
||||
/* already spoken — solid cobalt ink, block cleared */
|
||||
.caption-word.is-spoken {
|
||||
color: var(--cap-ink, #1f2be0);
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* optional per-word accents (dormant until a word-classer tags them) */
|
||||
/* brand / key term — Newsreader italic, cobalt's only display inflection */
|
||||
.caption-word.cap-brand {
|
||||
font-style: italic;
|
||||
}
|
||||
/* numerals / data — the body sans voice, slightly recessed */
|
||||
.caption-word.cap-num {
|
||||
font-family: var(--font-body, "Hanken Grotesk"), ui-monospace, monospace;
|
||||
color: color-mix(in srgb, var(--cap-ink, #1f2be0) 72%, var(--cap-canvas, #f0ebde));
|
||||
}
|
||||
/* emphasis — a cobalt hairline underline (no second hue; emphasis is the ink) */
|
||||
.caption-word.cap-cta {
|
||||
text-decoration: underline;
|
||||
text-decoration-thickness: 2px;
|
||||
text-decoration-color: var(--cap-accent, #5560e5);
|
||||
text-underline-offset: 0.1em;
|
||||
}
|
||||
|
||||
@media (max-aspect-ratio: 9/16) {
|
||||
.caption-pill {
|
||||
max-width: 92%;
|
||||
}
|
||||
.caption-line {
|
||||
font-size: clamp(34px, 5.4vw, 48px);
|
||||
}
|
||||
}
|
||||
</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.985 }, { 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
@@ -0,0 +1,314 @@
|
||||
---
|
||||
version: alpha
|
||||
name: Coral — Frame (video / frame layer)
|
||||
description: >
|
||||
Video-first companion to Coral's design.md. The unit is the frame (1920×1080), not the
|
||||
slide-in-a-deck. Atoms are identical and sacred — the three-surface system (coral fire /
|
||||
ink black / warm cream), Bebas Neue uppercase tracked + Inter body, the 45° diagonal hatch,
|
||||
decorative wallpaper numerals, hard color-region splits, zero shadow, zero radius (save
|
||||
circles). Composition, frame scale, and aspect-ratio behavior are rewritten for the frame.
|
||||
Motion is 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:
|
||||
coral: "#E85D5D"
|
||||
coral-dark: "#D44A4A"
|
||||
cream: "#F5F0E8"
|
||||
cream-dark: "#E8E0D4"
|
||||
black: "#1A1A1A"
|
||||
gray: "#6B6B6B"
|
||||
light-gray: "#B0B0B0"
|
||||
white: "#FFFFFF"
|
||||
|
||||
typography:
|
||||
# — reading ramp (Inter) —
|
||||
body: { fontFamily: "Inter", cqw: 1.0, weight: 400, lineHeight: 1.7 }
|
||||
body-light: { fontFamily: "Inter", cqw: 1.5, weight: 300, lineHeight: 1.5, note: "pull-quote voice" }
|
||||
section-label: { fontFamily: "Inter", px: 12, weight: 700, tracking: "4px", upper: true }
|
||||
item-label: { fontFamily: "Inter", px: 11, weight: 700, tracking: "3px", upper: true }
|
||||
quote-attribution: { fontFamily: "Inter", px: 14, weight: 600, tracking: "3px", upper: true }
|
||||
quote-role: { fontFamily: "Inter", px: 12, weight: 400, tracking: "1px" }
|
||||
# — display / hero ramp (Bebas Neue, uppercase, tracked) —
|
||||
card-title: { fontFamily: "Bebas Neue", cqw: 1.9, weight: 400, lineHeight: 1.1, tracking: "1px", upper: true }
|
||||
sidebar-value: { fontFamily: "Bebas Neue", cqw: 2.3, weight: 400, lineHeight: 1.0, upper: true }
|
||||
bar-title: { fontFamily: "Bebas Neue", cqw: 2.3, weight: 400, lineHeight: 1.0, tracking: "2px", upper: true }
|
||||
card-stat: { fontFamily: "Bebas Neue", cqw: 2.5, weight: 400, lineHeight: 1.0, upper: true }
|
||||
column-title: { fontFamily: "Bebas Neue", cqw: 3.7, weight: 400, lineHeight: 1.0, tracking: "2px", upper: true }
|
||||
section-headline:{ fontFamily: "Bebas Neue", cqw: 4.2, weight: 400, lineHeight: 1.0, tracking: "2px", upper: true }
|
||||
stat-numeral: { fontFamily: "Bebas Neue", cqw: 5.0, weight: 400, lineHeight: 1.0, upper: true }
|
||||
hero-title: { fontFamily: "Bebas Neue", cqw: 6.5, weight: 400, lineHeight: 0.9, tracking: "4px", upper: true }
|
||||
jumbo-feature: { fontFamily: "Bebas Neue", cqw: 9.0, weight: 400, lineHeight: 1.0, tracking: "12px", upper: true }
|
||||
# — decorative —
|
||||
background-numeral: { fontFamily: "Bebas Neue", cqw: 10.0, weight: 400, color: "rgba(0,0,0,0.12)", note: "wallpaper numeral inside a coral region" }
|
||||
giant-mark: { fontFamily: "Bebas Neue", cqw: 14.0, weight: 400, color: "rgba(0,0,0,0.35)", note: "decorative quote mark inside a coral region" }
|
||||
|
||||
spacing:
|
||||
pad-x: "5cqw" # standard horizontal frame padding
|
||||
pad-y: "4cqw"
|
||||
pad-col: "3cqw"
|
||||
gap-grid: "1.7cqw"
|
||||
card-pad: "2cqw"
|
||||
|
||||
components:
|
||||
diagonal-hatch:
|
||||
backgroundImage: "repeating-linear-gradient(45deg, transparent 0 20px, rgba(0,0,0,.06) 20px 40px)"
|
||||
placement: "::before overlay on {colors.coral} regions"
|
||||
description: "Signature 45° hatch (6% ink). Variants −45° 30/60px, 90° vertical 60/62px 10% ink. Texture, never depth."
|
||||
region-split:
|
||||
layout: "two/three solid surfaces meeting at a hard edge; ratios 38/62 rows, 40/60 cols, 50/50"
|
||||
rounded: "0"
|
||||
description: "The primary layout device — no gradient, no rounded junction; the boundary is the layout."
|
||||
card:
|
||||
backgroundColor: "{colors.white}"
|
||||
borderTop: "0.26cqw solid {colors.coral}"
|
||||
rounded: "0"
|
||||
shadow: "none"
|
||||
typography: "{typography.card-title} + {typography.body}"
|
||||
description: "5px coral TOP border is the only chrome; holds a card-icon, title, body, coral card-stat."
|
||||
sidebar-item:
|
||||
backgroundColor: "{colors.white}"
|
||||
borderLeft: "0.2cqw solid {colors.coral}"
|
||||
rounded: "0"
|
||||
typography: "{typography.sidebar-value} + {typography.section-label}"
|
||||
description: "4px coral LEFT border is the only chrome."
|
||||
card-icon:
|
||||
backgroundColor: "{colors.coral}"
|
||||
textColor: "{colors.white}"
|
||||
size: "2.5cqw square"
|
||||
rounded: "0"
|
||||
description: "The card mark — one white Bebas glyph centered."
|
||||
accent-line:
|
||||
backgroundColor: "{colors.coral}"
|
||||
size: "4cqw × 0.25cqw (60×4 closing variant)"
|
||||
rounded: "0"
|
||||
description: "Sub-headline accent rule."
|
||||
background-numeral:
|
||||
typography: "{typography.background-numeral}"
|
||||
color: "rgba(0,0,0,0.12)"
|
||||
placement: "behind a {colors.coral} region's title"
|
||||
description: "The wallpaper-numeral signature (12% ink)."
|
||||
giant-mark:
|
||||
typography: "{typography.giant-mark}"
|
||||
color: "rgba(0,0,0,0.35)"
|
||||
placement: "inside a {colors.coral} region"
|
||||
description: "Oversized quote mark / character, half-decorative."
|
||||
timeline:
|
||||
line: "0.2cqw solid {colors.black} (gradient-dashed ::after)"
|
||||
node: "{colors.coral} circle, {colors.cream} halo, 50% radius"
|
||||
description: "Ink line with coral nodes + cream halos."
|
||||
info-bar:
|
||||
backgroundColor: "{colors.cream-dark}"
|
||||
typography: "{typography.bar-title} + uppercase {typography.section-label}"
|
||||
rounded: "0"
|
||||
shadow: "none"
|
||||
description: "Footer band beneath a feature region — Bebas title left, Inter meta right."
|
||||
---
|
||||
|
||||
# Coral — Frame (video / frame layer)
|
||||
|
||||
## Overview
|
||||
|
||||
Coral at frame scale is a **bold magazine poster** built from three solid surfaces — coral fire,
|
||||
ink black, warm cream — that meet at **hard color edges.** The region boundary IS the layout: a
|
||||
frame splits into a coral plane + a cream plane, or a coral panel + an ink panel, each holding a
|
||||
self-contained composition. No gradient transitions, no rounded junctions, no drop shadows.
|
||||
|
||||
The voice is a two-face hierarchy: **Bebas Neue** — tall condensed caps, always uppercase, always
|
||||
tracked (1–12px) — carries every headline, stat, title, and meta figure; **Inter** carries every
|
||||
body line, label, and attribution across weights 300–700. Bebas declares; Inter explains. The
|
||||
signature atmospherics are the **45° diagonal hatch** (6% ink) over coral regions and the
|
||||
**oversized wallpaper numeral** (12% ink) behind a region's title.
|
||||
|
||||
**Key characteristics at frame scale:**
|
||||
|
||||
- **Three surfaces, hard edges** — `{colors.coral}` / `{colors.black}` / `{colors.cream}` as solid regions.
|
||||
- **Bebas uppercase + tracking** on every display element; **Inter** on every body/label.
|
||||
- **45° hatch** (6% ink) on coral regions; **wallpaper numerals** (12%) and **giant marks** (35%) behind content.
|
||||
- **Ink-on-fire** — Bebas on coral is always ink, never white. Eyebrows coral on cream/ink, ink on coral.
|
||||
- **Flat** — no shadow, no elevation; radius only on circles (nav dots, timeline nodes).
|
||||
- **Coral as accent AND environment** — 4–5px coral borders, 48px coral icon squares, and full coral regions.
|
||||
|
||||
### Frame Craft Bar
|
||||
|
||||
Three eyeball tests gate every frame before any structural check:
|
||||
|
||||
- **Squint** — one element dominates at **3–6× its nearest neighbor**: the `hero-title`/`jumbo-feature` or a wallpaper numeral behind a region's title, never two rival headlines.
|
||||
- **Silence** — coral/cream/ink regions read **40–55% empty**; the **three-column catalog is the one dense exception**. A coral region underfilled gets a wallpaper numeral, never more content.
|
||||
- **Restraint** — coral fires as **either accent or one full region per frame** (not both at full strength); one giant-mark per quote; ink-on-fire (never white on coral).
|
||||
- **Reference** — aim at a **sports-magazine cover / Saul Bass travel poster** (solid planes at hard edges, condensed caps as architecture); failure looks like a **soft drop-shadowed card deck**.
|
||||
|
||||
## The Frame
|
||||
|
||||
- **Primary:** 1920×1080 (16:9). Display sizes authored in **`cqw`** (`px ÷ 1920 × 100 = cqw`).
|
||||
- **Vertical:** 1080×1920 (9:16). **Square:** 1080×1080 (1:1).
|
||||
- **Safe area:** `5cqw` (pad-x) standard frame padding; region edges may bleed full-frame.
|
||||
|
||||
**The container law (load-bearing).** Every frame ground sets `container-type: size`; ALL
|
||||
frame-relative units are `cqw`/`cqh` resolved against it — **never `vw`.** A `vw`-sized frame
|
||||
inflates whenever it isn't full-screen; `cqw` resolves against the frame at any render size.
|
||||
|
||||
## Colors
|
||||
|
||||
Tokens identical to the source. At frame scale the three surfaces are intermixed by composition —
|
||||
coral/cream, coral/ink, ink/cream, or single-surface. `{colors.coral}` is both accent (borders,
|
||||
icon squares, timeline nodes, eyebrows on cream/ink) and environment (full regions). Headlines:
|
||||
ink on cream/coral, cream on ink — **never gray, never white-on-coral.** Eyebrows: coral on
|
||||
cream/ink, ink on coral — coral-on-coral does not exist. The only sanctioned gradient is the rare
|
||||
135° coral-dark→coral feature region; everything else is flat.
|
||||
|
||||
## Typography
|
||||
|
||||
Two ramps. The **reading ramp** (Inter body 1.0cqw, body-light 1.5cqw, labels in px) carries copy
|
||||
and eyebrows; the **display/hero ramp** (Bebas, `card-title` 1.9cqw → `jumbo-feature` 9.0cqw, plus
|
||||
the decorative `background-numeral` 10cqw and `giant-mark` 14cqw) carries every headline and stat.
|
||||
|
||||
- **Legibility floor:** any load-bearing line ≥ **1.4cqw**; px labels are chrome only.
|
||||
- **Fit-to-measure:** size the headline to its line length. Cap the block at **≤ 78cqw**; ≤3 words → `hero-title`/`jumbo-feature`; 4–6 → `section-headline`; 7+ → `column-title`.
|
||||
- **Every Bebas element is uppercase with ≥1px tracking** (2px standard, 4px hero, 12px jumbo). **Every Inter label is uppercase, 1–4px tracked.** No italic, no underline, no sentence-case Bebas.
|
||||
|
||||
## Depth & Surface
|
||||
|
||||
Flat, with hard color edges. Depth signals only:
|
||||
|
||||
- **Hard region boundaries** — the primary structural device.
|
||||
- **Accent borders** — 5px coral top (cards), 4px coral left (sidebar tiles), 4px ink (timeline).
|
||||
- **45° hatch** — 6% ink texture on coral regions (no depth).
|
||||
- **Wallpaper typography** — numerals at 12%, giant marks at 35%, layered behind content.
|
||||
|
||||
**Ceiling:** no box-shadow, no elevated card, no soft gradient (save the one 135° coral feature), no rounded rectangle.
|
||||
|
||||
## Shapes
|
||||
|
||||
- **0 radius** on every rectangle — regions, cards, sidebar tiles, icon squares, info bars, accent lines.
|
||||
- **50%** on circles only — nav dots (10px), nav arrows (44px), timeline nodes (20px).
|
||||
|
||||
## Components
|
||||
|
||||
- **region-split** — the layout device; surfaces meet at a hard edge.
|
||||
- **card** (5px coral top) / **sidebar-item** (4px coral left) / **card-icon** (48px coral square) — the only chrome on each is its single coral border.
|
||||
- **diagonal-hatch** / **background-numeral** / **giant-mark** — the atmospheric + wallpaper signatures on coral regions.
|
||||
- **accent-line** — coral sub-headline rule. **timeline** — ink line, coral nodes, cream halos. **info-bar** — cream-dark footer band.
|
||||
|
||||
## Frame Treatments
|
||||
|
||||
> Recipe per plate: ground · container · composes · focal · chrome · accent · silence · Fixed/Free · density.
|
||||
> Lean centered where the move allows; vary anchor; one idea per region.
|
||||
|
||||
### 1 · Region-Split Cover (identity · move: hard region edge · left)
|
||||
|
||||
**Ground** 38/62 split — `{colors.coral}` top band (hatch + wallpaper numeral) over `{colors.cream}`.
|
||||
**Container** grid rows; brand + meta in the coral band, hero title in the cream field. **Composes**
|
||||
region-split, diagonal-hatch, background-numeral, hero-title. **Focal** a 2-line `hero-title` in
|
||||
ink, second line in `{colors.coral}`, left-anchored in the cream field. **Chrome** Bebas brand left
|
||||
|
||||
- Bebas meta right in the coral band. **Accent** coral band + coral second line. **Silence** the
|
||||
cream field ~45% empty. **Fixed** ink-on-fire in band, hatch on coral, hard edge. **Free** title
|
||||
copy, which line is coral, meta. **Density** sparse.
|
||||
|
||||
### 2 · Feature Stat (anchor · move: scale · coral environment · left)
|
||||
|
||||
**Ground** full `{colors.coral}` with hatch. **Composes** diagonal-hatch, background-numeral,
|
||||
section-label, stat headline, body-light. **Focal** a `stat-numeral`/`jumbo-feature` figure or
|
||||
2-line headline in ink, with a `background-numeral` (12% ink) behind it as wallpaper. **Chrome** an
|
||||
ink `section-label` eyebrow; an optional Inter-300 support line ≤44cqw. **Accent** the coral ground
|
||||
IS the environment; ink type, no white. **Silence** ~40% of the coral field empty. **Fixed**
|
||||
ink-on-coral, hatch present, wallpaper numeral behind. **Free** the figure, headline, support copy.
|
||||
**Density** sparse.
|
||||
|
||||
### 3 · Quote Layout (quote · move: panel split · giant mark)
|
||||
|
||||
**Ground** 40/60 split — `{colors.coral}` left panel (hatch + giant-mark) + `{colors.black}` right
|
||||
panel. **Composes** region-split, giant-mark, body-light, accent-line, quote-attribution.
|
||||
**Focal** a 2–3 line pull quote in `{colors.cream}` Inter **weight 300** on the ink panel.
|
||||
**Chrome** a `giant-mark` (35% ink) on the coral panel; a `60×4` coral accent-line above the
|
||||
attribution. **Accent** coral panel + coral accent-line. **Silence** the coral panel is mostly the
|
||||
mark. **Fixed** Inter-300 quote, ink panel, ink-on-coral mark. **Free** quote, attribution, mark
|
||||
glyph. **Density** sparse.
|
||||
|
||||
### 4 · Closing Plate (closer · move: cream field + coral band · centered)
|
||||
|
||||
**Ground** `{colors.cream}` field with a bottom `{colors.coral}` band (hatch). **Composes**
|
||||
section-label, section-headline/hero-title, accent-line, info-bar. **Focal** a 2-line sign-off in
|
||||
ink, centered, with a coral `accent-line` beneath. **Chrome** an ink eyebrow above; a coral band
|
||||
footer carrying a Bebas sign-off + year. **Accent** coral band + coral accent-line. **Silence**
|
||||
~55% empty cream. **Fixed** centered, ink type, one coral band. **Free** sign-off copy, band
|
||||
contents. **Density** sparse.
|
||||
|
||||
### 5 · Three-Column Catalog (catalog · move: density — the dense frame · centered head)
|
||||
|
||||
**Ground** `{colors.cream}` (or `{colors.black}`), `pad-x`. **Composes** section-headline, 3× card.
|
||||
**Focal** a centered `section-headline` over three white `card`s (5px coral top, 48px icon square,
|
||||
Bebas title, Inter body, coral stat). **Accent** the three coral top borders + icon squares.
|
||||
**Silence** tight — the density exception. **Fixed** 5px coral top as sole chrome, no shadow/radius.
|
||||
**Free** the three cards' content. **Density** dense-exception.
|
||||
|
||||
### 6 · Timeline (process · move: horizontal rail · left)
|
||||
|
||||
**Ground** `{colors.cream}`, `pad-x`. **Composes** section-headline, timeline. **Focal** the ink
|
||||
timeline-line with 4–5 coral nodes (cream halos) and Bebas labels. **Accent** coral nodes.
|
||||
**Silence** moderate. **Fixed** ink line, coral nodes, cream halos. **Free** node count, labels.
|
||||
**Density** standard.
|
||||
|
||||
## Composition Rules
|
||||
|
||||
### Do
|
||||
|
||||
- Compose as **multi-surface region splits** — coral / ink / cream meeting at hard edges; the boundary is the layout.
|
||||
- Set every Bebas element **uppercase + tracked** (2px standard, 4px hero, 12px jumbo); every Inter label uppercase, 1–4px.
|
||||
- Render eyebrows **coral on cream/ink, ink on coral**; headlines ink on cream/coral, cream on ink.
|
||||
- Apply the **45° hatch** (6% ink) on coral regions; fill underweight coral regions with a **12% wallpaper numeral**.
|
||||
- Use the **5px coral top** (cards) / **4px coral left** (tiles) as the only chrome on those elements.
|
||||
- Lean centered on cover sign-offs and catalog heads; left/panel-split on features and quotes.
|
||||
|
||||
### Don't
|
||||
|
||||
- Don't render Bebas in sentence case or untracked; don't pair it with a non-Inter body sans.
|
||||
- Don't add a fourth surface, a drop shadow, an elevation, or a rounded rectangle.
|
||||
- Don't put white headlines on coral (always ink) or gray headlines anywhere (gray is body/meta).
|
||||
- Don't soften a region boundary with a gradient (except the rare 135° coral feature).
|
||||
- Don't fill a coral region with sparse fragments — fully populate it or add a wallpaper numeral / giant mark.
|
||||
- Don't blow a headline edge-to-edge — step the ramp down for long lines.
|
||||
|
||||
## Aspect-Ratio Behavior
|
||||
|
||||
| Treatment | 16:9 | 9:16 | 1:1 |
|
||||
| -------------------- | -------------------------- | ------------------------------------ | -------------------- |
|
||||
| Region-Split Cover | 38/62 rows, title left | taller coral band, title below | 40/60, title lower |
|
||||
| Feature Stat | figure left, numeral right | figure top, numeral behind | centered figure |
|
||||
| Quote Layout | 40/60 coral+ink | stacked: coral mark top, quote below | stacked |
|
||||
| Closing Plate | cream + bottom coral band | cream + taller band | centered, band below |
|
||||
| Three-Column Catalog | head over 3-up | head top, 3 stacked | head top, 2+1 |
|
||||
| Timeline | horizontal rail | vertical rail | compact horizontal |
|
||||
|
||||
Safe area holds the `5cqw` padding on the short edge; re-step display per ratio so no load-bearing
|
||||
line drops below the 1.4cqw floor. Bebas runs ~20% wider in CJK — adjust line breaks per ratio.
|
||||
|
||||
## Approved Entities
|
||||
|
||||
No real customers, logos, or vendors are defined in the source — render any such mark as a
|
||||
placeholder. The system supplies surfaces and geometry, not brands.
|
||||
|
||||
## Numerals & Claims (hard rule)
|
||||
|
||||
Never invent figures, stats, dates, or counts at frame scale. Render slots as `— figure —`,
|
||||
`{metric}`, `N×`. Real numerals appear only when the script supplies them — the feature stat,
|
||||
catalog, and timeline especially carry placeholders, not fabricated values. Wallpaper numerals
|
||||
(01, 02…) are decorative and may be ordinal.
|
||||
|
||||
## Pre-Render Self-Audit
|
||||
|
||||
- **Squint** — one focal element per region dominates at 3–5× its neighbor.
|
||||
- **Silence** — sparse frames 40–55% empty; only the catalog runs dense.
|
||||
- **Surfaces** — two or three of coral/ink/cream, meeting at hard edges; no fourth surface.
|
||||
- **Type** — Bebas uppercase + tracked, fit-to-measure; ink-on-coral; eyebrow color correct for surface; ≥1.4cqw floor.
|
||||
- **Depth** — 0 shadow, 0 rounded rectangle; hatch + wallpaper carry texture.
|
||||
- **Anchor** — centered on sign-offs/heads, panel/left on features/quotes; no 3 consecutive frames share an anchor.
|
||||
- **Fabrication** — every numeral traces to the script, else placeholder.
|
||||
|
||||
## Known Gaps
|
||||
|
||||
- **Motion intentionally out of scope.** frame.md specifies composition only; timing and transitions are a later stage. The 0.6s opacity fade in the source is a deck mechanic, not a frame spec.
|
||||
- **Bebas Neue + Inter via Google Fonts.** CJK pairing (ZCOOL XiaoWei / Yozai) carries over from the source's CJK section; Bebas runs ~20% wider in CJK.
|
||||
- **9:16 / 1:1 are guidance**, not pixel-locked; verify the legibility floor per ratio.
|
||||
- The 45° hatch, wallpaper numerals, giant marks, and timeline dash are CSS-only; no external imagery is required.
|
||||
@@ -0,0 +1,226 @@
|
||||
<!--
|
||||
Coral — 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 coral 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 flat warm-cream block on a single ink hairline border — coral's hard-edge
|
||||
geometry, square corners, ZERO radius, ZERO shadow (coral's depth is hard color edges,
|
||||
never cast). Bebas Neue, uppercase + tracked, ink glyphs. Upcoming words sit in faint
|
||||
ink; the current word gets a solid coral-fire flat block (the brand's signature
|
||||
hard-edge fill — a square block, no blur, no radius) with ink reading on top; spoken
|
||||
words settle to solid ink, fill cleared. Brand/key terms carry a coral hairline
|
||||
underline (the counter inflection).
|
||||
-->
|
||||
<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 flat cream block — single ink hairline border, square corners (coral's hard-edge
|
||||
geometry), ZERO radius, ZERO shadow */
|
||||
.caption-pill {
|
||||
max-width: 78%;
|
||||
padding: 18px 44px 20px;
|
||||
background: var(--cap-canvas, #f5f0e8);
|
||||
border: 1px solid var(--cap-ink, #1a1a1a);
|
||||
border-radius: 0;
|
||||
}
|
||||
.caption-line {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 0.1em 0.3em;
|
||||
font-family: var(--font-display, "Bebas Neue"), sans-serif;
|
||||
font-weight: 400;
|
||||
text-transform: uppercase;
|
||||
font-size: clamp(44px, 4.8vw, 64px);
|
||||
line-height: 1.04;
|
||||
letter-spacing: 0.06em;
|
||||
}
|
||||
|
||||
/* upcoming — faint ink, no fill */
|
||||
.caption-word {
|
||||
display: inline-block;
|
||||
padding: 0 0.12em;
|
||||
color: color-mix(in srgb, var(--cap-ink, #1a1a1a) 40%, var(--cap-canvas, #f5f0e8));
|
||||
}
|
||||
/* current word — THE coral-fire flat block (box-shadow spread = a hard square block
|
||||
w/o layout shift; no blur, no radius), ink reading on top */
|
||||
.caption-word.is-active {
|
||||
color: var(--cap-ink, #1a1a1a);
|
||||
background: var(--cap-accent, #e85d5d);
|
||||
box-shadow: 0 0 0 0.1em var(--cap-accent, #e85d5d);
|
||||
}
|
||||
/* already spoken — solid ink, fill cleared */
|
||||
.caption-word.is-spoken {
|
||||
color: var(--cap-ink, #1a1a1a);
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* optional per-word accents (dormant until a word-classer tags them) */
|
||||
/* brand / key term — a coral hairline underline (coral's only display inflection) */
|
||||
.caption-word.cap-brand {
|
||||
text-decoration: underline;
|
||||
text-decoration-thickness: 0.06em;
|
||||
text-decoration-color: var(--cap-accent, #e85d5d);
|
||||
text-underline-offset: 0.12em;
|
||||
}
|
||||
/* numerals / data — Inter body voice, slightly recessed */
|
||||
.caption-word.cap-num {
|
||||
font-family: var(--font-body, "Inter"), ui-sans-serif, sans-serif;
|
||||
color: color-mix(in srgb, var(--cap-ink, #1a1a1a) 72%, var(--cap-canvas, #f5f0e8));
|
||||
}
|
||||
/* emphasis — a coral-dark hairline underline (the counter accent) */
|
||||
.caption-word.cap-cta {
|
||||
text-decoration: underline;
|
||||
text-decoration-thickness: 2px;
|
||||
text-decoration-color: var(--cap-accent-2, #d44a4a);
|
||||
text-underline-offset: 0.1em;
|
||||
}
|
||||
|
||||
@media (max-aspect-ratio: 9/16) {
|
||||
.caption-pill {
|
||||
max-width: 92%;
|
||||
}
|
||||
.caption-line {
|
||||
font-size: clamp(38px, 5.8vw, 54px);
|
||||
}
|
||||
}
|
||||
</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.97 }, { scale: 1, duration: 0.14, ease: "power2.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
@@ -0,0 +1,333 @@
|
||||
---
|
||||
version: alpha
|
||||
name: Creative Mode — Frame (video / frame layer)
|
||||
description: >
|
||||
Video-first companion to Creative Mode's design.md. The unit is the frame (1920×1080),
|
||||
not the slide-in-a-deck. Atoms are identical and sacred — warm cream canvas, 4px ink
|
||||
borders, hard offset shadows (no blur), Archivo Black uppercase at 0.92 line-height,
|
||||
JetBrains Mono taxonomy, Space Grotesk body, the four-accent palette. Composition,
|
||||
frame scale, and aspect-ratio behavior are rewritten for the frame. Motion is 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:
|
||||
cream: "#EFE9D9"
|
||||
cream-2: "#E4DCC4"
|
||||
ink: "#0F0F0F"
|
||||
ink-2: "#2A2A2A"
|
||||
green: "#1F8A4C"
|
||||
green-dark: "#136636"
|
||||
pink: "#F06CA8"
|
||||
pink-dark: "#D14E8B"
|
||||
orange: "#E85A1F"
|
||||
yellow: "#F5C518"
|
||||
|
||||
typography:
|
||||
# — reading ramp (px @ 1920, with cqw) —
|
||||
body-lg: { fontFamily: "Space Grotesk", px: 28, cqw: 1.46, weight: 400, lineHeight: 1.4 }
|
||||
body-md: { fontFamily: "Space Grotesk", px: 24, cqw: 1.25, weight: 400, lineHeight: 1.3 }
|
||||
mono-label: { fontFamily: "JetBrains Mono", px: 24, cqw: 1.25, weight: 400, tracking: "0.06em", upper: true }
|
||||
mono-kicker:{ fontFamily: "JetBrains Mono", px: 24, cqw: 1.25, weight: 400, tracking: "0.14em", upper: true }
|
||||
table-head: { fontFamily: "Archivo Black", px: 28, cqw: 1.46, weight: 400, upper: true }
|
||||
# — display / hero ramp (frame-native, cqw-first) —
|
||||
step-title: { fontFamily: "Archivo Black", px: 34, cqw: 1.77, weight: 400, lineHeight: 1.0, upper: true }
|
||||
badge-label:{ fontFamily: "Archivo Black", px: 28, cqw: 1.46, weight: 400, upper: true }
|
||||
marker: { fontFamily: "Archivo Black", cqw: 2.4, weight: 400, lineHeight: 1.0, upper: true }
|
||||
stamp-num: { fontFamily: "Archivo Black", cqw: 2.2, weight: 400, lineHeight: 0.9 }
|
||||
stat-num: { fontFamily: "Archivo Black", cqw: 6.4, weight: 400, lineHeight: 0.88 }
|
||||
step-num: { fontFamily: "Archivo Black", cqw: 7.0, weight: 400, lineHeight: 0.85 }
|
||||
display-head: { fontFamily: "Archivo Black", cqw: 4.2, weight: 400, lineHeight: 0.92, tracking: "-0.01em", upper: true }
|
||||
display-lg: { fontFamily: "Archivo Black", cqw: 8.0, weight: 400, lineHeight: 0.9, tracking: "-0.01em", upper: true }
|
||||
display-xl: { fontFamily: "Archivo Black", cqw: 11.0, weight: 400, lineHeight: 0.9, tracking: "-0.01em", upper: true }
|
||||
display-hero:{ fontFamily: "Archivo Black", cqw: 15.5,weight: 400, lineHeight: 0.84, tracking: "-0.02em", upper: true }
|
||||
|
||||
spacing:
|
||||
frame-pad: "3.3cqw" # 64px chrome gutter @1920
|
||||
content-gutter: "5cqw" # 96px content gutter @1920
|
||||
grid-gap: "1.5cqw" # 28px
|
||||
cell-pad: "1.7cqw" # 32px
|
||||
|
||||
components:
|
||||
frame-chrome:
|
||||
typography: "{typography.mono-label}"
|
||||
placement: "topbar 2.5cqw from top, meta 2.5cqw from bottom, both inset {spacing.frame-pad}"
|
||||
rounded: "0"
|
||||
shadow: "none"
|
||||
description: "Mono topbar (section label left + 999px ink-stroked pill right) + meta footer (descriptor left + NN • NN counter right, 0.5cqw ink dot divider). Present on most frames."
|
||||
stat-cell:
|
||||
backgroundColor: "{colors.green} · {colors.pink} · {colors.orange} · {colors.cream}"
|
||||
textColor: "{colors.cream} on accent · {colors.ink} on cream"
|
||||
border: "0.4cqw solid {colors.ink}"
|
||||
rounded: "0"
|
||||
padding: "{spacing.cell-pad}"
|
||||
typography: "{typography.stat-num} + {typography.mono-label}"
|
||||
shadow: "none"
|
||||
description: "Flat accent/cream stat tile, square corners, no shadow."
|
||||
step-card:
|
||||
backgroundColor: "{colors.cream} · {colors.pink} · {colors.yellow} · {colors.green}"
|
||||
border: "0.4cqw solid {colors.ink}"
|
||||
rounded: "0"
|
||||
padding: "{spacing.cell-pad}"
|
||||
typography: "{typography.step-num} + {typography.step-title} + {typography.body-md}"
|
||||
shadow: "none"
|
||||
description: "Ink-bordered card; giant step-num top. Sequence alternates cream with accents and ENDS on green."
|
||||
marker-block:
|
||||
backgroundColor: "{colors.pink}"
|
||||
textColor: "{colors.ink}"
|
||||
border: "0.4cqw solid {colors.ink}"
|
||||
rounded: "0"
|
||||
typography: "{typography.marker}"
|
||||
shadow: "1.25cqw 1.25cqw 0 {colors.orange}, 1.25cqw 1.25cqw 0 0.2cqw {colors.ink}"
|
||||
description: "The one hard-offset featured callout per frame."
|
||||
kicker-block:
|
||||
backgroundColor: "{colors.ink}"
|
||||
textColor: "{colors.cream}"
|
||||
typography: "{typography.mono-kicker}"
|
||||
padding: "0.5cqw 1cqw"
|
||||
rounded: "0"
|
||||
description: "Inverted eyebrow chip."
|
||||
badge-rotated:
|
||||
backgroundColor: "{colors.yellow}"
|
||||
border: "0.4cqw solid {colors.ink}"
|
||||
typography: "{typography.badge-label}"
|
||||
rounded: "0"
|
||||
transform: "rotate(-4deg)"
|
||||
description: "Deliberate-imperfection annotation."
|
||||
pill-badge:
|
||||
backgroundColor: "{colors.cream}"
|
||||
border: "0.2cqw solid {colors.ink}"
|
||||
rounded: "999px"
|
||||
typography: "{typography.mono-label}"
|
||||
description: "The ONLY rounded element; reads as a chip, not a card."
|
||||
stamp:
|
||||
backgroundColor: "{colors.pink}"
|
||||
border: "0.4cqw solid {colors.cream}"
|
||||
size: "~18cqw square"
|
||||
rounded: "0"
|
||||
transform: "rotate(-6deg)"
|
||||
typography: "{typography.stamp-num}"
|
||||
description: "Closing seal with a cream circular inner ring."
|
||||
comparison-table:
|
||||
backgroundColor: "{colors.cream-2}"
|
||||
border: "0.4cqw solid {colors.ink}"
|
||||
rule: "0.3cqw solid {colors.ink}"
|
||||
rounded: "0"
|
||||
typography: "{typography.table-head} + {typography.body-md}"
|
||||
shadow: "none"
|
||||
description: "Ink head row with cream Archivo labels; pink/green column-fill variants."
|
||||
decorative-circle:
|
||||
backgroundColor: "{colors.yellow}"
|
||||
border: "0.4cqw solid {colors.ink}"
|
||||
rounded: "50%"
|
||||
description: "Decorative figure; pairs with a green panel for shape contrast."
|
||||
---
|
||||
|
||||
# Creative Mode — Frame (video / frame layer)
|
||||
|
||||
## Overview
|
||||
|
||||
Creative Mode at frame scale is a **neo-brutalist editorial poster in motion's clothing** — warm
|
||||
cream paper, near-black ink, and four accents that collide at full saturation. Every frame is one
|
||||
flat color-blocked composition: no gradients, no blurred shadows, no rounded cards (save the one
|
||||
pill chip). Depth is **hard offset shadow** (a solid same-direction duplicate) or **color-block
|
||||
contrast**, never light.
|
||||
|
||||
The display voice is **Archivo Black in strict uppercase at 0.92 line-height** — letters overlap
|
||||
their own cap height; that tightness is the brand. **JetBrains Mono** carries every label, kicker,
|
||||
counter, and axis as a "technical artifact" register. **Space Grotesk** carries the few body lines.
|
||||
The frame is loud by construction and calm by restraint: two or three accents per frame, the green
|
||||
ground reserved for a single closing plate, the hard shadow spent on one featured element only.
|
||||
|
||||
**Key characteristics at frame scale:**
|
||||
|
||||
- **Cream ground** (`{colors.cream}`) on nearly every frame; `{colors.green}` reserved for the closing plate.
|
||||
- **0.4cqw (4px @1920) ink borders** on every structural element; 0.3cqw internal rules.
|
||||
- **Hard offset shadow** (≈1.25cqw, orange+ink) on one featured block per frame — never blurred.
|
||||
- **Archivo Black uppercase**, 0.92 line-height, always; sentence-case Archivo Black does not exist.
|
||||
- **Two or three accents per frame**, never all four; collisions are the design.
|
||||
- **One pill chip** (999px) per frame as the sole rounded element.
|
||||
|
||||
### Frame Craft Bar
|
||||
|
||||
Three eyeball tests gate every frame before any structural check:
|
||||
|
||||
- **Squint** — exactly one element dominates, at **3–6× its nearest neighbor** (a chasm, not a ramp): the `display-hero`/`display-xl` claim or the `stat-num` figure, never two rival headlines.
|
||||
- **Silence** — sparse frames (cover, claim, closer) read **45–60% empty**; the **stat grid and comparison ledger are the one dense exception**. Never fill a sparse frame to look complete.
|
||||
- **Restraint** — the scarce gestures fire **once per frame**: at most one hard-offset shadow, two-to-three accents (never all four), the green ground reserved for the single closing plate.
|
||||
- **Reference** — aim at a **Risograph editorial poster / punk-zine spread** (flat ink-bordered blocks, one big claim, vast cream); failure looks like a **rounded, soft-shadowed SaaS feature grid**.
|
||||
|
||||
## The Frame
|
||||
|
||||
- **Primary:** 1920×1080 (16:9). All display sizes authored in **`cqw`** (`px ÷ 1920 × 100 = cqw`).
|
||||
- **Vertical:** 1080×1920 (9:16). **Square:** 1080×1080 (1:1).
|
||||
- **Safe area:** chrome at `3.3cqw` (64px) inset; content at `5cqw` (96px) gutter. No load-bearing element crosses the `3.3cqw` line.
|
||||
|
||||
**The container law (load-bearing).** Every frame ground sets `container-type: size`. ALL
|
||||
frame-relative units are `cqw`/`cqh` (1cqw = 1% of the frame's width), resolved against that
|
||||
ground — **never `vw`.** `vw` measures the page viewport, so a frame inflates whenever it isn't
|
||||
rendered full-screen; `cqw` resolves against the frame at any render size. This is why every size
|
||||
in the display ramp is `cqw`.
|
||||
|
||||
## Colors
|
||||
|
||||
Tokens identical to the source. At frame scale: `{colors.cream}` is the **ground**, `{colors.ink}`
|
||||
is borders + type, and the four accents (`green` / `pink` / `orange` / `yellow`) are **flat fills
|
||||
rationed two-to-three per frame.** `{colors.orange}` is also the hard-shadow color. `{colors.green}`
|
||||
doubles as the single closing-plate ground — its rarity is the impact. Never introduce a fifth
|
||||
accent; never use pure white; never gradient.
|
||||
|
||||
## Typography
|
||||
|
||||
Two ramps. The **reading ramp** (body, mono labels, table heads) holds px+cqw for chrome and copy.
|
||||
The **display/hero ramp** is frame-native and authored in `cqw` — from `display-head` (4.2cqw) up
|
||||
to `display-hero` (15.5cqw) for a wordmark cover.
|
||||
|
||||
- **Legibility floor:** any load-bearing line ≥ **1.4cqw (≈27px@1920)**. Mono chrome at 1.15cqw is colophon only.
|
||||
- **Fit-to-measure:** a headline's size tracks its line length. Cap the headline block at **≤ 78cqw** and never touch the safe margin. ≤3 words → `display-hero`/`display-xl`; 4–6 words → `display-lg`; 7+ words → `display-head`. Short lines go big; long lines step down.
|
||||
- **Uppercase + 0.92 line-height on all Archivo Black**, always. Mono carries 0.06–0.14em tracking. Never letter-space Archivo Black beyond the encoded −0.01em; never set body centered.
|
||||
|
||||
## Depth & Surface
|
||||
|
||||
Zero blur. Two depth devices only:
|
||||
|
||||
- **Hard offset shadow** — a solid duplicate offset ≈1.25cqw in X and Y (`box-shadow: 1.25cqw 1.25cqw 0 {colors.orange}, 1.25cqw 1.25cqw 0 0.2cqw {colors.ink}`). **One featured block per frame** (marker, stamp). Diagram stacks may use a 0.95cqw ink-only offset.
|
||||
- **Color-block contrast** — cream on cream-2, ink on cream, accent on cream. No shadow needed when contrast carries.
|
||||
|
||||
**Ceiling:** no blurred shadow, no gradient, no glow, anywhere.
|
||||
|
||||
## Shapes
|
||||
|
||||
- **0 radius** on every structural element — stat cells, step cards, table cells, markers, panels.
|
||||
- **50%** on decorative circles, stamp inner ring, the meta dot.
|
||||
- **999px** on the topbar pill chip only — the single rounded exception.
|
||||
- **Rotation** only at the fixed brand angles: badge −4deg, stamp −6deg.
|
||||
|
||||
## Components
|
||||
|
||||
- **frame-chrome** — the mono topbar + meta footer. Present on most frames; dropped only on a pure full-bleed wordmark beat if it competes.
|
||||
- **stat-cell / step-card** — ink-bordered flat-fill blocks; step sequences end on green.
|
||||
- **marker-block** — the one hard-shadow featured callout per frame.
|
||||
- **kicker-block** — inverted ink eyebrow chip; **badge-rotated** — −4deg yellow annotation; **pill-badge** — the lone rounded chip.
|
||||
- **stamp** — the −6deg closing seal. **comparison-table** — cream-2 ledger with ink head row.
|
||||
- **decorative-circle** — yellow disc for shape contrast against a green panel.
|
||||
|
||||
## Frame Treatments
|
||||
|
||||
> Recipe per plate: ground · container · composes · focal · chrome · accent · silence · Fixed/Free · density.
|
||||
> Authored at 1920×1080. Lean centered; vary the anchor; one idea per frame.
|
||||
|
||||
### 1 · Wordmark Cover (identity · move: full-frame lockup · centered)
|
||||
|
||||
**Ground** `{colors.cream}`, `frame-pad`. **Container** grid, chrome top/bottom, focal centered.
|
||||
**Composes** frame-chrome, the wordmark. **Focal** the two-line wordmark at `display-hero`
|
||||
(15.5cqw), centered, second line in an accent (`{colors.pink}`/`orange`). **Chrome** mono topbar
|
||||
(section label + pill) and meta footer (descriptor + 01•NN). **Accent** the second-line color only;
|
||||
optionally one corner decorative-circle bleeding off an edge. **Silence** ~55% empty cream.
|
||||
**Fixed** Archivo Black uppercase 0.84 lh, one accent on the wordmark, square corners. **Free** which
|
||||
accent, line break, whether a circle bleeds a corner. **Density** sparse.
|
||||
|
||||
### 2 · Big Claim (oversized statement · move: scale · left)
|
||||
|
||||
**Ground** one full-bleed accent (`{colors.pink}`/`green`/`orange`), `content-gutter`. **Container**
|
||||
flex, claim left-anchored and vertically centered. **Composes** kicker-block, the claim. **Focal**
|
||||
a 2–3 line claim at `display-xl`/`display-lg`, ink on the accent (ink-on-fire — never white).
|
||||
**Chrome** small ink kicker-block above the claim; mono meta footer. **Accent** the ground IS the
|
||||
accent; no second accent competes. **Silence** ~45% of the colored field empty. **Fixed** ink type
|
||||
on accent, fit-to-measure sizing, no shadow on type. **Free** the claim, which accent ground, which
|
||||
word breaks. **Density** sparse — one idea.
|
||||
|
||||
### 3 · Stat Grid (catalog · move: density — the one dense frame · centered)
|
||||
|
||||
**Ground** `{colors.cream}`, `content-gutter`. **Container** grid: a centered `display-head` over a
|
||||
3-up row of stat-cells. **Composes** frame-chrome, 3× stat-cell. **Focal** the row of three
|
||||
ink-bordered cells (green / pink / orange), each a `stat-num` + mono label. **Chrome** mono topbar +
|
||||
meta. **Accent** the three cell fills (the named density exception — three accents allowed here).
|
||||
**Silence** ~25% — tight by design. **Fixed** 0.4cqw borders, square corners, no per-cell shadow,
|
||||
mono labels. **Free** the three figures+labels, head copy, which three accents. **Density** dense-exception.
|
||||
|
||||
### 4 · Closing Plate (closer · move: ground-swap · centered)
|
||||
|
||||
**Ground** `{colors.green}` (the single green frame of the run), `frame-pad`. **Focal** a 2-line
|
||||
sign-off at `display-lg` in `{colors.cream}`, centered. **Composes** frame-chrome (cream variant),
|
||||
stamp. **Chrome** cream mono topbar + meta. **Accent** one `{colors.pink}` stamp rotated −6deg in
|
||||
a corner, cream ring + stamp-num. **Silence** ~55% empty green. **Fixed** green ground reserved to
|
||||
this beat, cream-on-green type, one stamp. **Free** sign-off copy, stamp text, stamp corner. **Density** sparse.
|
||||
|
||||
### 5 · Featured Marker (callout · move: hard-shadow focal · left/asymmetric)
|
||||
|
||||
**Ground** `{colors.cream}`. **Composes** frame-chrome, marker-block, optional body-md support line.
|
||||
**Focal** the pink marker-block with the signature orange+ink hard offset shadow, set asymmetrically.
|
||||
**Accent** pink block + orange shadow (two accents). **Silence** ~50%. **Fixed** exactly one hard
|
||||
shadow on the frame, 0.4cqw borders. **Free** marker copy, block position, optional support line.
|
||||
**Density** sparse.
|
||||
|
||||
### 6 · Comparison Ledger (data · move: matrix · left)
|
||||
|
||||
**Ground** `{colors.cream}`, `content-gutter`. **Composes** frame-chrome, comparison-table.
|
||||
**Focal** the cream-2 table with ink head row; one column fill (pink or green) marks the winner.
|
||||
**Accent** the single column fill. **Silence** tight — the second density exception. **Fixed**
|
||||
cream-2 fill, 0.3cqw internal rules, ink head row with cream Archivo labels. **Free** rows, which
|
||||
column fills, copy. **Density** dense-exception.
|
||||
|
||||
## Composition Rules
|
||||
|
||||
### Do
|
||||
|
||||
- Compose around **one idea per frame**, focal element **3–5× its neighbors** (squint test).
|
||||
- **Lean centered** — cover, claim, stat grid, and closer all center their focal element; reserve left/asymmetric for the marker and ledger.
|
||||
- Keep sparse frames **45–60% empty**; only the stat grid and ledger run dense.
|
||||
- Use **two or three accents per frame**; reserve `{colors.green}` ground for the closing plate.
|
||||
- Spend the hard offset shadow on **one featured block per frame**.
|
||||
- Size headlines **fit-to-measure**; render Archivo Black uppercase at 0.92 lh.
|
||||
|
||||
### Don't
|
||||
|
||||
- Don't round corners (except the pill chip); don't gradient, blur, or glow.
|
||||
- Don't set Archivo Black in sentence case or letter-space it beyond −0.01em.
|
||||
- Don't use all four accents on one frame, a fifth accent, or pure white.
|
||||
- Don't center body copy or set labels in anything but JetBrains Mono.
|
||||
- Don't blow a headline edge-to-edge — step the ramp down for long lines.
|
||||
- Don't put two hard shadows on one frame.
|
||||
|
||||
## Aspect-Ratio Behavior
|
||||
|
||||
| Treatment | 16:9 | 9:16 | 1:1 |
|
||||
| ----------------- | ------------------------------- | ---------------------------- | ------------------------ |
|
||||
| Wordmark Cover | two lines centered | stacked taller, circle top | centered, tighter |
|
||||
| Big Claim | claim left, full accent | claim top, accent full | claim centered |
|
||||
| Stat Grid | head over 3-up row | head top, 3 stacked | head top, 2×2 (4th cell) |
|
||||
| Closing Plate | sign-off centered, stamp corner | stacked, stamp below | centered, stamp corner |
|
||||
| Featured Marker | marker asymmetric | marker centered, shadow down | marker centered |
|
||||
| Comparison Ledger | full-width table | table scrolls to fewer cols | 2-col table |
|
||||
|
||||
Safe area holds the `3.3cqw` chrome inset on the short edge for every ratio; re-step display per
|
||||
ratio so no load-bearing line drops below the 1.4cqw floor.
|
||||
|
||||
## Approved Entities
|
||||
|
||||
No real customers, logos, or vendors are defined in the source — render any such mark as a
|
||||
placeholder. Products/sections are content-agnostic; the system supplies geometry, not brands.
|
||||
|
||||
## Numerals & Claims (hard rule)
|
||||
|
||||
Never invent figures, percentages, counts, or dates at frame scale. Render data slots as
|
||||
`— figure —`, `{metric}`, `N×`. Real numerals appear only when the script supplies them — the stat
|
||||
grid and ledger especially carry placeholders, not fabricated values.
|
||||
|
||||
## Pre-Render Self-Audit
|
||||
|
||||
- **Squint** — one element dominates at 3–5× its neighbor, else rescale.
|
||||
- **Silence** — sparse frames 45–60% empty; only stat grid / ledger run dense.
|
||||
- **Accents** — two or three per frame, never all four; green ground only on the closer.
|
||||
- **Depth** — 0 blur; at most one hard offset shadow; color-block contrast otherwise.
|
||||
- **Geometry** — square corners except the pill; rotation only at −4/−6deg.
|
||||
- **Type** — Archivo Black uppercase 0.92 lh, fit-to-measure, ≥1.4cqw on load-bearing lines.
|
||||
- **Anchor** — centered default; left/asymmetric only on marker + ledger; no 3 consecutive frames share an anchor.
|
||||
- **Fabrication** — every numeral traces to the script, else placeholder.
|
||||
|
||||
## Known Gaps
|
||||
|
||||
- **Motion intentionally out of scope.** frame.md specifies composition only; timing and transitions are a later stage. The closing green is described as a _plate_, not a transition.
|
||||
- **Archivo Black requires Google Fonts**; fallback is `sans-serif`. CJK pairing (Noto Serif SC 900 / NSC 400) carries over from the source's CJK section.
|
||||
- **9:16 / 1:1 are guidance**, not pixel-locked; verify the legibility floor per ratio.
|
||||
- Decorative geometry (circle, stamp, stacked blocks) is CSS-only; no external imagery is required.
|
||||
@@ -0,0 +1,227 @@
|
||||
<!--
|
||||
Creative Mode — 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 creative-mode
|
||||
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 warm-cream card on a thick 4px ink border with a HARD offset shadow (a solid
|
||||
same-direction ink+orange duplicate, no blur — creative-mode's only depth device),
|
||||
square corners. Archivo Black, UPPERCASE, 0.92 line-height, ink glyphs. Upcoming words
|
||||
sit in faint ink; the current word becomes a solid yellow inked block (the brand's
|
||||
signature hard-offset-shadow accent block, no blur) with ink reading on top; spoken
|
||||
words settle to solid ink, the accent 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 cream card — thick 4px ink border + the signature HARD offset shadow
|
||||
(a solid same-direction orange+ink duplicate, no blur), square corners */
|
||||
.caption-pill {
|
||||
max-width: 80%;
|
||||
padding: 18px 40px 22px;
|
||||
background: var(--cap-canvas, #efe9d9);
|
||||
border: 4px solid var(--cap-ink, #0f0f0f);
|
||||
border-radius: 0;
|
||||
box-shadow:
|
||||
14px 14px 0 var(--cap-accent-2, #e85a1f),
|
||||
14px 14px 0 4px var(--cap-ink, #0f0f0f);
|
||||
}
|
||||
.caption-line {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 0.16em 0.3em;
|
||||
font-family: var(--font-display, "Archivo Black"), sans-serif;
|
||||
font-weight: 400;
|
||||
text-transform: uppercase;
|
||||
font-size: clamp(34px, 3.8vw, 52px);
|
||||
line-height: 0.92;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
/* upcoming — faint ink, no accent block */
|
||||
.caption-word {
|
||||
display: inline-block;
|
||||
padding: 0.02em 0.1em;
|
||||
color: color-mix(in srgb, var(--cap-ink, #0f0f0f) 38%, var(--cap-canvas, #efe9d9));
|
||||
}
|
||||
/* current word — THE hard yellow inked block (box-shadow spread = a hard square
|
||||
block w/o layout shift; no blur), ink reading on top */
|
||||
.caption-word.is-active {
|
||||
color: var(--cap-ink, #0f0f0f);
|
||||
background: var(--cap-accent, #f5c518);
|
||||
box-shadow: 0 0 0 0.08em var(--cap-accent, #f5c518);
|
||||
}
|
||||
/* already spoken — solid ink, accent block cleared */
|
||||
.caption-word.is-spoken {
|
||||
color: var(--cap-ink, #0f0f0f);
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* optional per-word accents (dormant until a word-classer tags them) */
|
||||
/* brand / key term — an inverted ink chip (the kicker-block voice) */
|
||||
.caption-word.cap-brand {
|
||||
color: var(--cap-canvas, #efe9d9);
|
||||
background: var(--cap-ink, #0f0f0f);
|
||||
box-shadow: 0 0 0 0.08em var(--cap-ink, #0f0f0f);
|
||||
}
|
||||
/* numerals / data — the mono voice, slightly recessed */
|
||||
.caption-word.cap-num {
|
||||
font-family: var(--font-body, "Space Grotesk"), ui-monospace, monospace;
|
||||
color: color-mix(in srgb, var(--cap-ink, #0f0f0f) 72%, var(--cap-canvas, #efe9d9));
|
||||
}
|
||||
/* emphasis — a thick orange underline (the counter accent) */
|
||||
.caption-word.cap-cta {
|
||||
text-decoration: underline;
|
||||
text-decoration-thickness: 3px;
|
||||
text-decoration-color: var(--cap-accent-2, #e85a1f);
|
||||
text-underline-offset: 0.12em;
|
||||
}
|
||||
|
||||
@media (max-aspect-ratio: 9/16) {
|
||||
.caption-pill {
|
||||
max-width: 92%;
|
||||
}
|
||||
.caption-line {
|
||||
font-size: clamp(30px, 5vw, 44px);
|
||||
}
|
||||
}
|
||||
</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.16, ease: "back.out(2.2)" }, 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
@@ -0,0 +1,274 @@
|
||||
---
|
||||
version: alpha
|
||||
name: Daisy Days — Frame (video / frame layer)
|
||||
description: >
|
||||
Video-first companion to Daisy Days' design.md. The unit is the frame (1920×1080). Atoms are
|
||||
identical and sacred — the sunny-garden pastel palette (cream + turquoise/pink/butter/mint/
|
||||
lavender/peach/sky + coral accent), charcoal 3px outlines, hard offset shadows (6/4px, no blur),
|
||||
the Fredoka + Quicksand pairing, generous radii, headline text-shadow on color, dot bullets, and
|
||||
the hand-drawn SVG ornament layer. Composition + frame scale rewritten. 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:
|
||||
cream: "#F5F0E6"
|
||||
turquoise: "#7ECDC0"
|
||||
soft-pink: "#F7C8D4"
|
||||
butter: "#FDE68A"
|
||||
mint: "#A8E6CF"
|
||||
lavender: "#D4A5E8"
|
||||
peach: "#FFCBA4"
|
||||
sky: "#A8D8F0"
|
||||
coral: "#F8635F"
|
||||
text-dark: "#2D2D2D"
|
||||
text-muted: "#6B6B6B"
|
||||
white: "#FFFFFF"
|
||||
|
||||
borders: { primary: "3px solid text-dark", thin: "2px solid text-dark" }
|
||||
shadows: { default: "6px 6px 0 text-dark", small: "4px 4px 0 text-dark", text-headline: "3px 3px 0 text-dark", text-headline-soft: "3px 3px 0 rgba(0,0,0,0.2)" }
|
||||
|
||||
typography:
|
||||
# — reading ramp (Quicksand) —
|
||||
body: { fontFamily: "Quicksand", cqw: 0.95, weight: 500, lineHeight: 1.6 }
|
||||
body-strong:{ fontFamily: "Quicksand", cqw: 0.95, weight: 600, lineHeight: 1.5 }
|
||||
meta: { fontFamily: "Quicksand", cqw: 0.78, weight: 600, lineHeight: 1.45 }
|
||||
# — display ramp (Fredoka One / Fredoka 600 — single weight, never italic) —
|
||||
badge: { fontFamily: "Fredoka One", cqw: 0.9, tracking: "0.02em" }
|
||||
label-display:{ fontFamily: "Fredoka One", cqw: 1.3, lineHeight: 1.3, tracking: "0.02em" }
|
||||
subtitle:{ fontFamily: "Fredoka One", cqw: 1.8, lineHeight: 1.2, tracking: "0.02em" }
|
||||
quote: { fontFamily: "Fredoka One", cqw: 2.6, lineHeight: 1.35 }
|
||||
title: { fontFamily: "Fredoka One", cqw: 3.0, lineHeight: 1.15, tracking: "0.02em" }
|
||||
headline:{ fontFamily: "Fredoka One", cqw: 4.5, lineHeight: 1.1, tracking: "0.02em" }
|
||||
display: { fontFamily: "Fredoka One", cqw: 6.5, lineHeight: 1.1, tracking: "0.02em" }
|
||||
|
||||
spacing:
|
||||
pad-slide: "3cqw"
|
||||
radius: "20px"
|
||||
radius-lg: "28px"
|
||||
radius-pill: "50px"
|
||||
radius-round: "50%"
|
||||
|
||||
components:
|
||||
card:
|
||||
backgroundColor: "{colors.white}"
|
||||
border: "3px solid {colors.text-dark}"
|
||||
rounded: "{spacing.radius} (28px featured)"
|
||||
shadow: "6px 6px 0 {colors.text-dark}"
|
||||
description: "The universal container; white-on-pastel is standard."
|
||||
framed-header:
|
||||
backgroundColor: "pastel cap + {colors.white} body"
|
||||
border: "3px solid {colors.text-dark} (one continuous)"
|
||||
rounded: "{spacing.radius-lg}"
|
||||
shadow: "6px 6px 0 {colors.text-dark}"
|
||||
description: "Pastel header strip flush above a white body — one unit, one shadow."
|
||||
badge-pill:
|
||||
backgroundColor: "{colors.butter}"
|
||||
border: "3px solid {colors.text-dark}"
|
||||
rounded: "{spacing.radius-pill}"
|
||||
typography: "{typography.badge}"
|
||||
shadow: "4px 4px 0 {colors.text-dark}"
|
||||
description: "Section tag. white-space:nowrap."
|
||||
circle-marker:
|
||||
backgroundColor: "any pastel"
|
||||
textColor: "{colors.white} (dark on butter)"
|
||||
border: "3px solid {colors.text-dark}"
|
||||
rounded: "50%"
|
||||
size: "bullet 20 / icon 44 / dot 48 / step 90"
|
||||
typography: "Fredoka numeral"
|
||||
description: "Steps carry a small shadow."
|
||||
bullet-dot:
|
||||
backgroundColor: "{colors.butter}"
|
||||
border: "2px solid {colors.text-dark}"
|
||||
rounded: "50%"
|
||||
size: "20px (::before, 4px from line top)"
|
||||
description: "Lists never use glyph bullets."
|
||||
ornament:
|
||||
type: "hand-drawn SVG (daisy, star, sun, cloud, rainbow)"
|
||||
stroke: "2.1px {colors.text-dark}"
|
||||
placement: "z-index:1 behind content (z-index:2), cropping past the frame edge"
|
||||
description: "3–7 per frame, clustered at corners/edges."
|
||||
quote-mark:
|
||||
typography: "oversized Fredoka quote glyph"
|
||||
color: "{colors.soft-pink} (charcoal stroke)"
|
||||
description: "Above a quote body."
|
||||
text-headline-shadow:
|
||||
shadow: "3px 3px 0 {colors.text-dark} (soft 20% variant on pink/mint)"
|
||||
appliesTo: "Fredoka headlines on saturated surfaces (cream headlines sit flat)"
|
||||
description: "Makes the headline read 'outlined' like the shapes."
|
||||
---
|
||||
|
||||
# Daisy Days — Frame (video / frame layer)
|
||||
|
||||
## Overview
|
||||
|
||||
Daisy Days at frame scale is a **cheerful, childlike system** — picture-book illustration meets
|
||||
sticker-sheet kawaii. Every shape carries a **3px charcoal outline**, every elevated element a
|
||||
**solid hard offset shadow** (no blur), every surface a sunny-garden pastel. The voice is one
|
||||
pairing: **Fredoka One** (chunky rounded, single weight) for every headline, **Quicksand** for
|
||||
every body and meta line. The signature is the **hand-drawn SVG ornament layer** — daisies, stars,
|
||||
suns, clouds, rainbows clustering at corners and cropping past the edge.
|
||||
|
||||
The palette is **multi-pastel with one warm pop**: cream canvas, seven pastel surfaces, and
|
||||
`{colors.coral}` reserved for small high-attention markers only. Headlines on a saturated surface
|
||||
get a 3px charcoal text-shadow (so they read "outlined" like the shapes) and switch to white;
|
||||
headlines on cream sit flat in charcoal. Depth is 2D and graphic — thick outline + hard offset =
|
||||
sticker-on-paper.
|
||||
|
||||
**Key characteristics at frame scale:**
|
||||
|
||||
- **Cream default + rotating pastel surfaces**; `{colors.coral}` is a marker accent, never a surface.
|
||||
- **Fredoka One** headlines + **Quicksand 500/600** body — strict by role, never crossed.
|
||||
- **3px charcoal outline + hard offset shadow** (6/4px, zero blur) on every elevated shape.
|
||||
- **Generous radii** — 20px cards, 28px featured, pill badges, full-circle markers; no square corners.
|
||||
- **Headline text-shadow on saturated surfaces** (white text); flat charcoal on cream.
|
||||
- **Dot bullets** (outlined butter discs, never glyphs) + a **3–7 ornament wreath** per frame.
|
||||
|
||||
## The Frame
|
||||
|
||||
### Frame Craft Bar
|
||||
|
||||
Three eyeball tests gate every frame before any structural check:
|
||||
|
||||
- **Squint** — one Fredoka headline or content card dominates at 3–6× its neighbor.
|
||||
- **Silence** — **one content container per frame** surrounded by an ornament wreath; the **info-card grid is the one dense exception**. Empty corners read as broken.
|
||||
- **Restraint** — cream or **one** pastel surface; coral is a small-marker accent (never a surface); charcoal borders + hard offset shadows only; no ninth color.
|
||||
- **Reference** — aim at a **children's picture-book / sticker-sheet kawaii zine**; failure looks like a **flat, square-cornered, blurred-shadow corporate slide**.
|
||||
|
||||
- **Primary:** 1920×1080 (16:9). Display authored in **`cqw`** (`px ÷ 1920 × 100 = cqw`).
|
||||
- **Vertical:** 1080×1920 (9:16). **Square:** 1080×1080 (1:1).
|
||||
- **Safe area:** `pad-slide` ~3cqw; ornaments deliberately bleed past the edge.
|
||||
|
||||
**The container law (load-bearing).** Every frame ground sets `container-type: size`; ALL
|
||||
frame-relative units are `cqw`/`cqh` against it — never `vw`. Borders stay 3px/2px; radii stay
|
||||
20/28/50px; shadow offsets scale in `cqw` so the sticker offset holds proportionally.
|
||||
|
||||
## Colors
|
||||
|
||||
Tokens identical to the source. Default ground `{colors.cream}`; rotate saturated pastels
|
||||
(turquoise / soft-pink / butter / mint / lavender / peach / sky) for tonal mood. **Cards are white**
|
||||
on any surface. **Borders + shadows are always `{colors.text-dark}` charcoal** — never colored,
|
||||
never blurred, never `rgba` (save the soft text-shadow variant). `{colors.coral}` is the lone
|
||||
high-saturation accent — small markers (step circles, dots, headers) **only**, never a surface.
|
||||
Body is charcoal/muted; pastels carry **no semantic meaning**. No ninth color.
|
||||
|
||||
## Typography
|
||||
|
||||
Two ramps. The **reading ramp** (Quicksand 500 body 0.95cqw, 600 emphasis, meta) carries copy; the
|
||||
**display ramp** (Fredoka One `label-display` 1.3cqw → `display` 6.5cqw, single weight) carries
|
||||
every headline, title, quote, and marker numeral.
|
||||
|
||||
- **Legibility floor:** any load-bearing line ≥ **1.4cqw**; meta is chrome only.
|
||||
- **Fit-to-measure:** size the headline to its length. Cap the block at **≤ 78cqw**; ≤3 words → `display`; 4–6 → `headline`; 7+ → `title`.
|
||||
- **Fredoka One for all display, Quicksand for all body — never crossed.** Fredoka is single-weight (no italic, no underline, no alt weights); Quicksand stays 500/600/700. Fredoka tracking 0.02em; Quicksand body never uppercase.
|
||||
|
||||
## Depth & Surface
|
||||
|
||||
2D graphic depth — hard offset shadow, solid charcoal, zero blur, bottom-right:
|
||||
|
||||
- **`shadows.default` 6px** cards, frames, badges, chart containers.
|
||||
- **`shadows.small` 4px** small cards, step circles, avatars.
|
||||
- **Text-headline shadow** — 3px charcoal on Fredoka headlines over saturated surfaces (20% soft variant on pink/mint); cream headlines flat.
|
||||
- **Outline + offset** together are the sticker-on-paper signature.
|
||||
|
||||
**Ceiling:** no blurred shadow, no `rgba` (except the soft text-shadow), no gradient, no glow; an element either casts a hard charcoal offset or none.
|
||||
|
||||
## Shapes
|
||||
|
||||
- **20px** cards, **28px** featured, **50px** pill (badges, counter), **50%** all circles, **4px** legend swatch. Zero square corners — every region is rounded.
|
||||
|
||||
## Components
|
||||
|
||||
- **card / framed-header** — the white-fill bordered containers (one shadow). **badge-pill** — butter section tag.
|
||||
- **circle-marker** family (bullet 20 / icon 44 / dot 48 / step 90) + **bullet-dot** — outlined pastel discs, Fredoka numerals.
|
||||
- **ornament** — the hand-drawn SVG sticker layer (3–7 per frame). **quote-mark** — soft-pink Fredoka anchor. **text-headline-shadow** — the on-color headline treatment.
|
||||
|
||||
## Frame Treatments
|
||||
|
||||
> Recipe: ground · container · composes · focal · chrome · accent · silence · Fixed/Free · density.
|
||||
> One content container per frame + a 3–7 ornament wreath; empty corners read as broken.
|
||||
|
||||
### 1 · Cover (identity · move: ornament wreath · saturated · centered)
|
||||
|
||||
**Ground** a saturated pastel (e.g. `{colors.turquoise}`). **Composes** badge-pill, display, body sub, 3–7 ornaments, counter. **Focal** a 1–2 line Fredoka `display` in **white with the 3px charcoal text-shadow**, centered, under a butter badge-pill. **Chrome** counter pill. **Accent** the ornament wreath (daisies + stars at corners, cropping past edges). **Silence** content centered; ornaments fill the edges. **Fixed** text-shadow on color, 3px outlines, charcoal shadows. **Free** surface color, ornament mix/positions, copy. **Density** full-but-not-crowded.
|
||||
|
||||
### 2 · Info Cards (catalog · move: 3-up white cards · cream · the dense frame)
|
||||
|
||||
**Ground** `{colors.cream}`, `pad-slide`. **Composes** headline (flat charcoal), 3× card (circle-icon + Fredoka title + Quicksand body), a couple ornaments. **Focal** three white 3px-bordered cards with 6px shadows. **Chrome** flat headline. **Accent** the pastel circle-icons (rotate turquoise/coral/lavender). **Silence** tight — the density exception (fewer ornaments here). **Fixed** white cards, 3px + 6px, 20px radius. **Free** card content, icon hues. **Density** dense-exception.
|
||||
|
||||
### 3 · Process Steps (sequence · move: rotating circle markers · peach · centered)
|
||||
|
||||
**Ground** `{colors.peach}` (or another pastel). **Composes** headline (white + text-shadow), 3–4 step circles + `→` arrows, ornaments. **Focal** a row of 90px outlined step circles, fills **rotating coral → mint → sky → lavender**, Fredoka white numerals, linked by Fredoka arrows. **Chrome** white headline with text-shadow. **Accent** the rotating circle fills. **Silence** moderate. **Fixed** 3px circles + small shadow, rotating fills, arrow glyphs. **Free** step count, labels. **Density** standard.
|
||||
|
||||
### 4 · Quote (quote · move: quote-mark anchor · soft-pink · centered)
|
||||
|
||||
**Ground** `{colors.soft-pink}`. **Composes** a white quote card (28px radius, 6px shadow), quote-mark, Fredoka quote, Quicksand attribution, ornaments. **Focal** a Fredoka `quote` in charcoal inside the white card, under an oversized soft-pink quote-mark. **Chrome** Quicksand 700 attribution. **Accent** the quote-mark + a star or two. **Silence** card centered, ornaments at corners. **Fixed** quote-mark anchor, white card. **Free** quote, attribution. **Density** moderate.
|
||||
|
||||
### 5 · Framed Section (feature · move: cap+body card · cream)
|
||||
|
||||
**Ground** `{colors.cream}`. **Composes** framed-header (pastel cap + white body), bullet-dot list, ornaments. **Focal** a framed-header — a pastel cap strip (Fredoka title, optional text-shadow) above a white body with a butter-dot bullet list. **Accent** the cap color + bullet dots. **Silence** moderate. **Fixed** one continuous 3px border + one shadow, butter dot bullets. **Free** cap color, list. **Density** standard.
|
||||
|
||||
### 6 · Closing (closer · move: ornament wreath · saturated · centered)
|
||||
|
||||
**Ground** a saturated pastel (e.g. `{colors.lavender}`). **Composes** badge-pill, display (white + text-shadow), 3–7 ornaments. **Focal** a Fredoka `display` sign-off in white with the charcoal text-shadow, centered. **Accent** the ornament wreath. **Silence** content centered. **Fixed** text-shadow on color, ornaments fill corners. **Free** sign-off, surface, ornaments. **Density** full.
|
||||
|
||||
## Composition Rules
|
||||
|
||||
### Do
|
||||
|
||||
- Pair **Fredoka One headlines + Quicksand body** strictly by role.
|
||||
- Outline every shape **3px charcoal + a hard offset shadow** (6/4px, no blur); white card fills on any surface.
|
||||
- Give Fredoka headlines on **saturated surfaces a 3px charcoal text-shadow + white text**; flat charcoal on cream.
|
||||
- Use **outlined butter-disc bullets** (never glyphs); cluster **3–7 hand-drawn ornaments** per frame (corners, cropping past edges).
|
||||
- Keep **one content container per frame**; rotate marker colors (coral → mint → sky → lavender → butter); reserve coral for small markers.
|
||||
- Center most frames; lean cream for content-dense frames, saturated for cover/closer/quote.
|
||||
|
||||
### Don't
|
||||
|
||||
- No square corners; no blurred or `rgba` shadows (save the soft text-shadow).
|
||||
- No colored borders (charcoal only); no coral surface; no ninth color.
|
||||
- No third font; no Quicksand headline or Fredoka body; no italic/underline Fredoka; no uppercase Quicksand body.
|
||||
- No glyph bullets; no empty corners (ornaments fill them); no two competing content panels.
|
||||
- Don't blow a headline edge-to-edge — fit to measure.
|
||||
|
||||
## Aspect-Ratio Behavior
|
||||
|
||||
| Treatment | 16:9 | 9:16 | 1:1 |
|
||||
| -------------- | -------------------------- | -------------------------------- | ---------- |
|
||||
| Cover | centered, corner ornaments | centered, taller, more ornaments | centered |
|
||||
| Info Cards | 3 across | stacked | 2+1 |
|
||||
| Process Steps | horizontal + arrows | vertical, arrows rotate down | 2×2 |
|
||||
| Quote | centered card | centered, taller | centered |
|
||||
| Framed Section | cap + body | cap + body taller | cap + body |
|
||||
| Closing | centered, wreath | centered, wreath | centered |
|
||||
|
||||
`pad-slide` holds on the short edge; re-step display above the 1.4cqw floor. On tighter ratios keep
|
||||
the ornament count toward the upper end (5–7) so corners never read empty.
|
||||
|
||||
## Approved Real Entities
|
||||
|
||||
No real customers, logos, or vendors are defined in the source — render any such mark as a
|
||||
placeholder. Ornaments and pastels are content-agnostic; counters/badges carry per-deck text.
|
||||
|
||||
## Numerals & Claims (hard rule)
|
||||
|
||||
Never invent figures, dates, or counts at frame scale. Render slots as `— figure —`, `{metric}`,
|
||||
`N`. Step numbers and any chart values carry placeholders until the script supplies them; the slide
|
||||
counter is decorative chrome.
|
||||
|
||||
## Pre-Render Self-Audit
|
||||
|
||||
- **Squint** — one Fredoka headline or content card dominates per frame.
|
||||
- **Silence** — one container per frame surrounded by an ornament wreath; only the info-card grid runs dense.
|
||||
- **Color** — cream or one pastel surface; coral markers only; charcoal borders/shadows; no ninth hue.
|
||||
- **Type** — Fredoka headlines (text-shadow + white on saturated, flat charcoal on cream), Quicksand body; ≥1.4cqw floor.
|
||||
- **Depth** — 3px outline + hard offset (no blur, no rgba save soft text-shadow); rounded corners only.
|
||||
- **Ornaments** — 3–7 per frame, cropping past edges; no empty corners. **Bullets** — outlined discs, never glyphs.
|
||||
- **Fabrication** — every numeral traces to the script, else placeholder.
|
||||
|
||||
## Known Gaps
|
||||
|
||||
- **Motion intentionally out of scope.** frame.md specifies composition only; the source uses scroll-snap nav, no transition spec.
|
||||
- **Fonts:** the source names _Fredoka One_; Google now serves the **Fredoka** variable family — request `Fredoka:wght@500;600;700` and set display weight 600 (visually equal to Fredoka One), with `Fredoka One` kept first in the stack for environments that still serve it. Quicksand loads normally. CJK: ZCOOL XiaoWei (display) / Yozai (body).
|
||||
- **9:16 / 1:1 are guidance**; keep ornament count high so corners stay filled per ratio.
|
||||
- Ornaments (daisy/star/sun/cloud/rainbow), markers, and framed headers are CSS/SVG-only; recoloring SVG ornaments requires editing their stroke values.
|
||||
- **Contrast:** keep `{colors.text-muted}` off pastel surfaces (cream/white cards only); small text on saturated grounds should be charcoal or white.
|
||||
@@ -0,0 +1,230 @@
|
||||
<!--
|
||||
Daisy Days — 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 daisy-days 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 cheerful picture-book card — a cream surface inside a 3px charcoal outline
|
||||
with a hard 6px charcoal offset shadow (no blur) and a generous 24px radius, the
|
||||
sticker-on-paper signature. Fredoka One glyphs, sentence case, charcoal ink. Upcoming
|
||||
words sit in muted charcoal; the current word pops into a rounded sunny-pastel candy
|
||||
block (coral fill, 3px charcoal outline, hard 3px offset shadow, 12px radius) with a
|
||||
bouncy scale pop; spoken words settle to solid charcoal, the candy 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 picture-book card — cream surface, 3px charcoal outline, generous 24px radius,
|
||||
a hard 6px charcoal offset shadow (no blur): the sticker-on-paper signature */
|
||||
.caption-pill {
|
||||
max-width: 80%;
|
||||
padding: 18px 40px 22px;
|
||||
background: var(--cap-canvas, #f5f0e6);
|
||||
border: 3px solid var(--cap-ink, #2d2d2d);
|
||||
border-radius: 24px;
|
||||
box-shadow: 6px 6px 0 var(--cap-ink, #2d2d2d);
|
||||
}
|
||||
.caption-line {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 0.18em 0.32em;
|
||||
font-family: var(--font-display, "Fredoka One"), "Baloo 2", sans-serif;
|
||||
font-weight: 600;
|
||||
font-size: clamp(38px, 4.2vw, 56px);
|
||||
line-height: 1.22;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
/* upcoming — muted charcoal, no candy block */
|
||||
.caption-word {
|
||||
display: inline-block;
|
||||
padding: 0.02em 0.16em;
|
||||
border-radius: 12px;
|
||||
color: color-mix(in srgb, var(--cap-ink, #2d2d2d) 52%, var(--cap-canvas, #f5f0e6));
|
||||
}
|
||||
/* current word — THE sunny-pastel candy block: a rounded coral chip with a 3px
|
||||
charcoal outline + a hard 3px charcoal offset shadow (no blur), charcoal on top */
|
||||
.caption-word.is-active {
|
||||
color: var(--cap-ink, #2d2d2d);
|
||||
background: var(--cap-accent, #f8635f);
|
||||
border-radius: 12px;
|
||||
box-shadow:
|
||||
0 0 0 3px var(--cap-ink, #2d2d2d),
|
||||
3px 5px 0 0 var(--cap-ink, #2d2d2d);
|
||||
}
|
||||
/* already spoken — solid charcoal, candy block cleared */
|
||||
.caption-word.is-spoken {
|
||||
color: var(--cap-ink, #2d2d2d);
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* optional per-word accents (dormant until a word-classer tags them) */
|
||||
/* brand / key term — a butter candy chip, the cheerful second pastel */
|
||||
.caption-word.cap-brand {
|
||||
color: var(--cap-ink, #2d2d2d);
|
||||
background: var(--cap-accent-2, #fde68a);
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 0 0 3px var(--cap-ink, #2d2d2d);
|
||||
}
|
||||
/* numerals / data — Quicksand, slightly recessed */
|
||||
.caption-word.cap-num {
|
||||
font-family: var(--font-body, "Quicksand"), system-ui, sans-serif;
|
||||
font-weight: 700;
|
||||
color: color-mix(in srgb, var(--cap-ink, #2d2d2d) 78%, var(--cap-canvas, #f5f0e6));
|
||||
}
|
||||
/* emphasis — a butter underline rule (the second sunny pastel) */
|
||||
.caption-word.cap-cta {
|
||||
text-decoration: underline;
|
||||
text-decoration-thickness: 3px;
|
||||
text-decoration-color: var(--cap-accent-2, #fde68a);
|
||||
text-underline-offset: 0.12em;
|
||||
}
|
||||
|
||||
@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.82 }, { scale: 1, duration: 0.2, ease: "back.out(2.6)" }, 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
@@ -0,0 +1,265 @@
|
||||
---
|
||||
version: alpha
|
||||
name: Editorial Forest — Frame (video / frame layer)
|
||||
description: >
|
||||
Video-first companion to Editorial Forest's design.md. The unit is the frame (1920×1080). Atoms
|
||||
are identical and sacred — the green/pink/cream editorial triad, Source Serif 4 at weight 500
|
||||
(optical-size axis) for all display + JetBrains Mono 500 uppercase chrome, flat paper depth (no
|
||||
shadows), 2px hairline rules, 6/8px card radii, and the monogram circle stamp. Composition + frame
|
||||
scale rewritten. 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:
|
||||
green: "#2e4a2a"
|
||||
green-deep: "#243a21"
|
||||
green-lite: "#3a5a36"
|
||||
pink: "#e89cb1"
|
||||
pink-deep: "#d27e96"
|
||||
cream: "#efe7d4"
|
||||
cream-2: "#e6dcc4"
|
||||
ink: "#1a1a17"
|
||||
|
||||
typography:
|
||||
# — reading + chrome ramp —
|
||||
body: { fontFamily: "Source Serif 4", cqw: 1.56, weight: 400, lineHeight: 1.38 }
|
||||
body-card:{ fontFamily: "Source Serif 4", cqw: 1.35, weight: 400, lineHeight: 1.34 }
|
||||
label: { fontFamily: "JetBrains Mono", px: 26, cqw: 1.35, weight: 500, tracking: "0.18em", upper: true }
|
||||
caption-mono:{ fontFamily: "JetBrains Mono", px: 24, cqw: 1.25, weight: 500, tracking: "0.14em", upper: true }
|
||||
# — display ramp (Source Serif 4 weight 500, opsz, negative tracking) —
|
||||
title-card-sm:{ fontFamily: "Source Serif 4", cqw: 2.9, weight: 500, lineHeight: 0.98, tracking: "-0.01em" }
|
||||
title-card:{ fontFamily: "Source Serif 4", cqw: 3.5, weight: 500, lineHeight: 0.96, tracking: "-0.01em" }
|
||||
headline:{ fontFamily: "Source Serif 4", cqw: 4.4, weight: 500, lineHeight: 1.0, tracking: "-0.02em" }
|
||||
headline-xl:{ fontFamily: "Source Serif 4", cqw: 5.0, weight: 500, lineHeight: 0.96, tracking: "-0.02em" }
|
||||
display:{ fontFamily: "Source Serif 4", cqw: 7.3, weight: 500, lineHeight: 1.02, tracking: "-0.02em" }
|
||||
display-hero:{ fontFamily: "Source Serif 4", cqw: 11.5, weight: 500, lineHeight: 0.92, tracking: "-0.02em" }
|
||||
stat-figure:{ fontFamily: "Source Serif 4", cqw: 11.5, weight: 500, lineHeight: 0.92, tracking: "-0.03em" }
|
||||
stat-figure-unit:{ fontFamily: "Source Serif 4", cqw: 5.7, weight: 500, lineHeight: 0.92 }
|
||||
name: { fontFamily: "Source Serif 4", cqw: 2.3, weight: 600, lineHeight: 1.0 }
|
||||
|
||||
spacing:
|
||||
slide-pad: "5cqw"
|
||||
rule: "2px"
|
||||
rule-card: "2.5px"
|
||||
radius-card: "6px"
|
||||
radius-step: "8px"
|
||||
|
||||
components:
|
||||
topbar:
|
||||
typography: "{typography.label} (JetBrains Mono) + monogram-circle or counter"
|
||||
placement: "top edge, label left, mark right"
|
||||
description: "On EVERY frame — the system's spine; a frame without it reads untreated."
|
||||
footline:
|
||||
typography: "{typography.caption-mono} ×2, space-between"
|
||||
placement: "absolute bottom edge"
|
||||
description: "On cover/data/summary frames."
|
||||
monogram-circle:
|
||||
border: "2px solid {colors.pink}"
|
||||
rounded: "50%"
|
||||
size: "130px"
|
||||
typography: "mono monogram"
|
||||
description: "The identity stamp. Cover/summary only."
|
||||
topic-tile:
|
||||
backgroundColor: "{colors.green} (pink text) / {colors.pink} (green-deep) / {colors.green-lite} (pink) / {colors.cream-2} + 2px {colors.green} border (green)"
|
||||
rounded: "{spacing.radius-card}"
|
||||
shadow: "none"
|
||||
typography: "{typography.caption-mono} ordinal + {typography.title-card-sm} + mono foot"
|
||||
description: "Fills rotate; never repeat one across a grid."
|
||||
step-tile:
|
||||
backgroundColor: "{colors.cream} + green border / {colors.green} / {colors.pink}"
|
||||
border: "2.5px solid"
|
||||
rounded: "{spacing.radius-step}"
|
||||
typography: "mono ordinal + {typography.title-card} + {typography.body-card} + mono marker over a top rule"
|
||||
description: "Framework/process card."
|
||||
kpi-block:
|
||||
typography: "{typography.caption-mono} tag → {typography.stat-figure} (+{typography.stat-figure-unit}) → {typography.body}"
|
||||
rule: "2px accent rule above"
|
||||
description: "Oversized serif figure."
|
||||
meta-dl:
|
||||
borderTop: "2px solid {colors.green}"
|
||||
typography: "{typography.caption-mono} dt + {typography.meta-value} dd"
|
||||
description: "3-column dt/dd grid."
|
||||
bar:
|
||||
backgroundColor: "{colors.pink} / {colors.cream} / {colors.green}"
|
||||
rounded: "3px 3px 0 0"
|
||||
size: "56px wide"
|
||||
typography: "mono value above"
|
||||
description: "Vertical chart bar."
|
||||
rule-thin:
|
||||
rule: "2px solid (green on cream / pink on green / green-deep on pink)"
|
||||
description: "The only separator. Never 1px, never 3px+."
|
||||
---
|
||||
|
||||
# Editorial Forest — Frame (video / frame layer)
|
||||
|
||||
## Overview
|
||||
|
||||
Editorial Forest at frame scale is a **serif-led literary-editorial system** — the register of a
|
||||
Penguin classic or a quiet annual report. One confident voice, **Source Serif 4 at weight 500**
|
||||
(optical-size axis engaged), carries every headline and stat up to ~12cqw; **JetBrains Mono** at
|
||||
weight 500 uppercase is the editorial chrome (labels, captions, axis ticks, footlines). Three
|
||||
surfaces — forest green, dusty rose, oat cream — and nothing else. Depth is **flat and paper-based**:
|
||||
no shadows, no gradients; elevation is color-block contrast + 2px hairlines + border-vs-fill.
|
||||
|
||||
The unmistakable signature is **weight 500** (never 400 display, never 700) and the **mono/serif
|
||||
role inversion** (mono for chrome, serif for body and display). Body text drops to serif weight
|
||||
400 — that 500→400 step is the reading rhythm. Every frame carries a mono topbar; the monogram
|
||||
circle is the identity stamp.
|
||||
|
||||
**Key characteristics at frame scale:**
|
||||
|
||||
- **Green / pink / cream triad** — two surface tones per frame is typical, three is loud.
|
||||
- **Source Serif 4 weight 500** (opsz, negative tracking) for all display; **serif 400** body; **JetBrains Mono 500** uppercase chrome.
|
||||
- **Flat — no shadows, no gradients**; elevation is color-block + 2px hairline + border-vs-fill.
|
||||
- **2px hairline rules** are the only separator (2.5px on step tiles); 6/8px card radii; the monogram circle is the only full round.
|
||||
- **Topbar on every frame** (mono label + monogram/counter); footline on data/cover frames.
|
||||
- **Spacious & committed** — one subject per frame in deep negative space; fewer elements at larger sizes.
|
||||
|
||||
## The Frame
|
||||
|
||||
### Frame Craft Bar
|
||||
|
||||
Three eyeball tests gate every frame before any structural check:
|
||||
|
||||
- **Squint** — one Source Serif 4 moment dominates at 3–6× its neighbor; the surface block centers the eye.
|
||||
- **Silence** — one subject per frame in deep negative space; the **topic-tile and step grids are the one dense exception**.
|
||||
- **Restraint** — **two surface tones max** per frame (three is loud); serif **weight 500** display / **400** body; green ground reserved for gravity moments.
|
||||
- **Reference** — aim at a **Penguin classic / quiet annual report / art-book spread**; failure looks like a **shadowed, multi-color web dashboard**.
|
||||
|
||||
- **Primary:** 1920×1080 (16:9). Display authored in **`cqw`** (`px ÷ 1920 × 100 = cqw`).
|
||||
- **Vertical:** 1080×1920 (9:16). **Square:** 1080×1080 (1:1).
|
||||
- **Safe area:** `slide-pad` 5cqw (the source's generous 96–140px); the topbar/footline sit inside it.
|
||||
|
||||
**The container law (load-bearing).** Every frame ground sets `container-type: size`; ALL
|
||||
frame-relative units are `cqw`/`cqh` against it — never `vw` (the source is a fixed 1920 canvas, so
|
||||
its px map cleanly: `px ÷ 1920 × 100`). Hairlines stay 2px; card radii stay 6/8px.
|
||||
|
||||
## Colors
|
||||
|
||||
Tokens identical to the source. Default ground `{colors.cream}` for content; `{colors.green}` for
|
||||
cover/statement/summary gravity. **Headlines:** green on cream, cream (or pink at hero scale) on
|
||||
green, green-deep on pink. **Body:** ink on cream, cream on green, green-deep on pink. Labels/rules
|
||||
take the region's accent (pink on green, green on cream, green-deep on pink). Tile fills rotate
|
||||
green / pink / green-lite / cream-2-with-green-border. **No fourth color family** — the triad +
|
||||
near-duplicates is the whole palette; never `rgba` transparency on a surface.
|
||||
|
||||
## Typography
|
||||
|
||||
Two ramps. The **reading/chrome ramp** (Source Serif 4 **400** body 1.56cqw; JetBrains Mono labels
|
||||
in px) carries copy + chrome; the **display ramp** (Source Serif 4 **500** `title-card-sm` 2.9cqw →
|
||||
`display-hero`/`stat-figure` 11.5cqw, opsz, negative-tracked) carries every headline + figure.
|
||||
|
||||
- **Legibility floor:** any load-bearing line ≥ **1.4cqw**; mono px labels are chrome only.
|
||||
- **Fit-to-measure:** size the headline to its length. Cap the block at **≤ 78cqw**; ≤3 words → `display-hero`; 4–6 → `headline-xl`; 7+ → `headline`. Reserve the 7.3–11.5cqw tier for statement/stat/cover.
|
||||
- **Serif is weight 500 for display, 400 for body** (the rhythm), 600 only for an attribution name. Negative tracking (−0.01..−0.03em), tight line-height (0.92–1.02). **Mono uppercase, 0.08–0.18em.** No italic, no underline, no third face, no serif body at 500.
|
||||
|
||||
## Depth & Surface
|
||||
|
||||
Flat, paper-based. Elevation from:
|
||||
|
||||
- **Color-block contrast** — a green tile on cream reads elevated by ink-block separation.
|
||||
- **2px hairline rules** — section separators in the region's accent.
|
||||
- **Border vs fill** — a cream-2 tile + 2px green border reads a different elevation than a solid tile.
|
||||
|
||||
**Ceiling:** zero shadow (none — adding `box-shadow` shatters the paper feel), no gradient, no glow; surfaces are solid ink-on-paper.
|
||||
|
||||
## Shapes
|
||||
|
||||
- **6px** topic tiles, **8px** step tiles, **2px** legend swatch, **3px 3px 0 0** bar tops, **50%** monogram circle. No square corners, no heavy rounding.
|
||||
|
||||
## Components
|
||||
|
||||
- **topbar** (the spine) + **footline** + **monogram-circle** (identity stamp) — the chrome set.
|
||||
- **topic-tile** (6px, rotating fills) / **step-tile** (8px, 2.5px border) — the catalog + framework patterns.
|
||||
- **kpi-block** (oversized serif figure) / **meta-dl** (3-col, 2px rule) / **bar** / **rule-thin** — data + structure.
|
||||
|
||||
## Frame Treatments
|
||||
|
||||
> Recipe: ground · container · composes · focal · chrome · accent · silence · Fixed/Free · density.
|
||||
> Topbar on every frame; one subject per frame in deep negative space.
|
||||
|
||||
### 1 · Cover (identity · move: oversized serif · green · left)
|
||||
|
||||
**Ground** `{colors.green}`, `slide-pad`. **Composes** topbar (mono label + monogram-circle), display-hero, body lede, footline. **Focal** a 2-line Source Serif 4 `display-hero` in pink (one word in cream), left. **Chrome** mono topbar; mono footline. **Accent** pink display + the monogram circle. **Silence** ~50%. **Fixed** serif 500, flat, monogram 2px pink. **Free** title, which word is cream. **Density** low.
|
||||
|
||||
### 2 · Topic Tiles (catalog · move: rotating-fill grid · cream · the dense frame)
|
||||
|
||||
**Ground** `{colors.cream}`, `slide-pad`. **Composes** topbar, headline-xl, 3–4× topic-tile. **Focal** a row of 6px tiles, fills rotating green / pink / cream-2-with-border, each a mono ordinal + serif title. **Chrome** mono topbar. **Accent** the tile fills (mix 3 of 4, never repeat one). **Silence** tight — the density exception. **Fixed** 6px radius, rotating fills, no shadow. **Free** tile titles, which fills. **Density** dense-exception.
|
||||
|
||||
### 3 · KPI Stat (data · move: oversized figure · green)
|
||||
|
||||
**Ground** `{colors.green}`, `slide-pad`. **Composes** topbar, kpi-block (mono tag + 220px stat-figure + serif description), footline. **Focal** a Source Serif 4 `stat-figure` (~11.5cqw, +unit) in pink over a 2px pink rule. **Chrome** mono topbar + footline. **Accent** the pink figure. **Silence** ~55%. **Fixed** serif 500 figure, 2px accent rule, flat. **Free** the figure (from script), tag, description. **Density** low.
|
||||
|
||||
### 4 · Statement (quote · move: display serif · cream · left)
|
||||
|
||||
**Ground** `{colors.cream}`, generous pad. **Composes** topbar, display, name (600), caption-mono role. **Focal** a 2-line Source Serif 4 `display` (~7.3cqw) in green; a 600-weight attribution name + mono role beneath. **Chrome** mono topbar. **Accent** none — the serif carries it. **Silence** ~55%. **Fixed** serif 500 display + 600 name only, no italic. **Free** quote, name, role. **Density** low.
|
||||
|
||||
### 5 · Step Framework (process · move: 8px step tiles · cream/green)
|
||||
|
||||
**Ground** `{colors.cream}` (or green), `slide-pad`. **Composes** topbar, headline, 3–4× step-tile. **Focal** a row of 8px step tiles (2.5px border) — mono ordinal + serif 68px title + body + mono marker over a top rule; fills rotate cream-with-green-border / green / pink. **Accent** the tile fills. **Silence** moderate. **Fixed** 8px radius, 2.5px border, mono markers. **Free** steps, fills. **Density** standard.
|
||||
|
||||
### 6 · Chart (data · move: bars + meta · green)
|
||||
|
||||
**Ground** `{colors.green}`, `slide-pad`. **Composes** topbar, headline, bars + 2px axis rules, meta-dl. **Focal** 56px pink/cream/green bars on inner-edge 2px rules with mono ticks; an optional 3-col meta-dl beneath. **Chrome** mono topbar. **Accent** the bar fills. **Silence** moderate. **Fixed** 56px bars, 3px tops, 2px axes, mono ticks. **Free** values (from script), labels. **Density** standard.
|
||||
|
||||
## Composition Rules
|
||||
|
||||
### Do
|
||||
|
||||
- Run every display in **Source Serif 4 weight 500**, opsz, negative-tracked, tight line-height; **body at 400**.
|
||||
- Set every label/caption/axis/footline in **JetBrains Mono 500 uppercase, 0.08–0.18em**.
|
||||
- Give every frame a **topbar** (mono label + monogram or counter); footline on data/cover.
|
||||
- Pick **one dominant surface per frame** (max two tones); **rotate tile fills**, never repeat one across a grid.
|
||||
- Separate sections with **2px hairlines** in the region's accent; reserve the monogram circle for cover/summary.
|
||||
- Scale display aggressively (7.3–11.5cqw) for statement/stat/cover; lean left/editorial, one subject per frame.
|
||||
|
||||
### Don't
|
||||
|
||||
- No box-shadow, gradient, or glow — flat, paper-based depth only.
|
||||
- No third typeface; no italic/underline; no serif body at 500; no mono at headline scale.
|
||||
- No fourth color family; no `rgba` surfaces; no 1px or 4px+ rules (2px / 2.5px only).
|
||||
- No two competing content blocks; don't omit the topbar.
|
||||
- Don't blow a headline edge-to-edge — step the ramp down.
|
||||
|
||||
## Aspect-Ratio Behavior
|
||||
|
||||
| Treatment | 16:9 | 9:16 | 1:1 |
|
||||
| -------------- | -------------------------------- | --------------------------- | ------------------------ |
|
||||
| Cover | display left, monogram top-right | display top, monogram below | display, monogram corner |
|
||||
| Topic Tiles | 3–4 across | stacked | 2×2 |
|
||||
| KPI Stat | figure left | figure centered, taller | centered |
|
||||
| Statement | quote left | quote stacked | centered |
|
||||
| Step Framework | 3–4 across | stacked | 2×2 |
|
||||
| Chart | bars + meta | bars taller, meta stacks | square chart |
|
||||
|
||||
`slide-pad` holds on the short edge; re-step display above the 1.4cqw floor. Topbar spans the top
|
||||
on every ratio; the monogram may drop to a corner.
|
||||
|
||||
## Approved Real Entities
|
||||
|
||||
No real customers, logos, or vendors are defined in the source — render any such mark as a
|
||||
placeholder. The monogram, footline strings, and tile counters carry per-deck identity; figures are content.
|
||||
|
||||
## Numerals & Claims (hard rule)
|
||||
|
||||
Never invent figures, KPIs, dates, or counts at frame scale. Render slots as `— figure —`,
|
||||
`{metric}`, `— %`. KPI blocks, bars, and meta values especially carry placeholders until the script
|
||||
supplies them. Issue numbers / counters are decorative chrome.
|
||||
|
||||
## Pre-Render Self-Audit
|
||||
|
||||
- **Squint** — one serif moment dominates; the surface block centers the eye.
|
||||
- **Silence** — one subject per frame in deep negative space; only the topic-tile/step grids run dense.
|
||||
- **Triad** — two surface tones max; headlines/body/labels use the correct per-surface color; no fourth family.
|
||||
- **Type** — Source Serif 4 **500** display (opsz, negative-tracked) / **400** body; mono uppercase 0.08–0.18em; ≥1.4cqw floor.
|
||||
- **Depth** — 0 shadow, 0 gradient; 2px hairlines; 6/8px radii; monogram the only full round.
|
||||
- **Chrome** — topbar present on every frame; footline on data/cover.
|
||||
- **Fabrication** — every numeral traces to the script, else placeholder.
|
||||
|
||||
## Known Gaps
|
||||
|
||||
- **Motion intentionally out of scope.** frame.md specifies composition only; the source relies on `deck-stage.js` for scaling/nav, no transition spec.
|
||||
- **Source Serif 4 (opsz 8..60) + JetBrains Mono via Google Fonts** — the optical-size axis is critical; a non-opsz fallback flattens the size-aware letterforms. CJK: LXGW WenKai (display) / Noto Serif SC (body) / Noto Sans Mono CJK (chrome).
|
||||
- **9:16 / 1:1 are guidance**; verify the floor and that two-tone discipline holds per ratio.
|
||||
- Bars, hairlines, tiles, and the monogram circle are CSS-only; no external imagery is required.
|
||||
- **Contrast:** green-on-pink / pink-on-green only at display scale (84px+); keep small text cream-on-green or ink-on-cream.
|
||||
@@ -0,0 +1,223 @@
|
||||
<!--
|
||||
Editorial Forest — 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 an editorial-forest
|
||||
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: an oat-cream card on a single 2px green hairline border — the system's only
|
||||
separator — with a 6px card radius and NO shadow (editorial-forest is flat, paper-based;
|
||||
depth is color-block + hairline, never cast). Source Serif 4 weight 500, sentence case,
|
||||
warm-ink glyphs. Upcoming words sit in faint ink; the current word gets a quiet
|
||||
dusty-rose panel (a soft 6px-radius block, no blur — editorial restraint, not a loud
|
||||
block) with ink reading on top; spoken words settle to solid ink, panel cleared. The
|
||||
per-word emphasis accent is a 2px green hairline underline, never heavy.
|
||||
-->
|
||||
<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 oat-cream card — single 2px green hairline border (the system's only
|
||||
separator), 6px card radius, NO shadow (flat, paper-based) */
|
||||
.caption-pill {
|
||||
max-width: 78%;
|
||||
padding: 20px 44px 24px;
|
||||
background: var(--cap-canvas, #efe7d4);
|
||||
border: 2px solid var(--cap-accent, #d27e96);
|
||||
border-radius: 6px;
|
||||
}
|
||||
.caption-line {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 0.1em 0.34em;
|
||||
font-family: var(--font-display, "Source Serif 4"), Georgia, serif;
|
||||
font-weight: 500;
|
||||
font-size: clamp(40px, 4.4vw, 58px);
|
||||
line-height: 1.16;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
/* upcoming — faint warm ink, no panel */
|
||||
.caption-word {
|
||||
display: inline-block;
|
||||
padding: 0 0.06em;
|
||||
color: color-mix(in srgb, var(--cap-ink, #1a1a17) 42%, var(--cap-canvas, #efe7d4));
|
||||
}
|
||||
/* current word — a quiet dusty-rose panel (box-shadow spread = a soft 6px block w/o
|
||||
layout shift; no blur — editorial restraint, not a loud block), warm ink on top */
|
||||
.caption-word.is-active {
|
||||
color: var(--cap-ink, #1a1a17);
|
||||
background: var(--cap-accent, #d27e96);
|
||||
box-shadow: 0 0 0 0.05em var(--cap-accent, #d27e96);
|
||||
border-radius: 6px;
|
||||
}
|
||||
/* already spoken — solid warm ink, panel cleared */
|
||||
.caption-word.is-spoken {
|
||||
color: var(--cap-ink, #1a1a17);
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* optional per-word accents (dormant until a word-classer tags them) */
|
||||
/* brand / key term — heavier serif weight, editorial-forest's only display inflection */
|
||||
.caption-word.cap-brand {
|
||||
font-weight: 600;
|
||||
}
|
||||
/* numerals / data — the mono chrome voice, slightly recessed */
|
||||
.caption-word.cap-num {
|
||||
font-family: var(--font-body, "JetBrains Mono"), ui-monospace, monospace;
|
||||
color: color-mix(in srgb, var(--cap-ink, #1a1a17) 72%, var(--cap-canvas, #efe7d4));
|
||||
}
|
||||
/* emphasis — a 2px green hairline underline (the system's only separator, never heavy) */
|
||||
.caption-word.cap-cta {
|
||||
text-decoration: underline;
|
||||
text-decoration-thickness: 2px;
|
||||
text-decoration-color: var(--cap-accent-2, #e89cb1);
|
||||
text-underline-offset: 0.1em;
|
||||
}
|
||||
|
||||
@media (max-aspect-ratio: 9/16) {
|
||||
.caption-pill {
|
||||
max-width: 92%;
|
||||
}
|
||||
.caption-line {
|
||||
font-size: clamp(34px, 5.4vw, 48px);
|
||||
}
|
||||
}
|
||||
</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.985 }, { scale: 1, duration: 0.18, ease: "power1.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
Reference in New Issue
Block a user