fix(skill): eval feedback — font src paths, WCAG pairings, flash-through-white regression

Five fixes from Ular's first-pass workflow run:

1. step-1-design.md Fonts section — sub-agents pointed @font-face for
   "ES Build Neutral" at the Inter .woff2 files because DESIGN.md
   only named families, never emitted exact src: paths. Now the
   Fonts section example shows per-family + per-weight file paths
   AND a copy-verbatim @font-face block sub-agents can paste, so
   there's no inference step. Adds an explicit narrative of the
   real failure mode and how to avoid it.

2. beat-builder-guide.md FONTS rule — was "brand fonts with
   capture/assets/fonts/ path need @font-face in <style>." Now:
   "copy the @font-face block VERBATIM from DESIGN.md. Do NOT guess
   which .woff2 file belongs to which family — capture filenames
   are content-hashed and there is no visible mapping. If DESIGN.md
   doesn't include exact src: paths per family, STOP and ask the
   main agent; never pair an arbitrary .woff2 with a family name
   from memory."

3. step-1-design.md Colors section — Sub-agents reproduced brand
   colors faithfully and hit WCAG AA failures on dark surfaces
   (#68686A on #18191B = 3.16:1). Now the Colors section example
   computes per-pairing contrast ratios with /⚠/ markers,
   documents the dark-surface substitute color when the brand's own
   palette fails, and points at the /hyperframes-contrast skill for
   ratio computation. Sub-agents pick text colors by surface
   context, not by "this is the brand's secondary text color."

4. capabilities.md flash-through-white entry — the "ideal as
   invisible bridge at duration: 0.01" framing caused agents to
   scatter white flashes through every composition as transition
   bridges. The fix was documented in the branch's HANDOFF but
   never landed. Now: "Fade through white midpoint — a visible
   white flash between scenes. Use only when the brand specifically
   calls for a white-flash beat boundary; this is NOT a neutral
   'default' transition."

5. step-6-validate.md Warnings list — adds a paragraph on WCAG
   contrast false positives. The validator samples at fixed
   timestamps; elements at opacity:0 / mid-fade get measured as if
   fully visible, producing spurious failures. Tells the agent to
   verify visually before changing colors to clear a WCAG warning
   — bumping a color to fix a sampling artifact changes brand
   identity for no real benefit.
This commit is contained in:
ukimsanov
2026-05-21 11:08:52 -07:00
parent e5160b6022
commit 24aef5475c
4 changed files with 69 additions and 30 deletions
@@ -180,7 +180,7 @@ If any are missing from the beat spec, the beat is under-defined. Don't fill the
- DETERMINISTIC: no `Math.random()`, `Date.now()`, `requestAnimationFrame`, `repeat:-1`.
- Always `tl.fromTo()` not `tl.from()` for entrances.
- Never stack two transform tweens on same element at same time.
- FONTS: brand fonts with `capture/assets/fonts/` path need `@font-face` in `<style>`.
- FONTS: copy the `@font-face` block VERBATIM from DESIGN.md's Fonts section. Do NOT guess which `.woff2` file belongs to which family — capture filenames are content-hashed (`14d7ce3e41dcbb66-s.p.woff2`) and there is no visible mapping. If DESIGN.md doesn't include exact `src:` paths per family, STOP and ask the main agent to add them; never pair an arbitrary `.woff2` file with a family name from memory.
## Easing — pick per intent
@@ -99,22 +99,22 @@ The runtime registers these adapters in order; each implements `discover()` / `s
### The 14 shaders
| Name | Visual | Notes |
| ------------------- | -------------------------------------------------------------------------------------- | -------------------------------------- |
| domain-warp | Multi-octave FBM warps both scenes oppositely; organic dissolve edge with accent flash | Uses NQ noise bundle |
| ridged-burn | Ridged multifractal mask reveals B; accent → bright → white burn ramp; sparks | NQ |
| whip-pan | 10-sample horizontal motion blur + lateral crossfade | No noise |
| sdf-iris | Aspect-corrected circle SDF expansion + accent-tinted glow rings | — |
| ripple-waves | Radial standing-wave UV displacement + tinted crossfade | — |
| gravitational-lens | Pinch pull toward center + R/B chromatic separation | — |
| cinematic-zoom | 12 RGB-offset radial zoom blur samples (chromatic zoom streak) | — |
| chromatic-split | R/B radial channel shift outward / inward; G fixed | Distinct from CSS chromatic aberration |
| swirl-vortex | CCW swirl with FBM noise; reciprocal on incoming | NQ |
| thermal-distortion | Vertical sin + FBM horizontal displacement; warm haze | NQ |
| flash-through-white | Fade through white midpoint; ideal as invisible bridge at `duration: 0.01` | No accent |
| cross-warp-morph | FBM vector field displaces both scenes; third FBM biases irregular wipe | NQ |
| light-leak | Fixed off-frame leak with exponential falloff + accent warmth + ridge flare | Hard-coded leak anchor |
| glitch | Line displacement + RGB lateral split + scan modulation + posterization + flicker | Deterministic |
| Name | Visual | Notes |
| ------------------- | -------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| domain-warp | Multi-octave FBM warps both scenes oppositely; organic dissolve edge with accent flash | Uses NQ noise bundle |
| ridged-burn | Ridged multifractal mask reveals B; accent → bright → white burn ramp; sparks | NQ |
| whip-pan | 10-sample horizontal motion blur + lateral crossfade | No noise |
| sdf-iris | Aspect-corrected circle SDF expansion + accent-tinted glow rings | — |
| ripple-waves | Radial standing-wave UV displacement + tinted crossfade | — |
| gravitational-lens | Pinch pull toward center + R/B chromatic separation | — |
| cinematic-zoom | 12 RGB-offset radial zoom blur samples (chromatic zoom streak) | — |
| chromatic-split | R/B radial channel shift outward / inward; G fixed | Distinct from CSS chromatic aberration |
| swirl-vortex | CCW swirl with FBM noise; reciprocal on incoming | NQ |
| thermal-distortion | Vertical sin + FBM horizontal displacement; warm haze | NQ |
| flash-through-white | Fade through white midpoint — a visible white flash between scenes | No accent. Use only when the brand specifically calls for a white-flash beat boundary; this is NOT a neutral "default" transition. |
| cross-warp-morph | FBM vector field displaces both scenes; third FBM biases irregular wipe | NQ |
| light-leak | Fixed off-frame leak with exponential falloff + accent warmth + ridge flare | Hard-coded leak anchor |
| glitch | Line displacement + RGB lateral split + scan modulation + posterization + flicker | Deterministic |
### Public API
@@ -55,38 +55,75 @@ A flat lookup of the values sub-agents grab while composing beats. Two sub-secti
List 812 colors with brand-specific names + HEX + role. Not generic ("Accent 1") but evocative ("Stripe Purple", "Deep Navy", "Slate Border"). The name carries meaning; "blue 4" doesn't.
**For each text-on-surface combination the brand uses, compute the WCAG AA contrast ratio and flag failing pairings explicitly.** A real failure mode from prior runs: the brand's secondary-text color (`#68686A`) on its dark panel color (`#18191B`) = 3.16:1, which fails AA's 4.5:1 minimum. Sub-agents faithfully reproduced the brand's color choice and the result was unreadable. Encode the safe / unsafe pairings here so sub-agents pick text colors by surface context, not by "this is the brand's secondary text color." The `/hyperframes-contrast` skill audits ratios — run it before finalizing DESIGN.md.
**Example:**
```markdown
#### Colors
- **Stripe Purple** (`#533AFD`): Primary CTA, interactive elements, focus rings — the brand's action signal
- **Deep Navy** (`#061B31`): Primary text, headings — gravitas color
- On Pure White: 6.2:1 ✅ — On Deep Navy: 3.8:1 ⚠ AA-only-Large
- **Deep Navy** (`#061B31`): Primary text on light surfaces, also a dark surface tier
- As text on Pure White: 17.4:1 ✅ — As surface: see Slate-on-Navy pairings below
- **Pure White** (`#FFFFFF`): Page background, card surfaces
- **Light Gray** (`#F5F7FA`): Surface tier 2 (cards on white pages, alternating sections)
- **Slate Blue** (`#273951`): Secondary text, subheadings, lower-emphasis content
- **Light Slate** (`#64748D`): Metadata, captions, disabled-adjacent text
- **Subtle Border** (`#D4DEE9`): Card borders, dividers
- **Vibrant Orange** (`#FF6118`): Energy accent — gradient endpoints, highlight bursts (never primary)
- **Error Red** (`#FF0022`): Validation errors
- **Success Green** (`#4CD963`): Confirmation states
- **Slate Blue** (`#273951`): Secondary text on LIGHT surfaces
- On Pure White: 12.6:1 ✅ — On Light Gray: 11.8:1 ✅ — On Deep Navy: 1.4:1 ❌ DO NOT USE
- **Light Slate** (`#64748D`): Metadata, captions on light surfaces only
- On Pure White: 4.8:1 ✅ — On Light Gray: 4.5:1 ✅ — On Deep Navy: 3.0:1 ❌ — On Dark Panel: 2.9:1 ❌
- **For dark-surface metadata, use `#9A9A9E` instead: 6.4:1 on Deep Navy ✅, 6.1:1 on Dark Panel ✅**
- **Subtle Border** (`#D4DEE9`): Card borders, dividers (not text — borders don't need AA)
- **Vibrant Orange** (`#FF6118`): Energy accent — gradient endpoints, highlight bursts (never primary text)
- **Error Red** (`#FF0022`): Validation errors. On Pure White: 4.5:1 ✅
- **Success Green** (`#4CD963`): Confirmation states. On Pure White: 1.7:1 ❌ — must be paired with a darker outline or use as accent on dark surfaces
```
**Where the brand's own palette fails WCAG**, document the substitute (like the `#9A9A9E` override above). Sub-agents pick the safe color by surface — and if the deviation matters to the brand identity, the user can revisit at Step 6.
#### Fonts
List font families (with availability notes) + the headline / body / mono trio if applicable:
List font families with their role AND **the exact file path per family + weight** from `fonts-manifest.json`. Sub-agents will copy the `@font-face` block verbatim — if you only name the family without the path, sub-agents have to guess which `.woff2` file belongs to which family and get it wrong half the time (a real failure mode from prior runs: agents pointed `@font-face` for "ES Build Neutral" at the Inter `.woff2` files and the wordmark rendered in Inter).
**Example:**
```markdown
````markdown
#### Fonts
- **Display + Body:** `sohne-var` (variable 100900, captured ✓) — weights 300 / 400 used in production
- **Mono:** `"JetBrains Mono"` — code, metadata, technical content
- **Display:** `"ES Build Neutral"` — wordmarks, headlines
- 600: `capture/assets/fonts/14d7ce3e41dcbb66-s.p.woff2`
- 700: `capture/assets/fonts/e8b276476c0ac6fa-s.p.woff2`
- **Body:** `"Inter"` (variable 100900, captured ✓) — body, labels, UI
- 400: `capture/assets/fonts/9a8d3f06c4e89f2b-s.p.woff2`
- 600: `capture/assets/fonts/1b0b3615811be75b-s.p.woff2`
- **Mono:** `"JetBrains Mono"` — code, metadata
- 400: `capture/assets/fonts/c7d2e9f5a1b3c8d4-s.p.woff2`
- **Fallback stack:** `-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif`
The brand uses **size for hierarchy, weight for emphasis**. Display 1: 48px/300, Display 2: 32px/300, Body: 14px/400. Never use weights above 400 — this brand has no bold variant in production.
**`@font-face` block to paste in every composition** (sub-agents copy this verbatim — do not invent file paths):
```css
@font-face {
font-family: "ES Build Neutral";
src: url("capture/assets/fonts/14d7ce3e41dcbb66-s.p.woff2") format("woff2");
font-weight: 600;
font-display: block;
}
@font-face {
font-family: "Inter";
src: url("capture/assets/fonts/9a8d3f06c4e89f2b-s.p.woff2") format("woff2");
font-weight: 400;
font-display: block;
}
/* + any other family/weight combinations the storyboard's beats need */
```
````
The brand uses **size for hierarchy, weight for emphasis**. Display 1: 48px/600, Display 2: 32px/600, Body: 14px/400. (Adjust to the actual brand's hierarchy.)
````
The exact `@font-face` block lets sub-agents copy verbatim instead of constructing one from inference. If a beat needs a weight that isn't in the manifest, flag it explicitly here: e.g., "ES Build Neutral 900 NOT in capture; use ES Build Neutral 700 as substitute, or fall back to Inter 700."
That's the whole typography section. If sub-agents need exact line-heights or letter-spacing, they read `design-styles.json` directly.
@@ -132,7 +169,7 @@ Cover every variant the site uses — typically Primary, Secondary/Ghost, and Ic
- **Font:** sohne-var 14px / 400
- **Padding:** `12px 0`
- **Hover:** background `rgba(83, 58, 253, 0.08)`, optional underline
```
````
#### Cards & Containers (always required if the site uses any)
@@ -62,6 +62,8 @@ Some are style suggestions you can safely ignore:
- **Deprecated attributes** (data-layer, data-end) — still work, just not preferred
- **Dense tracks** — informational, not a bug
**WCAG contrast false positives** — the validator samples text colors at fixed timestamps. Elements that are at `opacity: 0` (pre-entrance) or mid-fade at those sample timestamps get measured against the background as if they were fully visible, which produces spurious contrast failures. Before changing a color to clear a WCAG warning, verify visually that the element is actually unreadable when on-screen at full opacity. If it's only flagged for pre-entrance / exit moments, the warning is a sampling artifact, not a real failure. Bumping the color to "fix" these false positives changes the brand identity for no real benefit.
Don't blindly ignore 158 warnings. Don't blindly fix all of them either. Read them.
## Visual Verification (snapshot)