diff --git a/docs/catalog/blocks/code-highlight.mdx b/docs/catalog/blocks/code-highlight.mdx
index 690b5772e6..7423e13765 100644
--- a/docs/catalog/blocks/code-highlight.mdx
+++ b/docs/catalog/blocks/code-highlight.mdx
@@ -1,11 +1,11 @@
---
title: "Code Highlight Sweep"
-description: "A highlight band sweeps across a target line while the surrounding context dims — draws the eye to one line."
+description: "A highlight band sweeps across a target line while the surrounding context dims — draws the eye to one line. `line` is 0-based: `line: 1` targets the second displayed line (unlike code-scroll, whose target is 1-based)."
---
# Code Highlight Sweep
-A highlight band sweeps across a target line while the surrounding context dims — draws the eye to one line.
+A highlight band sweeps across a target line while the surrounding context dims — draws the eye to one line. `line` is 0-based: `line: 1` targets the second displayed line (unlike code-scroll, whose target is 1-based).
`code` `code-animation` `highlight` `developer`
diff --git a/packages/cli/src/commands/layout-audit.browser.test.ts b/packages/cli/src/commands/layout-audit.browser.test.ts
index a7b36d2d36..eb551164ba 100644
--- a/packages/cli/src/commands/layout-audit.browser.test.ts
+++ b/packages/cli/src/commands/layout-audit.browser.test.ts
@@ -63,6 +63,49 @@ describe("layout-audit.browser", () => {
expect(after).not.toBe(before);
});
+ // Opacity-reveal fixture (CLI feedback digest 2026-07-14): code-typing style
+ // scenes reveal pre-laid-out characters via opacity only — no geometry ever
+ // moves. The sweep fingerprint must treat that as motion, both while a glyph
+ // fades (opacity value changes) and when it crosses the 0.2 visibility floor
+ // (element enters the signature); otherwise `check` misfires `sweep_static`
+ // and authors reach for geometry hacks (a slow host y-drift) to pass.
+ it("changes the sweep fingerprint when text reveals via opacity alone", () => {
+ document.body.innerHTML = `
+
,
+ },
+ );
+
+ installAuditScript();
+ const collect = (window as unknown as { __hyperframesLayoutGeometry: () => string })
+ .__hyperframesLayoutGeometry;
+
+ const hidden = collect(); // below the 0.2 visibility floor — not in the signature
+ charOpacity = "0.5";
+ const fading = collect(); // mid-fade — present, opacity part of the signature
+ charOpacity = "1";
+ const revealed = collect(); // settled
+
+ expect(fading).not.toBe(hidden);
+ expect(revealed).not.toBe(fading);
+ });
+
it("uses authored canvas dimensions when the root bounding rect is degenerate", () => {
document.body.innerHTML = `
diff --git a/registry/blocks/code-highlight/code-highlight.html b/registry/blocks/code-highlight/code-highlight.html
index 1d6dd76e94..02661a5661 100644
--- a/registry/blocks/code-highlight/code-highlight.html
+++ b/registry/blocks/code-highlight/code-highlight.html
@@ -536,6 +536,9 @@
id: "code-highlight",
effect: "highlight",
seq: "feature",
+ // `line` is ZERO-BASED: line: 1 highlights the SECOND displayed line
+ // (gutter 2). Note code-scroll's target is 1-based — don't carry a
+ // value between the two blocks without re-checking it.
line: 1,
duration: 5,
};
diff --git a/registry/blocks/code-highlight/registry-item.json b/registry/blocks/code-highlight/registry-item.json
index 2c37e03407..bf92c5d1ac 100644
--- a/registry/blocks/code-highlight/registry-item.json
+++ b/registry/blocks/code-highlight/registry-item.json
@@ -3,7 +3,7 @@
"name": "code-highlight",
"type": "hyperframes:block",
"title": "Code Highlight Sweep",
- "description": "A highlight band sweeps across a target line while the surrounding context dims — draws the eye to one line.",
+ "description": "A highlight band sweeps across a target line while the surrounding context dims — draws the eye to one line. `line` is 0-based: `line: 1` targets the second displayed line (unlike code-scroll, whose target is 1-based).",
"dimensions": {
"width": 1920,
"height": 1080
diff --git a/skills-manifest.json b/skills-manifest.json
index 4868c30dd2..ae2a7e40e8 100644
--- a/skills-manifest.json
+++ b/skills-manifest.json
@@ -26,7 +26,7 @@
"files": 102
},
"hyperframes-cli": {
- "hash": "bc0feefa2255c5e1",
+ "hash": "1065ea34fecfda50",
"files": 11
},
"hyperframes-core": {
@@ -50,7 +50,7 @@
"files": 133
},
"motion-graphics": {
- "hash": "1aee662a16dedc08",
+ "hash": "cc93e3d220a0ebf5",
"files": 23
},
"music-to-video": {
diff --git a/skills/hyperframes-cli/references/lint-validate-inspect.md b/skills/hyperframes-cli/references/lint-validate-inspect.md
index 984fdb5bc2..78a6eee89c 100644
--- a/skills/hyperframes-cli/references/lint-validate-inspect.md
+++ b/skills/hyperframes-cli/references/lint-validate-inspect.md
@@ -56,7 +56,7 @@ One command, one Chrome boot. `check` runs the linter first and skips the browse
Every finding carries a selector, the element's `data-*` identity, the composition source file, a bbox, and the sample time: jump straight from the JSON to the HTML you must edit and re-run.
-**Severity is persistence-aware.** A dynamic issue observed at a single grid sample (an entrance/exit transient) demotes to info and never gates. Issues held across samples gate the exit code, a held `content_overlap` is an error, and a held, partially-visible `canvas_overflow` breaching ≥5% of the canvas promotes to warning. Coordinate-frame findings (`escaped_container`, `panel_out_of_canvas`, `connector_detached`) flag geometry computed in one frame but rendered in another — an element far outside its offset parent, a painted panel stuck across the canvas edge, a connector line detached from every node. If a 3s+ composition shows zero geometry change across every sample, `check` fails with `sweep_static`: a frozen timeline makes every green verdict unreliable, so it refuses to pass.
+**Severity is persistence-aware.** A dynamic issue observed at a single grid sample (an entrance/exit transient) demotes to info and never gates. Issues held across samples gate the exit code, a held `content_overlap` is an error, and a held, partially-visible `canvas_overflow` breaching ≥5% of the canvas promotes to warning. Coordinate-frame findings (`escaped_container`, `panel_out_of_canvas`, `connector_detached`) flag geometry computed in one frame but rendered in another — an element far outside its offset parent, a painted panel stuck across the canvas edge, a connector line detached from every node. If a 3s+ composition shows zero geometry change across every sample, `check` fails with `sweep_static`: a frozen timeline makes every green verdict unreliable, so it refuses to pass. The fingerprint includes per-element opacity, so opacity-only reveals (code typing, staggered fades) count as motion — but only while they're still in flight at the sampled times. The classic trap is a reveal that completes early and then holds a static frame for the rest of the duration: every sample lands on the settled state and the run fails. Spread the reveal across the timeline or keep one continuously animated element alive (a blinking caret is idiomatic for code typing) — don't bolt on a slow position drift just to appease the check.
**Escape hatches** (mark intent in the HTML, then re-run):
diff --git a/skills/motion-graphics/catalog-map.md b/skills/motion-graphics/catalog-map.md
index a0d008a806..b69bcd7adc 100644
--- a/skills/motion-graphics/catalog-map.md
+++ b/skills/motion-graphics/catalog-map.md
@@ -10,19 +10,19 @@ After classifying intent → category, the **Director names the block(s) + what
## Category → block(s)
-| Category | Borrow from catalog | Customize | Gaps → hand-author |
-| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------- |
-| **kinetic-typo** | one of **18 `caption-*`** (kinetic-slam, editorial-emphasis, clip-wipe, gradient-fill, neon-glow, glitch-rgb, matrix-decode, particle-burst, weight-shift, pill-karaoke, highlight, blend-difference…) | words; `emphasis_words` → `word--emphasis`; palette; font; timing | a motif none of the 18 cover |
-| **charts** | `data-chart` (bar+line, staggered, value labels) | edit the data array + headline/subtitle; `--bg`/`--text` | pie/donut, bar-chart-race, ring/% |
-| **stat** | `apple-money-count` (finance: $ counter + burst + SFX) **or** our generic `stat-motion` (any %/number + ring) | target, prefix/suffix, label, palette | — |
-| maps/geo | **vector lane**: `us-map` (+bubble/hex/flow), `world-map`, `spain-map` + **hand-author** `geo-highlight`/`geo-flow`/`flag-borders`/`pin-rollout` (NOT in registry — build per `categories/maps/module.md`). **basemap lane** (real satellite/dark / zoom-to-place): bake via `categories/maps/bake-basemap.mjs` → `