mk-background and mk-clone-wall-transition tween the card's `top`/`left`.
Layout properties snap to integer device pixels, so the move stutters under
the seek-by-frame capture engine (lint: gsap_non_transform_motion). Both
cards sit at top:0/left:0 in CSS, so the values carry straight over to x/y,
and in clone-wall the later scale composes cleanly with the translate.
Re-rendered both and diffed frames against the previous output — identical,
as intended: this changes how the motion is computed, not how it looks.
Adds scripts/lint-registry-items.mjs (bun run lint:registry-items), which
mounts each item into a throwaway project and lints it. Registry items ship
as `<name>.html`, so `hyperframes lint <dir>` fails with "No composition
found" and these items had never actually been linted — which is how both
errors reached main. Verified the script reproduces the original failure on
the pre-fix source.
Left as a local command rather than a CI gate for now; wiring it up needs
two prior fixes, noted in the PR.
Beat-driven speed ramp, freeze-frame hit, and hard cut for music-led
promos and montages. Contributed as #2957; applied here as content so the
whole catalog set lands together.
Verified by rendering: 6s, clean, deterministic (the one Math.random hit
is a comment above a fixed pattern array). Opaque root, so no demo.html
is needed for its catalog preview.
Co-authored-by: akiyoshisan1218 <akiyoshisan1218@users.noreply.github.com>
Found by stepping through motion contact sheets rather than single frames.
- hw-pipeline: arrowheads were two asymmetric barbs (l -30 -14 / l -26 18)
fixed to horizontal, so they detached from a curve that arrives on a
descending tangent. Barbs now derive from the quadratic's end tangent
(end - control) with symmetric length and spread.
- hw-pipeline, hw-scribble-transition, hw-callout-circle: the family carried
two accents — #ffb020 in hw-frame/hw-title/hw-underline and #6d6dff here.
Unified on the warm marker accent; the blue read as a default UI colour.
- hw-callout-circle: the scribble hatch rendered as stacked bars over the
labelled subject. More rows so the zigzag reads as a scribble, lighter
weight and alpha so it sits behind the label.
- yt-camera-move: the stage grid was too faint to reveal the move, so the
preview of a camera-move component looked static. Stronger grid; zoom,
slide and tilt now read.
Demos resynced from source — demo.html is a plate-swapped copy, so a source
fix does not reach the catalog preview until it is regenerated.
The contributed HTML predates any oxfmt pass, so format:check failed for
42 files in both the Format and Preflight jobs. All 29 items re-rendered
after formatting and verified against frames — no visual change.
Adds three themed registry families contributed by @jbernard077:
- mk-* minimal presentation (7 blocks, 3 components)
- yt-* retro-broadcast creator (5 blocks, 4 components)
- hw-* hand-drawn scribble (6 blocks, 5 components)
Consolidates PRs #1933, #1992, #1993 and #1994, cuts 5 of the original 34
items, and fixes the defects that CI never got to report.
Cut (5), each covered by something we already ship or trivial to inline:
- yt-doc-lower-third — would be the 12th name/role lower third next to the
10 lt-* variants and lower-third-bild
- mk-logo-sting — logo-outro exists, and yt-logo-intro is the richer version
- mk-cta-button, mk-pill-callout, yt-avatar-pip — a styled button, a styled
chip and a masked circle
Fixes:
- hw-path-text rendered as a solid black blob. #hw-pt-path is the textPath
carrier and had no rule, so it inherited SVG's default black fill while
only #hw-pt-guide set fill:none.
- mk-line-graph value labels collided with their own dots and each other.
Series after the first now label downward, and the offset moved off a CSS
transform because the entrance tween animates y and GSAP rewrote it.
- Added demo.html for the 11 components and the 3 transparent-root blocks.
Without it generate-catalog-previews.ts skips components entirely, so the
catalog-previews job failed for every component in the original PRs.
- Tagged hw-title, hw-pipeline and hw-path-text as overlays.
Also registers all catalog pages in docs.json, which the original PRs left
orphaned, and picks up 4 pre-existing blocks that had no catalog page
(camcorder-hud, editorial-flash-overlay, freeze-frame-dressing,
organic-light-leak-overlay) because the regenerated nav now links them.
Every item was verified by rendering it and looking at the frames.
Co-authored-by: jbernard077 <jbernard077@users.noreply.github.com>
- caption-emoji-pop: shadowForColor now builds its glow via color-mix()
instead of hex-pair slicing, so the strict 6-digit brand-color gate is
gone — any CSS color the sibling templates accept (#fff, rgb(), named)
now renders instead of silently falling back to the default palette
- caption-weight-shift: fitFontSize now sizes against the WIDEST split
line rather than the joined group text (two-line groups no longer shrink
unnecessarily), and avoidSingleWordGroups' merges re-check fitsInTwoLines
like makeGroups' first pass does (merged groups can no longer overflow
the split budget)
- all 5: hfApplyStageConfig clamps resolution to the published validator's
<=8192 bound (validator is optional pre-flight; unbounded stages OOM
render workers), and fit floors carry a comment documenting that the
minimum size is returned unverified by design
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two review findings on the caption-data runtime, applied to all 5 templates:
- hfValidate's version gate used Math.floor(Number(v)) > HF_CONTRACT_VERSION,
and Number("v2") is NaN — NaN comparisons are always false, so malformed
versions slid through with no unsupported-version signal. An explicit
Number.isFinite check closes it.
- hfBoot's sibling-fetch .then called hfAttach unconditionally; a manual
window.__HF_CAPTION_ATTACH__ call landing while the fetch or fonts.ready
was still pending got clobbered by the late boot payload. Boot now yields
if a timeline already exists.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
caption-highlight was already IIFE-wrapped; the other four leaked their
runtime (hfAttach/hfBuild/...) as script-scope globals. Harmless when boot
was synchronous, but the data-driven retrofit defers attach behind
fonts.ready + the sibling fetch — with two caption components pasted into
one composition document, every script finishes before any deferred boot
runs, the last script's definitions win the shared scope, and the first
component never registers its timeline (a renderer waiting on it hangs to
timeout). Wrapping each template's script keeps its internals private so
each boot attaches its own component. window.__HF_CAPTION_ATTACH__ remains
intentionally window-scoped (last-defined-wins).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
hfApplyStageConfig (Block B, verbatim across all 5 retrofitted caption
identities) only ever set --hf-caption-primary/--hf-caption-accent when the
corresponding brand.primaryColor/accentColor key was present, with no else
branch to clear it when absent. Re-attaching a brand-less payload after a
branded one left the custom property (and any JS-cached color derived from
it, e.g. caption-pill-karaoke's hfColorActive and caption-emoji-pop's
hfAccentColors) stuck at the stale value instead of reverting to the CSS
fallback, violating idempotent re-attach.
Also removes caption-editorial-emphasis's dead .word--italic CSS rule and
CLASS_MAP.i entry — hfMakeBlocks only ever emits "n"/"e" tags, "i" was only
reachable via the old hand-authored BLOCKS literal this task replaced.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Both identities looped over every OTHER group/block to force its opacity
to 0 at each group's own start time, in addition to each group already
setting its own opacity to 0 at its own end. Since groups/blocks occupy
non-overlapping time windows and already own their full opacity
lifecycle, that loop was dead weight — but it made timeline construction
O(n^2) in the number of groups/blocks. Under the Task 7 stress transcript
(long, frequent "emphasis" words forcing near single-word blocks/groups),
n reached ~2000 and the page hung well past a 30s test timeout for both
identities.
Verified behavior-preserving: full templates.test.ts (41 tests, incl.
opacity/seek assertions) and the new limits.test.ts stress suite pass
against both identities after the removal.
Replaces the hand-authored BLOCKS literal with hfMakeBlocks, a heuristic that
groups words into blocks/lines from timing (pauses, punctuation, max words per
block) and hfIsEmphasisWord (long, non-stopword content words) to decide which
word gets the large Playfair Display emphasis treatment and its own slide-in
line. computeLineSize/buildBlocks/fitBlocks and timeline construction now live
inside hfBuild, closing over layout-scaled font sizes and widths. Adds the
shared Blocks A-E caption-data runtime (attach/gate/brand config) and the
.word/.word--emphasis CSS brand hook for --hf-caption-primary. This is the
last of the five caption identities to go data-driven.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Replaces the hardcoded RAW_GROUPS index-pair array with hfMakeGroups, a
real fitting/pause/punctuation-based grouper, and wires the component
into the shared caption-data runtime (validate/gate/attach). Adds CSS
brand hooks: .hl-word text color from --hf-caption-primary, .hl-word-bg
gradient from --hf-caption-accent (second stop via color-mix()).
Retrofits caption-emoji-pop onto the shared HyperFrames caption-data runtime
(Blocks A-D) and the emphasis heuristic (Block E), replacing the hardcoded
7-word emoji map, fixed KEYWORDS set, and TRANSITION_WORDS set with a
generic ~40-entry lexicon and HF_STOPWORDS/hfIsEmphasisWord. The component
now consumes brand.primaryColor (hfPrimaryColor) and brand.accentColor
(hfAccentColors) and rescales stage/font/emoji sizing to the runtime
resolution via layout.fontScale/scaleX/scaleY.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The flowchart and flowchart-vertical blocks read tl.labels["hold5"] back
from the timeline to schedule the "Pythom" -> "Python" typing correction.
The render compiler executes composition scripts under a GSAP proxy whose
timeline exposes no readable .labels map, so this line throws
("Composition script failed ... Cannot read properties of undefined
(reading 'hold5')") before window.__timelines is assigned. The block is
never registered: renders show only the dotted-grid background and every
render pays the 45s sub_timeline_readiness_timeout.
Use GSAP's own label-relative position syntax ("hold5+=<offset>") instead,
matching every other step in these files. Identical timing, no .labels read.
Verified with hyperframes@0.7.60 render: block was blank before, animates
after; the readiness timeout disappears.
* docs(registry,skills): surface code-highlight 0-based indexing and opacity-reveal sweep guidance
From the 2026-07-14 CLI feedback digest (skills-owner action): a user
building code teaching videos hit two authoring gaps.
1. code-highlight's `line` is intentionally zero-based (`line: 1` =
second displayed line) but the warning lived only in pr-to-video's
code-vocabulary reference — nowhere an author actually touches the
value. Call it out at the block-use sites: the `__BLOCK` declaration
itself, the registry-item description, and the motion-graphics
catalog map.
2. Opacity-only code-typing tripped `sweep_static` for that user, who
worked around it with a slow host y-drift. The sweep fingerprint
does include per-element opacity, so document the actual trap (a
reveal that settles before the sampled window, then holds a static
frame) and the idiomatic fixes (spread the reveal / keep a blinking
caret alive) in the check reference — and pin the fingerprint's
opacity sensitivity with a regression test covering both the
visibility-floor crossing and a mid-fade value change.
* docs(catalog): regenerate code-highlight page from updated registry-item description
Only the code-highlight page is committed: a full generate-catalog-pages
run also surfaces ~34 blocks missing from the git-tracked catalog index
(pre-existing drift on main), which belongs in its own chore PR.
Running the CLI from source (tsx dev script, as CI's smoke job does)
transpiles with keepNames, which rewrites named inner functions in
serialized page closures into __name(...) calls — a helper that exists
in the Node bundle but not in the browser realm. The unified seek
closure was the first validate-path page function with named inner
functions, so 'hyperframes validate' threw '__name is not defined' in
CI while the dist build worked. Every session opener now installs a
no-op __name shim via evaluateOnNewDocument before any page script
runs, immunizing all serialized closures regardless of build mode.
Three filters keep the escalated contrast gate honest, each surfaced by
running check across the registry examples:
- data-layout-ignore (the layout audit's existing decorative opt-out)
now also excludes set-dressing text from the contrast audit — one
vocabulary for 'not copy a viewer must read'.
- Text that has (nearly) left the canvas is skipped: sampling a clamped
off-canvas box reads border pixels and produced the classic false
white-on-white (a cursor exiting the frame).
- Contrast failures follow the same persistence rule as layout findings:
observed at a single sample of a multi-sample sweep demotes to
warning; held failures gate.
Example fixes the sweep exposed: motion-blur's 21 deliberately-dim rail
labels are marked decorative (its vivid labels pass on their own);
nyt-graph's subtitle and source-note adopt the gate's suggested
compliant gray; decision-tree's declared duration drops 15s to 10s —
its content ends at ~9.5s and every render shipped a blank white tail.
The persistence-tier promotion surfaced two held content_overlap errors
on the hero heading; zooming in shows the collaborative-cursor demo
(cursor badge + typed comment bubble) hovering by design. Opt out with
data-layout-allow-overlap per the finding's own fixHint. The example's 4
pre-existing gsap_css_transform_conflict lint errors stay for a separate
example-maintenance pass.