Commit Graph
109 Commits
Author SHA1 Message Date
WaterrrForeverandClaude Opus 5.5 688229314f feat(skills)!: publish seven knowledge skills aligned with Studio and retire the workflow skills (#5651)
* refactor(skills): fold six domain skills into hyperframes and media-use guides

hyperframes-core, -animation, -keyframes, -creative and -studio move to
skills/hyperframes/{core,animation,keyframes,creative,studio}/, and
hyperframes-audio moves to skills/media-use/mix/. Each moved SKILL.md becomes
a frontmatter-free GUIDE.md so installers that scan recursively for SKILL.md
no longer see them as skills; the published list goes from 21 to 15.

Relative links, code paths and skill-name mentions are rewritten to the new
locations, the catalog surfaces, marketplace core-skills list, fallback core
set, module-copy targets and skills-manifest.json follow, and
`hyperframes skills update` now removes the retired skills (RETIRED_SKILLS)
when they are still installed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(skills): slim the core entries and give media-use a HeyGen and a free path

The four entries every run opens are rewritten to what an agent cannot infer:
hyperframes 21.8 KB to 7.7 KB, hyperframes-cli 24.8 KB to 6.9 KB,
hyperframes-registry 9.9 KB to 4.3 KB, media-use 9.7 KB to 4.9 KB. Detail moved
whole into references (routing close calls and creator edits, CLI pin upgrades,
catalog search tiers, CLI conventions and feedback reports).

- hyperframes: state table, routing, the render contract, a terminal work loop
  (check, snapshot, preview before render), the desktop-app hand-off, and a
  table of the folded guides.
- media-use: one table of HeyGen versus free local tools per need; HeyGen
  sign-in, credits and the CLI's video-agent, avatar, clone, lipsync, translate
  and clipping commands; every free path works with no account.
- scripts/check-core-skills.mjs holds entries and descriptions to budget and
  checks every taught `npx hyperframes` command and path; runs in lint.
- Core entries are exempt from the formatter: its table padding cost ~1.5 KB
  per entry on every run.
- Content tests follow the text to its new home.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(skills): plugin runs skip skills update; media-use explains HeyGen sign-in and TTS setup

The smoke eval showed a presenter request never reaching media-use and the
free voiceover failing on a missing kokoro-onnx; plugin runs also ran the
skills update the plugin rules forbid.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* chore(skills): drop a committed Python bytecode file and ignore bytecode

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(skills): align the entry with the Studio skills and keep the workflows' measured facts

The entry drops routing and the intent interview and carries the contract, the
editable-project rules and the work loop. Studio's format, runtimes, motion,
look and captions references come over for terminal use, frame presets move to
assets/, media-use gains Studio's mixing and local-command references, and
film.md and overlays.md keep what the workflow skills measured.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(skills)!: retire the workflow skills and folded guides; publish seven knowledge skills

The skill pack now publishes exactly seven skills - hyperframes,
hyperframes-cli, hyperframes-registry, media-use, figma, slideshow and
remotion-to-hyperframes - carrying knowledge, not process. The eight
workflow skills, the folded domain guides and the routing/interview
references are deleted; their measured facts already live in the
hyperframes references (format, runtimes, motion, look, captions, film,
overlays) and media-use's mixing reference.

- bake-basemap.mjs and locate.mjs move to skills/hyperframes/scripts/
  (bake-basemap gains --help); extract-audio-data.py moves to
  media-use/audio/scripts/ for the caption motion reference.
- skills update: the eight workflows join RETIRED_SKILLS so existing
  installs prune them (by lock attribution only, since their names are
  generic); naming a retired skill reports where its knowledge went and
  succeeds instead of failing as unknown.
- Capture prompts point at the hyperframes skill's references/film.md;
  the live-map warning names the new bake-basemap path.
- Content tests that pinned knowledge are retargeted to the kept
  references; tests of deleted workflows, scripts and process go.
- Catalog surfaces, templates and docs describe the seven skills.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(skills): resolve the local.md captions link and allow capture's output files in citations

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(skills): regenerate the skills manifest from tracked files only

The committed media-use hash included an ignored __pycache__ bytecode file present
in the local checkout, so CI's clean checkout hashed differently.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(docs): restore generated catalog pages and docs navigation to main

Catalog PRs commit item sources only; CI regenerates the pages from the edited
registry sources. docs/docs.json is catalog navigation, so its Workflows group
rename is left for a non-catalog PR.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* build(cli): raise the CLI size budget for the skill references it bundles

The CLI bundles skills/hyperframes, which now carries the folded domain guides and
frame presets (155 KB to 1.08 MB in git). Budgets are the CI-measured size plus 5%:
packed 7526634, unpacked 22593213.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* test: keep repo-sweep guards meaningful after the workflow skills left

The last shell ffprobe caller and most root index.html examples lived in retired
skills. The shell detector is now pinned on a literal command line, and the
structure-rule sweep needs more than 5 roots (11 remain) rather than 30.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(skills): slim figma, slideshow and remotion to the Studio shape; restore the storyboard board

slideshow 33.5 KB to 4.9, figma 18.2 to 6.2, remotion-to-hyperframes 11.0 to
4.9, each keeping the facts only it holds (the figma motion helpers, verify gate,
storyboard decoding and usage beacons; present and hand-off; the Remotion lint,
SSIM check and API map). The board page format from the storyboard recipe comes
back as hyperframes/references/storyboard.md: the desktop app's Storyboard tab
reads boards/storyboard.html by its frame, title and time classes. The entry and
the init template now say to snapshot the moments an edit changed before
replying, which the terminal eval found skipped in 22 of 38 runs.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(skills): manifest without local bytecode; Studio's edit prompt names kept files

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(skills): render when the person asks for the file or for no stops, not only after approval

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(skills): keep the voiceover carve in media-use; presets stage their own fonts

Review follow-ups on #5651:
- carve.mjs and its tests move to skills/media-use/audio/scripts; the audio
  docs, mixing.md and two Studio comments point there.
- code-editorial's FRAME.md says to copy its bundled WOFF2s and add the
  @font-face blocks, instead of promising the retired build-frame.mjs.
- media-use states the host rule before the HeyGen sign-in steps.
- storyboard-format marks transition_in as a note the agent builds itself.
- Two stale citations (creator-editing-recipes.md, sub-compositions.md).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* chore(skills): regenerate the manifest without local bytecode

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(skills): the CLI render step follows step 4's rule, not approval alone

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(ci): run the core-skills check from lint again after the main merge

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(skills): films that tell a story start from three storylines; maps use real imagery

Opus 5.5 arm D read motion.md and applied it, but its launch film was a
feature list (pain, three feature cards, proof, logo): film.md left story
to the model. And the map case never opened film.md, so it drew Natural
Earth outlines instead of baking satellite tiles.

- references/story.md: the generic half of Studio's moment-film story and
  craft (one thing, three storylines with world and device, the hook,
  beats, worlds, copy, brand, tells of a generated film). The person picks
  a storyline, or the agent takes its recommendation when asked for the
  file or for no stops.
- The entry's work loop names the storyline step for films over ~15 s; the
  references table routes stories to story.md and maps to film.md.
- film.md points at story.md and gives maps and real places a section.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(skills): story.md follows the entry: no voiceover unless asked

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(skills): bake-basemap starts from a globe and stops waiting out its idle timeout

The Opus 5.5 map run spent 13 of its 38 minutes on parallel bakes that
stalled and 15 more recording 300 frames, after rewriting the script because
it could only start on a flat map.

- PROJECTION=globe with a seeded starfield and atmosphere, and FROM/PAN to
  turn the globe to CENTER before the dive.
- Each frame polls for loaded tiles instead of waiting for an "idle" event
  that had often already fired: 30 frames went from 198-219 s to 10-13 s
  locally with byte-identical frames.
- Progress prints seconds a frame and minutes left; film.md shows the globe
  call and says to run one bake at a time.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(skills): render in the foreground; wait on a detached render's log, never pgrep -f

Both Opus 5.5 explainer runs started a detached render, then waited with
`until ! pgrep -f "hyperframes render"`, which matches its own loop: each
render took 88-117 s and the wait sat for the tool's full 10 minutes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(skills): route each kind of film; storylines carry look and sound; add review and sound

The entry routes by request (launch, explainer, music, data, vertical, 3D,
character) to a self-contained file under references/types/, and a film
over ~15 s now gets music and sounds on its visible actions unless the
person asked for silence. Each storyline names its look and its sound, so
picking one picks both; STORYBOARD.md frames carry `reads` and `sound`.

review.md is the whole-film pass: the check warnings, the review sheets,
seven questions to look for, P0/P1/P2 notes and REVIEW.md. motion.md gains
eases by role, six title entrances, a reading-time rule and nine measured
transitions; look.md a design plan before the first scene, with presets
kept for decks and named picks. story.md adds the second default that
generated design falls into, with what to do instead.

The blank template and format.md no longer teach the fade-and-slide-up
entrance on a dark canvas.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(cli): check warns on unread lines, dead stretches, small type, empty frame 0 and silence

A coding agent cannot watch its film, so check now measures what a viewer
notices over time. A new `craft` section holds warnings only (exit 0
unless --strict), sampled on the dense 8 fps grid the overlap pass
already seeks, skipped for --range checks:

- line_unread: a reading-size line (>= 30 px tall at 1080) on screen
  under 0.4 s for 1-2 words, else under 3/4 of 0.5 s + 0.3 s a word;
  the film's last line needs 2 s. Short lines are re-measured between
  samples to ~0.01 s before judging. Captions-track and
  data-layout-ignore text are exempt.
- dead_stretch: more than 3 s where the keepsMoving liveness signature
  does not change, outside the final 3 s and any data-hold element;
  quiet when sweep_static fires or a whole-composition keepsMoving
  assertion owns the limit.
- text_below_floor: text under 2.6% of the short side (28 px on 1080)
  held 0.5 s, one finding per file listing every element.
- first_frame_empty: frame 0 paints only the background.
- film_silent: over 15 s with no unmuted audio or audible video.

Each message names the fix and the skill reference to read. keepsMoving
now also leaves the film's last 3 s (a still end card) out of its
static runs, so the two agree.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(cli): snapshot --review captures a beat sheet, seam strips and a phone sheet

snapshot --review picks the frames that show a film over time instead of
evenly spaced ones, in the same single browser session:

- review-beats.jpg: one settled frame per scene at its start + 0.3 s,
  plus one per timed data-beat="name" element, labelled time and id.
  Scenes are the composition's top-level timed clips and sub-composition
  hosts, read like `timeline` reads rows (captions and audio excluded)
  and timed by the runtime's start resolver over the live page.
- review-seam-NN.jpg: frames at -0.2, -0.1, 0, +0.1, +0.2 s around
  every cut (each scene start, and any scene end no scene starts at).
- review-phone.jpg: the beat frames at 360 px wide.

--composition scopes the review to one sub-composition; with fewer than
two scenes the beats spread evenly and a note says so. snapshot also
gains --json: one envelope with every frame's path and time and, under
--review, the scenes, beats and seams behind the sheets; progress moves
to stderr.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(cli): craft warnings stay out of --strict; a hold gets a real next action, not a caret

Craft findings (unread lines, dead stretches, small type, an empty first
frame, silence) are judgment calls a film may break on purpose, so
--strict keeps gating defects only. The check reference no longer
suggests a blinking caret to keep a still frame moving.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(cli): review spreads beats when every scene starts together; map credits skip the type floor

A map film whose basemap and overlay both start at 0 s got one beat
frame; the even spread now keys on distinct scene starts. film.md tells
agents to mark the imagery credit data-layout-ignore and back it, since
the new type floor flagged it on the first real run.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(cli): review frames each scene at its middle too, not only 0.3 s in

On the first real explainer, 0.3 s into a scene showed a bare colour
field before its title entered. Each scene longer than about a second
and a half now adds a frame at its middle, so the sheet shows what the
scene builds.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(skills): a film is built from a numeric SPEC.md, with defaults banned by name and a look test

Studied in full: flxrnc's Motion Lab (vague vs directed prompt), 0xMovez's
course, EianLu's riso skill, agentchengfeng's style samples and the two
Opus 5.5 guides. Their common point: adjectives are where the default look
lives, so the agent now writes SPEC.md before any scene (GOAL, OUTPUT,
TIME, LOOK, MOTION, SHOTS, BANNED, QA), with a number for every adjective.

spec.md bans the defaults by name, unless the brand or reference uses
them (a dark void with small things floating, glow and particles, the
cream-serif-mono-label look, fade-and-slide-up, corner brackets), and
grows that list after the first render. Each storyline gets a 3 s test of
its hero shot before the pick; a look is written as the rules of one
medium. Composition numbers: the hero fills 40% of the frame, one hero
move per shot, the shot's information in within 2.5 s then a held poster.

motion.md adds springs by damping ratio, shorter exits, overlap, stagger
and the beat grid; review.md reviews as a harsh director and grows BANNED;
vertical.md takes the riso skill's measured safe band; explainer.md the
shapes of information.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(skills): story before look; every object reads as itself; posters only for info cards

The first spec-driven C02 swapped the dark default for a flat Swiss
poster, and the story went with it: two black bars nobody read as mirrors,
slogans instead of cause and effect, a storyline picked for its picture.

The spec now serves the story: a storyline is picked for the story a
stranger would follow, the look test asks first whether a stranger
understands what they see, and SPEC.md gains an OBJECTS line giving each
thing the material and details that make it read without a label. The
2.5 s held-poster rule is limited to information cards; in a story film
every shot causes the next. Explainers start from a person or situation
the viewer knows. Review names every object as a stranger would and
treats one that needs explaining as a P0.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(skills): storylines are three films in words, each with a device and a form

The three storylines are pitched as text and the person picks from them;
nothing is built for a storyline before the pick, so the 3 s hero-shot
look test is gone. Ideas pair a device with a form, five or more first,
keeping the three that differ most, and the one stop states the rest in
an assumption line that names every paid item beside its free option.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* ci: list main's new embedded-captions hf-cli test among the retired workflow files

The merge with main removed it with the rest of the retired skill, and the
deletion guard rejects any removal it has not been told about.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(core): keep the audio preset comment within core's size budget

The comment's new citation to media-use's mixing reference made the published
package 14 bytes larger than its budget, which sits at main's exact size.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(cli): craft keeps its own 8 fps grid; comments back within the checks

Review on #5651 found that the merge moved craft onto the overlap pass's
times, which main caps at 180, so a brief line in a long film fell between
samples and line_unread missed it. Craft now builds its own grid at 8 fps up to
600 samples, as before the merge, and a 60 s film with a line shown for 0.2 s
pins it.

The Comments check passes again: REVIEW.md and STORYLINES.md join the files a
skill writes into a project, the comment lines this branch added come back
under each file's share at the merge-base, and the audio preset comment drops
a word the check reads as history.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-21 19:19:31 +00:00
Miguel Ángel b16d5a7063 fix(catalog): token-themed component previews no longer render black (#5995)
* fix(catalog): token-themed component previews no longer render black

Seven components read theme tokens with no fallback, so with no theme
pack they painted dark text on a black canvas, both when installed and
in their live Catalog preview. They now carry exact-value fallbacks from
their own demos, as the theme contract requires, and a test fails any
registry item that reads a contract token bare.

The Catalog's snippet preview wrapper also copies the tokens each demo
sets on its mount, and paints the demo's background, so the preview
wears the demo's theme.

* fix(catalog): trim the preview theme helper comment

* test(catalog): strip HTML comments with the parser in the theme fallback gate
2026-10-21 05:25:09 +00:00
Vance IngallsandClaude Sonnet 5.5 23e7a384b6 feat(registry): add echo-smear component (#5980)
* feat(registry): echo-smear engine and math tests

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* feat(registry): echo-smear demo

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* feat(registry): register echo-smear

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* fix(registry): echo-smear copies inherit visibility and record fast

Final review fixes. Copies were forced visible and escaped a hidden scene.

A selector matching earlier copies re-targeted them, and copies kept data-start and data-duration.

A later attach re-drew every earlier copy at each recording sample.

Tests now run the engine through a fake gsap and timeline, and catch five seam mutations.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* fix(registry): echo-smear skips targets that are not out of flow

Found testing on real blocks: transitions-push and blue-sweater animate static elements.

Their copies stacked in normal flow and pushed the scene 64800px down, rendering black.

The snippet now warns and skips a target that is not absolute or fixed.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* fix(registry): echo-smear group keeps the target's z-index

The wrapper group formed a new stacking context at z-index auto,

so a target that sat above a sibling dropped below it (the cursor went under the card).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* fix(registry): echo-smear copies live in a sibling group, like motion-blur

The target is no longer wrapped, so it keeps its parent, blend mode and pointer events.

Static targets smear in place; the group mirrors the target's visibility and z-index.

Copies carry resolved styles, media inside is inert, and an element is attached once.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* fix(registry): echo-smear records resolved transforms and leaves callbacks alone

Poses come from the resolved transform and opacity, so xPercent and any other transform survive.

Travel is measured on the element's own box, so ancestor-only motion draws no copies.

The recording sweep is quiet unless callbacks: true, survives a missing root, and releases its flag.

Copies share one style rule per target and are built to the peak count.

The demo is pinned to the snippet by a test.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* fix(registry): echo-smear copies follow the box, root and look of their target

Copies hidden at a box change never caught up, so a static target drew at the corner.

A shadow-root target threw on every seek; its rules now live in its own root.

Copies show the recorded filter, clip-path and individual transform properties.

No ResizeObserver, so a frame no longer depends on the first frame a worker drew.

Parent perspective, border-box sizing, media children and SVG children are handled.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* test(registry): echo-smear leaves real GSAP callbacks unfired at attach

Covers the blocker from review: a call or onComplete on the timeline fired during attach.

Runs against real GSAP 3.15 with a removal callback; it fails if the sweep seeks with events on.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* fix(registry): echo-smear frames do not depend on the seek path

Group visibility and the copies' box are recorded in the sweep, not read live in the update.

A backward seek renders the update before the target, so the live read was stale.

Individual translate, rotate and scale are folded into the measured transform.

An element named twice in one call is attached once.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* fix(registry): echo-smear resolves a percentage translate against the box

A computed translate of 150% was read as 150px, so count and copy placement were wrong.

Percentages are now a share of the element's own width and height; a scale percentage is a factor.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-20 17:00:43 -07:00
Vance IngallsandClaude Opus 5.5 37a79e9371 fix(registry): clamp filmed-screen's curve and vignette (#5501)
* fix(registry): clamp filmed-screen's curve and vignette

Post-merge review of #5499:

- curve is clamped at 0: a negative value samples outside the frame and
  folds over below about -0.32.
- the vignette factor is clamped at 0: above vignette ~1.11 it went
  negative in the corners.
- README: a "previous look" line with the pre-#5499 values, noting that
  the LED fade-in threshold and the trace of fringing on the focused
  element are hard-coded.

The demo renders the same: at the defaults neither clamp is reached.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(registry): point filmed-screen's catalog page at its recorded preview

The catalog builds this item's payload as unsupported (it needs
canvas-draw-element), and without preview.video the page fell back to the
flag notice: "Needs chrome://flags/#canvas-draw-element to render live"
under a "Recorded preview" caption, with no video, whatever the visitor's
flags. The manifest now names the CDN video and poster the way the
liquid-glass blocks do; the regenerated page carries video= and poster=
instead of needsFlag.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 20:52:18 +00:00
Vance IngallsandClaude Opus 5.5 3886aaec28 feat(registry): filmed-screen curves, orbits and follows typing by default (#5499)
New options, with defaults tuned on a white page filmed the way Vox shoots
screens:

- vignette (1.0, was a fixed 0.42) and aberration (2.2x, with a quarter of
  it kept on the focused text so it still reads as glass)
- curve (0.15): a barrel bend normalised so the frame's corners stay put
- led (0.45): the LED grid now only fades in once a cell spans ~4 output
  px; finer, it aliased into moire on light pages
- tiltTo per shot: the tilt eases between the two, for an orbit
- rack 0.3 (was 0.7), fast enough to follow text as it is typed, and shot
  blur 0.45 (was 0.9)

The demo is now a white search homepage for a made-up engine, "lumen": a
query types letter by letter while the camera follows each new letter and
orbits from one side of the screen to the other, all on defaults.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 13:12:21 -07:00
Vance IngallsandClaude Opus 5.5 071ce8044f feat(registry): add filmed-screen component (#5487)
* feat(registry): add filmed-screen component

Films a live-DOM scene like a camera pointed at a monitor: a tilted screen
plane in perspective, depth of field that follows a named element, chromatic
fringing, LED sub-pixels, per-shot palettes and cut tears. The focused element
stays sharp. Ported from ff-tracking's lens.sksl, with each colour channel
weighted by its own sample in the bokeh average.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(registry): land the filmed-screen catalog poster on the title

The catalog poster is captured at min(3 s, 0.6 × duration), which was the
demo's first cut, so it showed a tear over a blurred title. The first cut
moves to 3.8 s and the first shot racks back to the title before it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(registry): filmed-screen glides between shots and racks focus

Shots now glide into each other by default: camera, palette and colour mode
ease over `glide` seconds, and `transition: "cut"` keeps the hard cut with its
tear. Focus racks instead of snapping: the focus box is a Hann-weighted
average of where it was over the last `rack` seconds, so a new target eases in
on an S-curve and every seek order still renders the same frame.

The demo now films three scenes in the style of other compositions, a code
editor, an analytics card and a chat thread, one shot and grade each.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(registry): filmed-screen shares the composite slot and films each instance

Review on #5487:

- The composite hooks no longer cycle with data-vfx-chain. Both re-arm every
  seek, and core's resolver reads its prior when called, so a stale wrapper
  of ours is re-entered through it. Each wrapper now keeps its own prior and
  returns at once when re-entered, so the chain stays a constant depth. The
  slot is only claimed once a lens is mounted.
- A second instance films its own canvas. mount() takes the snippet's
  caller-scoped query result and picks the first canvas not already filmed;
  the library's own document belongs to whichever copy loaded first.
- No WebGL2 now steps aside like no HTML-in-Canvas. mount() returns
  { destroy() }, a lens whose canvas leaves the page is dropped, and a lens
  whose composition is not on screen skips its capture instead of logging.
- Docs: fadeOut and zIndex, the rack comment, and the description and tags
  now describe glides instead of cuts.

filmed-screen.test.mjs runs both arm orders against core's arming pattern,
two mounts over two canvases, and that the demo pastes the snippet's library.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(registry): filmed-screen test passes oxlint

The vfx stand-in returned `true || prior`, which oxlint rejects as a
constant-truthiness expression.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(registry): match script tags case-insensitively in the filmed-screen test

CodeQL reads the extraction regexps as HTML filters (js/bad-tag-filter).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(registry): filmed-screen waits for the scene's first paint instead of erroring

In Studio's preview the first draw can run before Chrome has painted the
layoutsubtree scene once (the paint wait gives up after 250 ms on a busy
machine). drawElementImage then throws "No cached paint record", which was
logged with console.error and surfaced as a preview console error. That
error is now a wait: the lens requests a paint and draws again when it
lands. Other capture failures still log.

Render output is unchanged: the demo renders byte-identical to before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 11:56:38 -07:00
Miguel Ángel 7b85b28406 fix(ci): keep the catalog publish PR when main changes nothing it generates (#5372)
* fix(ci): keep the catalog publish PR when main changes nothing it generates

The bot compared the whole branch tree with main, so every main push rewrote
the standing PR and dismissed its approval. It now compares only generated
catalog files, with search vectors judged by the drift check's cosine rule.

* fix(ci): republish when main edits generated files or the branch carries others

* docs(registry): motion-blur copies explain their inline styles in three lines

* refactor(ci): split the kept-snapshot check into named conditions
2026-10-09 22:19:38 +00:00
Miguel Ángel b50dead1da feat(registry): gooey split block with metaball goo and directional motion blur (#5123)
* feat(registry): gooey split block with metaball goo and directional motion blur

A dot splits into a three-dot menu through a gooey neck, spins into a
swirl mark under shutter motion blur, breaks back into three dots and
merges into one. Timing, spacing and eases are measured off a reference
recording frame by frame.

The motion-blur snippet gains an opt-in `sharp: 0` option that hides the
crisp frame-time copy while an element moves, leaving only the shutter
average. The default is unchanged; the inlined copies carry the same text.

* feat(registry): grow gooey-split blades out of swelling dots

* refactor(registry): tidy gooey-split pose code and document sharp 0 limits

* fix(registry): hide a sharp 0 motion-blur target by marker, not its visibility

Hiding through the element's own visibility fought timelines that
write it (autoAlpha): a target faded in after attach stayed hidden at
rest. A marker attribute and one !important rule now do the hiding, the
default path writes nothing, and a declarative sharp other than 0 or 1
is refused like any other bad option.

* fix(registry): smear a motion-blur target that was hidden when blur attached

Copies froze the target's computed visibility at attach, so a target
entering with autoAlpha never smeared (and vanished entirely under
sharp 0). The group now carries the target's current visibility each
frame, read with the sharp 0 marker lifted, and a copy keeps a
descendant's visibility only where it differs from its parent's.

* test(registry): pin a copy keeping a descendant's own visibility

* fix(registry): keep a child's own hiding in the smear of a target hidden at attach

* refactor(registry): restore a lifted visibility in one call and lift any hidden form

* fix(registry): restore a lifted visibility even when copying styles throws

* fix(registry): let motion-blur copies hide with a clip that has ended

The smear group set an explicit visibility on every moving frame, and
copies used to freeze an explicit visible, so an ancestor clip hiding
after the last update could not hide them: copies from a 0-4 s
sub-composition stayed on screen at 16.4 s. The group now sets a
visibility only where the element's differs from its parent's.
Also regenerates skills-manifest.json for the motion-blur skill doc.
2026-10-06 21:24:38 +00:00
04acbb4d9e feat(registry): add 4 community components (#4906)
* feat(registry): add cinemagraph — a still photograph with one living region

* feat(registry): add ai-answer-rank — a mock AI answer that re-ranks to put your brand in it

A 1080x1080, 9s block for answer-engine and AI-visibility marketing. A prompt
types into a chat card, the assistant answers with a ranked list of three picks,
then re-ranks: the promoted brand slides into its slot (brandRank 1-3), the picks
below step down, the third fades out of the list, and the brand row lands with
the one accent highlight and a drawn check. Monochrome placeholder UI; prompt,
lead-in, pick names, brand, rank, accent, ink and ground are variables.

* feat(registry): add caret swap text effect

* feat(registry): add pendulum-swing component

Add a motion-primitive component that hangs content from a fixed pivot and
lets it settle with a damped oscillation, like a hanging sign coming to rest
on its hook.

The swing rides a single unitless custom property (--hf-swing-angle) that the
timeline eases 1 -> 0, so only rotation animates and it stays sub-pixel under
the seek-by-frame capture engine. The demo drives it with a damped-oscillation
ease (a cosine inside an exponential decay, normalised to land exactly upright)
so the sign passes through center several times, each pass smaller, before it
rests.

Four enum variables: pivot (top-center/left/right), amplitude
(gentle/standard/wide), direction (right/left), tone (ink/paper/accent, each a
bg/fg pair to keep AA contrast on any frame). The snippet is recipe-only and
does not own its timeline; its demo carries the same variable declaration.

Ran npx hyperframes lint/validate on both files, regenerated registry.json via
scripts/generate-registry-items.ts and the docs via
scripts/generate-catalog-pages.ts.

* fix(registry): drop hand-added cinemagraph index entry; catalog-publish adds it

* style(registry): format pendulum-swing with oxfmt

* fix(registry): keep pendulum-swing demo caption clear of the swinging sign

* fix(registry): ship the ad-template editing contract for ai-answer-rank

---------

Co-authored-by: Carlos Miranda Levy <carlosmirandalevy@gmail.com>
Co-authored-by: tugrawork-creator <319556794+tugrawork-creator@users.noreply.github.com>
Co-authored-by: Akshar Patel <aksharcommit@gmail.com>
Co-authored-by: rottenpen <12029924+rottenpen@users.noreply.github.com>
2026-10-02 17:14:52 +00:00
Miguel Ángel 590c192498 feat(catalog): add 3D objects, cursors, snow, starfield and paper transitions (#4349)
* feat(catalog): shelve 3D objects and cursors in their own catalog sections

Carries the generator grouping rules from #4310 and #4308.

* feat(registry): add a starfield ambient overlay

From #4313.

* feat(registry): add a falling snow ambient overlay

From #4331.

* feat(registry): add a torn-paper-rip transition

From #4321.

* feat(registry): add a paper-collage-reveal transition

From #4322.

* feat(registry): add a curtain-reveal transition

From #4323.

* feat(registry): add 3D objects: product turntable, glass hero, exploded stack, extruded logo

From #4310.

* feat(registry): add eight cursor animations and the dart cursor

From #4308.

* fix(catalog): find items by everyday editor words and accept a bare search word

From #4311.

* chore(registry): remove the simulated-cursor component

The Cursors section's new pointer animations replace it (from #4308).

* fix(registry): take cursor-zoom-follow's screenshot from runtime variables only

The declared default is empty, so page markup never becomes an image URL.

Clears the code-scanning alert raised on #4308.

* docs(cli): show a catalog search example that finds items

"smoke" matches nothing in the catalog; "crossfade" finds fade-through.

* chore(ci): allow the simulated-cursor removal in the deletion guard

* fix(ci): skip tracked files missing from disk in the player CDN pin check

Catalog regeneration removes a deleted item's pages before the publish PR untracks them,

so the check crashed on every catalog deletion PR.

* chore(cli): drop comments that narrate catalog search history

* fix(registry): drag-drop keeps the family arrow and shows the drop slot only while hovering

* fix(registry): agent cursor labels its steps in an accent tag, no glow or sparkles, in 4.8 s

* fix(registry): annotate cursor draws a tapered marker stroke and clears the circled value

* fix(registry): show real file thumbnails and end the context menu demo sooner

* fix(registry): give marquee select real photos, a Finder-blue marquee and a tighter loop

* fix(registry): centre the text-select toolbar on the selection and tighten its timing

* fix(registry): selection cursors default to the macOS system blue

* style(registry): format the agent and annotate cursor sources

* fix(registry): dart-cursor stops covering the choice it makes and shows the export finishing

* fix(registry): frame the product turntable fully and light it with two studio softboxes

* fix(registry): touch-indicator taps like a real phone and the post it taps gets liked

* fix(registry): press-ripple confirms with a check and small click rings at the cursor

* fix(registry): exploded stack separates at once and marks each layer in blue as it is labelled

* fix(registry): extruded logo settles facing the camera, set in Geist Bold with a neutral rim

* fix(registry): magnetic cursor docks its toolbar on the selection and ends after the press

* style(registry): format the exploded stack source

* fix(registry): glyph trail becomes a tutorial cursor path with numbered steps on a settings screen

* fix(registry): make glass-hero a clear lens that shows the headline through it

* fix(registry): snow reads as real snowfall with bokeh flakes over a winter title card

* fix(registry): oversized cursor is a 3.5x arrow that clicks a real editor toggle on

* fix(registry): starfield reads as a drifting night sky over a real title card

* style(registry): format the glass hero source

* fix(registry): make the paper tear show a white fibrous edge and lift the torn sheet away

* fix(registry): curtain reveal draws velvet folds that gather onto a title card

* fix(registry): make every zoom-follow click change the screen and read at zoom

* fix(registry): paper collage pieces stay opaque, tear with deckled grain and lift away on an arc

* refactor(registry): drop dead code and correct stale comments in reworked items

* fix(registry): agent cursor scrubs cleanly, docs match press-ripple, fictional demo names

* docs(registry): catalog notes say oversized-cursor exits and how it differs from press-ripple

* docs(registry): plain lowercase comments in oversized-cursor and touch-indicator

* docs(registry): oversized-cursor names CLICK_AT as the click cue
2026-09-28 22:03:59 +00:00
Miguel Ángel 571c99ce9e fix(sdk): read a composition's variables from its root when its html has none (#4454)
* fix(sdk): read a composition's variables from its root when its html has none

* fix: composition metadata and lint also read variables declared on the root

* fix(sdk): keep root declarations on the root through undo and prefer them like the runtime

* fix(sdk): read variables from both <html> and the root, editing each where declared
2026-09-25 00:14:11 -04:00
Miguel ÁngelandJake Moran 0945de83c7 feat(registry): text-match-cut (#4288)
* feat(registry): add text-match-cut — one text becomes another mid-move on a single 85 % curve

Video primitive reversed from an After Effects tutorial (Editing Snapped, "The Match
Cut Technique Every After Effects User Needs"), measured per frame at 60 fps. Both
texts ride one keyframe pair, cubic-bezier(.85, 0, .15, 1) over 2 s, and hard-swap at
the speed peak (1.0 s). A `mode` variable picks the property that carries the cut:
scale down (the tutorial), scale up, tracking in, or a same-direction slide in four
directions. Optional cyan-to-blue gradient sweep on its own 87 % curve, navy or
transparent background.

* chore(registry): integrate text-match-cut with current main

---------

Co-authored-by: Jake Moran <jakemoran2011@gmail.com>
2026-09-22 12:14:46 -04:00
b22bc2c979 feat(registry): extended-keyframe (#4281)
* feat(registry): add extended-keyframe, the never-fully-lands arrival with motion blur

A hyperframes:component. An element covers most of its move in the first fifth of the
duration and creeps the rest to the last frame (After Effects' extended-last-keyframe
technique), exits along the vector it arrived on, and carries shutter motion blur:
slides via the bundled feOffset filter smear, scale beats via a velocity-driven blur.

  var a = attachExtendedArrival("#word", tl, { at: 0.3, from: { x: -1400 } });
  attachExtendedArrival("#next", tl, { at: a.end, from: { scale: 0.35 } });

Direction and property agnostic (x / y / scale). The ease is exposed on its own as
EK.ease() for any tween, with EK.next() for sequencing and EK.gate() as a numeric check
(single velocity peak, no rises after it, tail above zero to the last frame).

registry/registry.json, docs/docs.json and docs/public/catalog-index.json regenerated
with the two generator scripts; they also pick up drift already present on main.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* feat(registry): publish live extended keyframe payload

* fix(registry): tear down extended keyframe blur

* fix(catalog): refresh extended keyframe payload

* fix(catalog): preserve merged catalog entries

* fix(catalog): regenerate catalog index

* fix(registry): trim extended-keyframe comments

* chore(registry): retrigger checks

* fix(registry): refresh extended-keyframe artifacts

* fix(catalog): refresh extended-keyframe payload

* fix(registry): refresh extended-keyframe artifacts against current main

---------

Co-authored-by: Jake Moran <jakemoran2011@gmail.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 11:41:48 -04:00
01734fae3c feat(registry): colorama-wipe (#4277)
* feat(registry): add colorama-wipe, a colour-mapped feathered wipe reveal for headlines

A hyperframes:component recreating the After Effects "shiny wipe": masked text +
Compound Blur + Colorama over a moving gradient, with a crisp duplicate fading on top.
One call per text element:

  attachColoramaWipe("#headline", tl, { at, in, hold, out, cycle, feather, tilt, blur })

adds a single linear clock tween to the host timeline and paints the effect on two
canvases over the element's box; the element is hidden (visibility) with its layout
kept. All sizes scale from the element's font size. Deterministic: hash noise keyed by
frame, no Math.random, no clocks; seeks outside the window paint an empty stage.

registry/registry.json, docs/docs.json and docs/public/catalog-index.json were
regenerated with the two generator scripts as the contributing guide asks; they also
pick up drift already present on main.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* feat(registry): publish live colorama wipe payload

* style(registry): format colorama wipe demo

* fix(registry): tear down colorama wipe on timeline kill

* fix(catalog): refresh colorama wipe payload

* fix(catalog): preserve merged catalog entries

* fix(catalog): regenerate catalog index

---------

Co-authored-by: Jake Moran <jakemoran2011@gmail.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 03:55:03 -04:00
Miguel Ángel f24ff83834 feat(catalog): add the seven 3D-motion pieces as installable registry blocks (#4056)
* feat(catalog): add the seven 3D-motion pieces as installable registry blocks

Each piece ships as a registry block with a CDN poster and preview, a live
catalog detail page and its source. Scripts travel inside the catalog payload
(shared vendor libraries fetched once, item scripts inline, the one HDR as a
data URI) because the docs host only serves fonts, images and JSON.

NO_ESSAY_COMMENTS_OK: the long comment blocks are in vendored libraries and
the ported frost source under registry/blocks, not hand-written here.
HF_MAX_NONLFS_KB=6000: four hosted texture PNGs under docs/public/catalog/items
are runtime assets the docs host must serve as files.
fallow skipped locally: it audits against origin/main and this branch is
stacked on an older main.

* chore(catalog): regenerate gallery data and nav after rebasing onto main

* fix(catalog): read the install manifest through the bounded project reader

* fix(catalog): a script the inliner missed fails the payload instead of shipping a dead URL

* refactor(catalog): the leftover-script check reuses the reference finder

* fix(catalog): frost and glass textures load from the CDN and the iteration presets are gone

* chore(catalog): regenerate the frost and glass payloads and nav

* fix(catalog): frost and glass textures are hosted through the registry url field, not hardcoded

* chore(catalog): regenerate the frost and glass payloads against the hosted texture URLs

* fix(catalog): the hosted-reference rewrite refuses a bare name it cannot safely replace

* ci(catalog): verify changed catalog payloads in headless Chrome and check the docs catalog

* feat(catalog): every item page follows the shared layout with a Tune panel and tabs

* fix(catalog): let the texture-mask item host its masks so its tile plays live

* chore(catalog): regenerate item pages, gallery data and the texture-mask payload

* chore(catalog): regenerate item pages after merging main

* fix(catalog): declared-only variables get the Tune panel, Code and Docs tabs capped

* feat(catalog): give the blend-difference caption a demo scene so its tile plays live

* chore(catalog): live payload for the blend-difference caption

* perf(catalog): heavy 3d pieces compile and draw before ready, not on the first visible frame

* feat(catalog): the avatar promo card hosts its videos, audio and fonts on the CDN

* feat(catalog): the avatar promo card tile plays live from its CDN-hosted media

* chore(catalog): the avatar promo card plays live, no allowlist left

* chore(catalog): shorten two comment blocks

* chore(catalog): trim first-party comment blocks to the comment-check limits

* chore(catalog): shorten two frost source comments

* fix(catalog): the Docs tab renders as prose again, and the detail page honours reduced motion

Frame-mode pages carry no prose wrapper, and the detail component sat inside
not-prose, so the Docs slot's headings, paragraphs, links and lists had no
styling at all. The slots now carry their own prose classes and the chrome
around them keeps not-prose.

The recorded clip also gets the reduced-motion guard the docs-snippet check
requires, and the generator reads stability from the source metadata type so
the scripts typecheck passes.

* fix(catalog): docs-tab paragraphs render as blocks

Mintlify emits paragraphs as inline spans, so two paragraphs in the Docs slot ran together.

* fix(ci): drop the duplicate docs_catalog job output

git's line-based auto-merge silently duplicated the outputs key when
main and this branch each added it on non-adjacent lines during the
rebase; oxfmt's yaml parser rejects the repeated map key.

* fix(catalog): close four CodeQL alerts in the frost source

* chore(catalog): allowlist the avatar-promo-card CDN asset deletions, format sync-docs-catalog

check-no-main-deletions.mjs was rejecting 11 local mp4/font files this branch
already deleted in favor of CDN hosting; scripts/sync-docs-catalog.mjs had one
line over the 100-col print width from an earlier edit in this branch.

* fix(catalog): detail tabs use docs prose spacing, Install first; previews skip no-WebGPU runners

* fix(catalog): the docs tab scrolls with the page instead of a capped inner box

* refactor(catalog): split the payload asset and verify scripts into single-purpose helpers

* refactor(catalog): split the preview generator into single-purpose helpers

* ci(catalog): fail when the committed catalog differs from generator output

The docs host serves the committed catalog and nothing regenerates it, so a stale
copy shipped silently. The catalog payloads job now generates into a temp dir and
compares it with the committed tree.

* chore(catalog): regenerate docs/public/catalog with the refactored scripts

One full regeneration from an empty output tree with the current scripts. The tree grows
from 58.4 MB to 65.1 MB. Two hosted assets are about 2 MB each (an avatar png and a wav);
the same bytes are already committed under assets/, so HF_MAX_NONLFS_KB is raised for
this commit only.

* fix(catalog): run the catalog script tests under the runner they are written for

The rebase put three node:test files in the vitest half of test:scripts, where they report
"No test suite found". They now sit in the node --test list.

* fix(catalog): a WebGPU item without an adapter shows its recorded clip and says why

* ci(catalog): the drift check runs from a committed fetch mirror with no network

Generation now serves every font and CDN script fetch from scripts/catalog-fetch-mirror and fails
when a fetch is not in it, even if the caller swallowed the error. CATALOG_FETCH_MIRROR=record
refreshes the mirror from the live network. The docs_catalog filter also watches packages/core,
packages/producer and the mirror, and the 13 re-hashed font files are listed in ALLOWED_DELETIONS.

* chore(catalog): clean up the temp font cache and sort the mirror index without a locale

* fix(catalog): hide Tune when the clip stands in, time out the adapter request, share one check

* fix(catalog): the adapter probe survives a sync throw and a hang; test the clip guard

* fix(catalog): keep the adapter probe inside the component, Mintlify evaluates only exports

* fix(catalog): run the detail and page generator tests in CI; no Tune flash while probing WebGPU

* docs(catalog): credit vendored libraries and make each SKILL.md list what the block installs

CREDITS.md names every vendored dependency with its upstream and licence, and flags the two leaf
textures whose origin is still to be confirmed. Each SKILL.md Files table now comes from the block
manifest and its install and render paths point at compositions/<name>/. A test checks that every
path a SKILL.md names is a manifest file that exists.

* fix(catalog): a declared WebGPU item passes verify by its manifest tag, not by console wording

verify-catalog-payloads forgave a missing adapter only by matching the markup and the message, which
library-reported errors and widgets never satisfy. The webgpu tag in the item manifest is now the
owner: a tagged item ignores its page and console errors, an untagged one gets no forgiveness, and a
marker payload with no html is listed instead of crashing the run.

* docs(catalog): correct the credits per block and guard the mount and render paths

CREDITS.md states the three.js version and vendored-or-CDN status per block, adds d3-delaunay,
Clipper on the CDN and the Google Fonts loads, and lists every texture with no known origin.
The SKILL.md test now also checks the mount and render paths against the manifest composition.

* test(catalog): read each block manifest once in the SKILL.md file test

The two checks shared a copied manifest read, which fallow flagged as a clone with a high score.

* fix(catalog): commit the hosted item files a gitignore rule kept out, and check git not the disk

The compositions/ and *-ad/ ignore rules matched folder names under docs/public/catalog/items,
so 33 hosted fonts, textures and marks were generated but never committed and would 404 on
the docs host. The rules now have a negation for that tree and the files are added.

The drift check read the checkout, where git-ignored files still exist, so it passed locally
and failed only in CI. It now unpacks HEAD with git archive and compares that.

* fix(catalog): a tile keeps its poster until the composition has painted

* fix(catalog): a tagged WebGPU item forgives only the adapter-absent error, and test the wiring

The manifest webgpu tag now decides whether an item may lack an adapter, and the error wording
decides which lines are that absence. Any other page or console error on the item, and every
failed request, still fails. The per-item decision is one exported function the tests drive,
marker payloads are parsed by a tested helper, and the SKILL.md variable counts are checked
against their tables (cuboid-carousel said forty-nine for forty).

* fix(catalog): the pinned group's posters load eagerly at high priority

* fix(catalog): type the tile reveal test so the scripts typecheck passes

* fix(catalog): stop the adapter signature matching an unrelated failed adapter request

failed to request .*adapter also matched errors about other adapters. It now matches only a
request for the WebGPU adapter, so a tagged item cannot hide an unrelated failure behind it.

* test(catalog): a tagged item still fails on an error about some other adapter

* feat(catalog): the install command sits above the preview, as on the live catalog

* fix(catalog): find the install slot through Mintlify's child wrapper

* fix(docs): catalog copy buttons keep their size when the label swaps to Copied

* refactor(docs): name the shown copy label once in the catalog copy button

* fix(docs): primary catalog button keeps a readable label on hover and after copy

* fix(docs): inset the catalog tab strip and tab content to match the boxed panels
2026-09-19 16:53:29 -04:00
752b552789 feat(registry): blur an element by marking it, not by calling a function (#4045)
attachMotionBlur(el, tl) has an ordering contract: it has to run after every
tween is defined and before the composition registers its timeline. Getting it
wrong makes the blur silently do nothing, which is the worst shape a failure can
take in a renderer nobody watches. An attribute has no order to get wrong.

data-hf-motion-blur on any element is now the contract. Empty means defaults;
anything else is a JSON object of the same options. The element finds its own
timeline: its nearest [data-composition-id] ancestor names the key, and the
snippet reads window.__timelines[key]. attachMotionBlur stays exported for a
target built after the page has settled.

claim(el) is the only thing that attaches, and two triggers call it. An accessor
on window.__timelines wraps the registry in a Proxy whose set trap sweeps on
every per-key registration, which is what attaches before the renderer captures
frame zero. A bounded poll, 32 ms by 250 ticks, reads the registry directly and
needs no cooperation from whoever wrote it. The poll is not belt and braces: a
registry reference captured before the snippet installs bypasses the trap
outright, and a composition that mounts its DOM after registering its timeline
has no marked element to sweep at trap time. It runs its whole budget every time
rather than stopping the first tick nothing is pending, because on an ordinary
page the top-level target is claimed on the registration write, so an early exit
kills the poll one tick in, before any late composition arrives.

Every refusal is by name, once, and never silent: a value that is not JSON, a
value that parses but is not an options object, an option name that is not one
of the four, an option value that is not a finite number, a target inside
another target, and a second timeline registered under one key. Parsing runs
before the timeline lookup, so a malformed value is reported as malformed rather
than as a composition that never registered.

Two properties of the primitive this change made load-bearing:

- Frame rate is resolved from the target's own composition root, not from the
  first root in the document. The document-wide lookup was survivable while
  every caller could pass fps explicitly; the declarative form has no per-call
  argument. Two compositions on one page at different rates now each get their
  own shutter window, and one call naming both says it cannot.
- An attach is all-or-nothing. The mark, the copies and the resize observer go
  in together and come back out together, so a throw partway through leaves the
  element untouched rather than marked blurred with copies in the page and no
  tracker driving them. The sweep is also bounded per element, because the write
  trap runs it inside the author's own registration statement.

Copies are stripped of data-hf-motion-blur, data-composition-id and data-fps,
on the clone and every descendant: the first would make a copy a target of its
own, and the other two would let a copy win the fps lookup, since copies are
inserted before the original.

skills/hyperframes-animation/references/motion-blur.md is the agent-facing half,
mirroring how colour grading is surfaced. It leads with when NOT to blur,
because the attribute makes blurring everything trivial and a smear on motion
that was never fast enough reads as a soft, cheap render.

Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com>
Co-authored-by: Vance Ingalls <vance@heygen.com>
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-09-18 22:55:15 -07:00
miga-heygenandMiguel Ángel 82f49262ec feat(registry): blur every transform driver, and ship the reference case as shutter-slam (#4038)
The motion-blur snippet integrated translation only. Its output stage was one
SVG filter per target whose copies were feOffset, and feOffset only translates,
so a beat that only scaled or only turned sampled a displacement of zero and
rendered sharp. No SVG filter primitive can give a copy its own affine
transform, so the sampling loop was never the problem: the output stage was.

Copies are now DOM clones of the element in an isolation:isolate group, each at
1/N opacity with mix-blend-mode plus-lighter, each carrying the element's
resolved transform at its sample time. plus-lighter adds premultiplied colour,
so N + 1 copies at 1/N sum to the average the shutter integral asks for; the
isolation is load-bearing rather than tidy, because without a transparent
backdrop of its own the first copy adds onto the page and blows a light
background out to white. Reading the resolved matrix instead of a list of named
GSAP properties is what makes every driver work at once: translation, scale,
rotation, 3D rotation and skew arrive in the same one value.

Measured against the After Effects export the shutter model was fitted to, per
beat, previous stage to this one: scale +0.62 dB, rotate X +0.43, rotate Y
+0.84, rotate Z +1.15, translate X -0.18, translate Y -0.04. The two small
translate regressions are inherent and not tunable: the filter offset one
rasterisation so every copy was pixel-identical, where each copy is now
rasterised independently at its own fractional transform with its own subpixel
antialiasing.

Three consequences of the new stage, each carrying its own fix:

- mix-blend-mode flattens preserve-3d, so a copy cannot inherit the parent's 3D
  context. Each copy carries the parent's perspective as its own first transform
  function instead.
- A copy is styled by nothing that selected the original, since dropping the id
  also drops every #id rule that gave it size, colour and font. Each copy
  carries its own resolved style inline, read once and replayed. Those values
  are px, so a container-relative element needs them again when its box changes:
  a ResizeObserver re-snapshots rather than paying a subtree walk every frame.
- A beat that moved and faded at once kept a full-strength smear behind a
  vanishing element. The element's opacity now rides the group; the 1/N weight
  stays on the copies.

The axis option is gone. It selected which of x and y to sample, a question the
resolved matrix does not have.

shutter-slam is the new component: one word through six single-property beats,
two translate slams, a scale punch, then a full turn about each axis, which
exercises every driver the model covers in one mount. Easing is power2.inOut
because it measured best, not because it was the default. A bezier fitted to the
reference's own bbox centres looks like the better curve and renders worse: a
blurred bbox centre is the shutter window's average rather than the instant, so
on an accelerating segment the fit measures its own blur.

Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com>
2026-09-17 16:03:14 -04:00
miga-heygenandMiguel Ángel 9aa60a844f fix(registry): make motion-blur match After Effects shutter-based blur (#4008)
* fix(registry): make motion-blur match After Effects shutter-based blur

The motion-blur catalog snippet estimated speed from the x/y delta
between consecutive timeline updates and drew three one-sided ghost
copies with decaying alpha plus a Gaussian on top. After Effects does
temporal supersampling: each frame integrates the layer over a shutter
window (shutter angle, shutter phase, samples per frame), producing a
symmetric box smear of length speed x shutter time.

The snippet now does the same inside the page. After every timeline
update a deferred pass seeks the GSAP timeline to N sub-frame times
across the shutter window (events suppressed), reads each target's x/y,
restores the frame time, and drives an SVG filter that averages N
feOffset copies with equal weight (feComposite arithmetic, 1/N, sRGB).
Defaults follow AE: shutterAngle 180, shutterPhase -90, samplesPerFrame
16; fps comes from the render config, then the root data-fps, then 30.
Displacements are mapped through the inverse of the element's own
scale/rotation; percent x/y values are resolved to px; SVG targets use
their bbox; an existing inline filter is kept and the blur appended to
it. The pass runs in a microtask rather than inside the tracker tween's
onUpdate, because seeking GSAP from within its own render reads stale
tween state and suppresses the composition's callbacks on forward
seeks. The heuristic options blurScale/blurMax/stretchScale/stretchMax
are removed.

Reference: an AE-algorithm reconstruction of the same composition
rendered without the component at an effective 960 fps and averaged
over the 16 sub-frames of the 180 degree window per output frame.
Against it (1920x1080, 30 fps, solid + text translating linear then
power2.inOut): old mean PSNR 53.2 dB (min 26.7), SSIM 0.9930; new mean
PSNR 72.4 dB (min 52.6), SSIM 0.9998. The 10-90 edge ramp at 66.7
px/frame is 26.8 px in the reference, 27.5 px new, 14.5/6.3 px
(one-sided) old; at the ease peak 79.3 vs 79.5 vs 7.9/17.7 px, with
smear centres matching to 0.1 px.

Co-Authored-By: Miguel Ángel <miguel.sierra@heygen.com>

* fix(registry): drop render-config fps probe from motion-blur and refresh its docs

The snippet resolved fps by reconstructing the renderer's private seek
config key so the compiler's inline-script guard would not strip it. A
public catalog snippet must not depend on that key, so fps is now the
explicit option, then the root data-fps, then 30; renders with an fps
override pass fps explicitly. The catalog page's Usage, How it works and
Options sections still described the old velocity heuristic and are
rewritten for the shutter model. The 1/N weight keeps full precision.

Co-Authored-By: Miguel Ángel <miguel.sierra@heygen.com>

* fix(registry): match the After Effects reference export's shutter window

The shutter model landed on this branch was built against a reconstruction
of After Effects' algorithm, not against After Effects. A real 1920x1080 /
30 fps export of translating text disagrees with it on three points, all
measurable off the export itself.

The window is two frames wide, not half a frame. On the measured frame the
outermost duplicate on the trailing side sits 128 px back while the previous
frame is 125.8 px back, and the outermost leading duplicate sits 189 px ahead
while the next frame is 184.9 px ahead: the window ends land on the
neighbouring frames. Defaults become shutterAngle 720, shutterPhase -360.

Duplicates sit on the sub-interval boundaries, both ends included, not at
sub-interval centres. The staircase across a stroke steps every 15.6 px on a
125.8 px displacement and every 23.0 px on a 184.9 px one, i.e. an eighth of
a frame, with the run closing exactly on the neighbouring frame's position.
samplesPerFrame now means sub-intervals of the window, so there are N+1
copies at k/N rather than N copies at (k+0.5)/N.

The frame-time instance is drawn sharp over the smear. At full ink the core
bounding box is the whole word at every frame and total ink rises to 1.44x
the unblurred frame's, neither of which an average can produce. A final
feComposite over of SourceGraphic reproduces it. Each duplicate stays at
1/N: the staircase plateaus step by 0.063 +- 0.002 of the sharp text
intensity, flat, with no taper toward the window edges, and triangle
weighting scores 1.6 dB worse against the export.

Verified by replaying the export's translating section as a composition
whose sprite is the export's own rest-frame ink and whose eight legs follow
its own measured per-frame positions, rendered at 1920x1080 / 30 fps and
compared frame by frame over the text band. Mean PSNR against the export
rises from 24.19 dB (min 17.07) to 32.51 dB (min 27.57) across 72 frames,
and from 19.83 dB (min 17.07) to 32.03 dB (min 27.57) across the 26 frames
that actually move. Ghost extents match to a few px and the rendered
staircase plateaus match the export's to two decimal places. The export's
later scale, skew, flip and 3D rotation sections are out of reach here:
integrating anything but translation inside the shutter is engine work
tracked in issue #4010.

The filter region now clamps its bounds through zero. It was derived from
the duplicate offsets alone, which was correct while the output was nothing
but duplicates; the sharp instance draws at offset zero, so a window sitting
entirely on one side of the frame time - any shutterPhase above 0 or below
-shutterAngle - produced a region starting inside the element's own box and
cut the instance off. The defaults straddle the frame time and were never
affected, so the measured numbers above are unchanged.

Co-Authored-By: Miguel Ángel <miguel.sierra@heygen.com>

---------

Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com>
2026-09-17 00:51:03 +00:00
f498be94c2 feat(registry): add kinetic center build component (#3269)
* feat(registry): add kinetic center build component

* chore: regenerate search vectors for kinetic-center-build

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(registry): load all kinetic center build font weights

* chore(catalog): regenerate local search vectors

---------

Co-authored-by: miga-heygen <miguel.sierra_miga@heygen.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com>
2026-09-01 23:07:55 -04:00
Akshar Patel 305de15432 fix(catalog): sync tab labels with the sliding indicator (#3267)
* fix(catalog): sync tab labels with the sliding indicator

* fix(catalog): preserve tab contrast during indicator motion
2026-09-01 20:40:11 -04:00
5b45bcc16d feat(registry): hw write-on wave — hw-write-title block + control surfaces for the handwritten family (#3557)
* feat(registry): hw write-on wave — hw-write-title block + control surfaces for four hw components

Adds hw-write-title (true glyph write-on: pen-traced Caveat via baked
centerline masks, curvature-adaptive pen velocity, pen lifts, underline)
and grows the handwritten family's four components with declared control
surfaces (controls per the #3227 convention), the completed stroke-texture
matrix (sharp + deterministic seeded spray — no feTurbulence), boil poses,
and physically-derived arrival deformation (travel-aligned squash with
volume preserved and spring recovery). Shipped single-path callers keep
working unchanged (legacy helper bodies preserved; proven in a legacy
wiring harness).

Validated in one reference build: check clean, double-render framemd5
910/910 bit-identical, seek-shuffle 8/8, WCAG 2.3.1 flash-scan zero
violations, physics burn-ins hand-recomputed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(catalog): index hw-write-title for meaning search

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com>
2026-08-30 23:46:39 -04:00
Miguel Ángel d4765512df fix(catalog): render the Matrix Decode docs preview; remove Checkout Flow (#3396)
* fix(catalog): reveal Matrix Decode captions with autoAlpha so the docs preview renders

The demo and snippet flipped word spans with zero-duration display sets,
which the seek-driven docs player never applies after its style restore:
the composition played 8s of black. autoAlpha reveals with the scrambles
as same-length absolute overlays follow the keyframes contract (never
tween display) and survive seeks and loop wraps. Scramble text now
matches each word's length so it decodes in place instead of jumping.

* chore(registry): remove the Checkout Flow component

Owner-directed removal of the checkout-flow catalog item: source,
demo, generated docs page and payload, and its entries in the registry
manifest, catalog index, docs nav, and search vectors. The deletions
are allowlisted in check-no-main-deletions.
2026-08-21 15:14:11 -04:00
Miguel Ángel 406bf316a3 fix(catalog): make component previews answer their variables panel (#3323)
* fix(catalog): make component previews answer their variables panel

Every control on a component's catalog page did nothing. Asking
caption-camera-follow for a violet accent rendered gold, and so did green
and blue, on 166 of the 168 components that declare variables.

A component ships a snippet, which is what the page hands you to paste and
which carries the declaration plus the script that turns a chosen value
into a CSS custom property, and a demo.html which stages and animates it.
The preview is built from the demo, and the demo was authored as a copy of
the snippet rather than a reference to it. The copies drifted until almost
none of them carried the declaration or the reader, so the payload for that
page never contained the word violet at all.

Components come in two shapes, so the repair does too.

123 ship a snippet that registers its own paused timeline. That snippet is
a whole piece, so their preview is now built from it and carries markup,
variables and motion together.

45 are markup plus a commented recipe, where the demo owns the motion.
Those demos now carry the snippet's declaration, reader and var-driven CSS
in the registry itself, written by scripts/catalog/sync-demo-variables.ts.
Nothing is patched in at build time.

A test runs that tool in dry mode and fails when a demo has drifted again,
naming the command that repairs it. It also asserts it inspected more than
a hundred components, because a check that silently matches nothing is how
this rotted in the first place.

Measured by rendering every payload in a real player rather than by reading
markup: payloads declaring their variables go from 2 of 168 to 168 of 168,
previews that animate go from 166 to 167, and nothing that moved stopped
moving. ascii-render-pass and star-rating-fill render a still frame when
built from their snippet, so they keep the demo path as a recorded
exception and stay in the state they were already in.

* refactor(catalog): give the preview pipeline one lookup and one entrypoint guard

Follow-up on the same branch, no behaviour change: 42 tests still pass and
`sync-demo-variables --check` still reports all 168 components clean.

The payload generator and the demo sync had each grown their own copy of
"given a component directory, find the snippet and the demo". Both now call
`componentFiles`, which is the same duplication-by-copying that broke the
previews in the first place.

Both catalog generators also carried a byte-identical 12-line guard for
"only run main() when this file is the entrypoint". That clone was already
in the tree, but nothing had touched both files at once before, so it had
never surfaced. It is now `runAsCommand`, and the sync script's variant of
the same condition is `isEntrypoint`.

The rest is flattening: the layering guards read as a table of conditions
instead of a chain, the reporting splits by what it reports, and the entry
resolution comes out of `buildPayload` rather than being spliced into it.

Also runs the formatter over the demos this branch rewrote. Whitespace only,
and `notes-typing` is the only component demo that renders pre-formatted
text, which this does not touch.
2026-08-18 01:37:46 -04:00
Miguel Ángel ed18f1ea9a chore(catalog): remove two AI UI items (#3317) 2026-08-17 18:32:22 -04:00
Miguel Ángel 532caf7aa2 chore(catalog): remove internal source markers 2026-08-13 21:10:12 -04:00
Akshar Patel cbd38b32f7 fix(catalog): restore the caption texture preview (#3258) 2026-08-13 00:38:47 -04:00
Akshar Patel c71fab322e fix(catalog): center the menu morph preview (#3256) 2026-08-12 23:55:29 -04:00
Akshar Patel a2109a8633 fix(catalog): keep the text stagger preview inside the frame (#3255) 2026-08-12 23:55:25 -04:00
Akshar Patel 158f15e8f0 fix(catalog): keep the animated bar chart preview in frame (#3254) 2026-08-12 23:55:21 -04:00
Akshar Patel dc01a12d71 fix(catalog): keep the typewriter preview inside the frame (#3249) 2026-08-12 23:49:13 -04:00
WaterrrForever 4347af3fe1 feat(registry): add five vertical ad-template blocks and their motion primitives (#3217)
* feat(registry): add the notification-cascade block and banner-push-in component

A vertical ad-template block: phone notifications land one after another on a
2.000s beat and stack up the screen, each banner pushing the pile down as it
arrives, before the whole stack lifts away into a closing card carrying the
HyperFrames lockup. Every decisive number - the 214px pitch, the 22px entry
rise, the expo.out restack - is measured from reference footage rather than
invented. The backdrop plate is a generated photograph, shipped with the item.

The banner-push-in component carries the reusable arrival move on its own:
one banner fades in and rises into place while everything already on screen
slides up one pitch in lockstep.

Both pass the full browser gate (runtime, layout, motion, WCAG AA contrast)
installed into a scratch project from a served registry.

* feat(registry): add the share-sheet-carousel block and sheet-spring-up component

A share sheet springs up from the bottom of the phone and cycles photographic
previews inside its card on a fast cut rhythm - fourteen cuts over 7.2s -
while the blurred ground swaps behind it on the same beats, ending on an
accept tap. A brand strip with the HyperFrames lockup rides the bottom of the
preview, as the reference's did. The four stills are generated photographs,
shipped with the item.

The sheet-spring-up component carries the arrival on its own: a real spring
sampled frame by frame from reference footage - up from below, 192px
overshoot, 0.72s settle - shipped as the sampled table rather than an ease
that approximates it.

Both pass the full browser gate (WCAG AA) installed from a served registry.

* feat(registry): add the typing-indicator and streaming-text components

Two chat primitives extracted from reference footage of real conversations.

typing-indicator is the three-dot bubble that says someone is writing: it
pops in at the thread's edge on the measured entry (scale 0.6 to 1, alpha
0.4 to 1, 0.10s power2.out), holds, and vanishes when the message lands. An
optional bounded dot wave is included for compositions that want the dots to
breathe.

streaming-text is not a typewriter: words arrive in the uneven bursts of a
streamed AI answer, on a token rhythm measured word-by-word from the
reference, each word landing grey and inking to full colour over 0.27s. The
GAPS table ships in the integration note and cycles for any word count.

Both demos pass the browser gate.

* feat(registry): add the ai-chat-reveal block

A question is typed on a rising keyboard - measured keystroke rhythm, steady
caret - sent, and answered by an AI that streams its reply word by word:
thinking dot on the measured alpha ramp, tokens arriving in uneven bursts,
a bulleted list, then a cut to a branded closing card.

The typing and streaming schedules are generated at load from the measured
gap tables, so every piece of copy is a variable - question, three answer
paragraphs, three bullets, and the whole closing card - while the rhythm
stays the reference's. Kept at the native 750x1624 capture size so the
measured keyboard geometry stays pixel-true.

Passes the full browser gate (145/145 WCAG AA) installed from a served
registry.

* feat(registry): add the message-thread-reveal block

A full text conversation plays out on a phone: bubbles popping in on their
measured entries, typing indicators holding the beat, a shared link card,
an emoji burst, delivered receipts, the composer typing five messages
keystroke by keystroke - and the whole thread scrolling up beat by beat on
the measured y-choreography until a warm closing card lands.

Every message swap keeps the measured keystroke tables intact: the new copy
is length-matched character for character to the reference's typed lines, so
all 97 typed frames land on their original beats. The footage plate is
replaced by a CSS room-and-phone-body, the link card carries a generated
still, and the closing card is rebuilt around the HyperFrames lockup with
its stars, proof line, and feature icons preserved.

Relates to the thread-message-stack work in #3046: that PR ships a
caller-payload message-stack component; this block is the complete
ad-template film around the same subject. Kept as its own commit so either
can move independently.

Passes the full browser gate (65/65 WCAG AA) installed from a served
registry.

* feat(registry): add the notes-reveal block and line-reveal-list component

A personal note is typed character by character in a notes app - the caret
riding every letter on the measured keystroke frames, the page scrolling up
on the measured settle curves as each line lands - then cuts to a paper card
with a marker headline, a circled word, a ticked checklist, and the video
card. New copy is length-matched line by line to the measured reveal table,
so all 176 keystrokes land on their original frames. The two hand-drawn
faces (Permanent Marker, Courier Prime - both OFL) ship with the item; the
reference's proprietary UI face is replaced by the system stack.

The line-reveal-list component carries the paragraph rhythm on its own:
lines land on the measured writing cadence that slows as the thought gets
heavier - 2.20s, 2.03s, 2.97s, 3.10s, 3.33s, 3.63s - and the page makes room
with the measured scroll-settle table instead of a linear glide.

Both pass the browser gate (366/366 WCAG AA for the block) installed from a
served registry.

* docs(catalog): publish the ten new ad-template items

Regenerated pages, payloads, navigation and the flat index for the five
blocks and five components added in the preceding commits. Every existing
page is untouched: the diff is ten new item pages, their payload assets, and
the two regenerated indexes.

* chore(registry): rebuild the on-device search index for the new items

* style(registry): run oxfmt over the new item sources

The repo-wide format gate covers registry HTML; the staged-file hook did
not reach these. Payloads and pages regenerated to match.

* feat(registry): restage the five component demos as device specimens

The first demos dropped each primitive onto a bare canvas, which stripped
the context the moves were measured in - a dark bubble on a black frame, a
sheet floating in empty gradient. Each demo now plays inside a phone on a
studio ground, in the move's native habitat - lock screen, chat thread,
share sheet, assistant answer, notes page - with a specimen caption naming
the component and its measured numbers. Interiors are authored at 1080x1920
and scaled into the screen, so they reuse the blocks' gate-proven geometry.
Timelines are unchanged. All five pass the browser gate.

* refactor(registry): shelve the five ad-template films as showcases

The video-primitive tag pins an item to the Motion Primitives shelf and
overrides every other grouping rule - right for an atomic move, wrong for a
25-second film. The five ad-template blocks move to the Showcases shelf
(Scenes & demos), where complete scenes live; the primitives shelf keeps
only the moves.

* refactor(registry): retire banner-push-in and line-reveal-list

Reviewed against the shelf they would join and cut as duplicates.
banner-push-in was the thinnest cut against the existing notification
family - notification-stack already owns the arrival-plus-lockstep-shift
move, mirrored. line-reveal-list was pinched from both sides: the existing
line-by-line-slide owns generic staggered lines, and the incoming
notes-typing scene carries the measured writing cadence and scroll-settle
at scene level. Their measured tables live on inside the films they were
extracted from.

* feat(registry): add four scene primitives - the moments, mountable

social-proof-card: the app-store close as one scene - wordmark, five stars,
proof line, three feature icons, CTA capsule - in a quiet stagger. Composes
with, not against, star-rating-fill / testimonial-proof-card / cta-lockup,
which stay the atomic owners of their moves.

marker-checklist-card: a hand-lettered paper card - marker headline, an
ellipse that draws itself around one word, checklist rows whose checks draw
on. The paper-and-marker sibling of mk-specs-list.

chat-thread: a conversation as a scene - measured bubble pop-ins, expo
lockstep shifts, typing dots holding the beat, a media placeholder. Message
content is one variable: heights are measured at mount and the choreography
is generated to fit. Relates to #3046's caller-payload stack; kept
independent.

notes-typing: the notes-app confession - lines typed on the measured human
keystroke rhythm, the caret riding every landed character. The scene
counterpart to typed-prompt's bare prompt line.

All four follow notification-stack's mountable-scene pattern: container
units, theme-contract tokens, one paused timeline registered under the
literal id. All demos pass the browser gate.

* docs(catalog): regenerate for the reshelved films and scene primitives

Ten pages become twelve: the five films move to the Showcases shelf, the
two retired components' pages are gone, and the four scene primitives get
pages under Motion Scenes. Every untouched page is byte-identical.

* feat(registry): bring chat-thread to film fidelity, with a per-message type switch

The scene now carries everything the reference conversation carries: status
bar, back badge, avatar and name pill, bubbles with real iMessage tails, a
Delivered receipt under the last sent bubble, large emoji rows, a media
placeholder, and a link card with title, domain and chevron.

Every message picks its type by prefix, the way marker-highlight picks a
style: recv:/sent: text bubbles, emoji: a large row, img a media
placeholder, card:Title~domain a link card. Heights are still measured at
mount and the choreography generated, so any mix of types fits.

* feat(registry): add chat-message - the atom, with its type as an enum

ONE message arrives on the measured entry, growing from its sender's
corner. The type is a proper enum - Received, Sent, Emoji, Image, Link
card - switched on the catalog page the way marker-highlight switches its
style, with the bubble tail and the Delivered receipt as their own
switches. chat-thread remains the conversation-as-scene built from
arrivals like this one; the pipe grammar belongs at that level, the enum
belongs here. Demo cycles all five types, one at a time, with a type chip
naming each.

* fix(registry): neutral composer placeholder, gsap 3.14.2 pin, exact font licences

Review fixes for #3217:
- Rendered composer placeholder "iMessage" -> "Message" in chat-thread,
  its demo, and message-thread-reveal (technique comments stay as provenance)
- notes-reveal now loads gsap@3.14.2 like every other item
- Licence wording names each face: Courier Prime OFL 1.1,
  Permanent Marker Apache-2.0 (verified against embedded name-table records)

Mirrored catalog payloads and pages regenerated for the four touched items.
2026-08-11 16:03:42 -04:00
Miguel Ángel 9734578e60 feat(registry): bring back the video-primitive moves (#3169)
Restores the 208 catalog items reverted after their previews 404'd in
production, this time on the payload mechanism rather than the .html files
that caused the outage.

The generator no longer writes a preview document to docs/public. That writer,
and the machinery under it, existed only to produce files the docs host
discards, so it is gone rather than bypassed. Items now embed the composition
itself via a payload, which is what the previous change already does for the
items that were already in the catalog.

The variables explorer is parked, not restored: it drove its preview through
the same unpublished .html path, so it would have shown an empty frame. Items
that declare variables get the live player plus the static variables table, and
reconnecting the explorer to payloads is a follow-up.
2026-08-10 18:46:03 -04:00
Miguel Ángel c86d4013f5 Revert "feat(registry): the video-primitive moves, documented and customisable (#3090)" (#3162)
This reverts commit 3b53bfd2f7.
2026-08-10 14:47:47 -04:00
Miguel Ángel 3b53bfd2f7 feat(registry): the video-primitive moves, documented and customisable (#3090)
* feat(registry): add the video-primitive moves, and rebuild the catalog around them

Adds the motion primitives: 277 new components and the blocks that go with
them, plus the ui-primitives, themes and generators they are produced by. The
registry index goes from 176 items to 454, and the search catalog is rebuilt so
the set that is ranked is the set that can be installed.

Additive on purpose. An earlier pass of this port used rsync --delete, which
removed 101 files that exist on main because the incoming set is not a superset
of the current one: beat-freeze-cut and camcorder-hud among them. Whether the
re-port replaces those or sits alongside them is a product decision and not one
a sync flag should make, so nothing is removed here. If any of them are meant
to go, that belongs in its own commit where it can be seen.

The generator is ported too. Main's version only scans examples, so running it
without this change silently rewrote the index down to nine items. It also
rewrites example manifests from templates.json and will overwrite hand-edits;
those were reverted here after each run, and the diff is worth reading rather
than trusting.

Not covered. The 445 moves are not individually reviewed in this commit; the
machinery that ranks and installs them landed separately so it could be read on
its own. The internal evaluation corpus is deliberately absent: it is 1,400
files of briefs, gold labels and verdicts, and this repository is public.

* docs(catalog): publish the primitive and component pages

Adds the Mintlify pages for the moves this PR ships: 163 component pages, 13
primitive pages, and the navigation that lists them. Without these the moves
land installable and undocumented, which is the worse half of a catalog.

Three things left out deliberately.

The 78 MB of docs/public. Nothing references it: every page loads its preview
from static.heygen.ai, so those bytes would be weight in a public repo with no
reader. Checked rather than assumed, by grepping the pages for the path.

Pages for the thirteen moves that were specified and never built. They had
documentation but no registry item, so a reader would have followed a page to a
`hyperframes add` that fails. Their nav entries are pruned with them, and every
one of the 309 remaining catalog and primitive nav entries was verified to point
at a page that exists.

Spike and scratch files that sit alongside the real docs on the source branch:
qa-gallery.html, experiment pages, bundled player javascript. They are working
artifacts, not documentation.

Not covered: the pages are generated output and have not been read individually.
The nav is verified to resolve, and the previews load from a CDN this commit
does not control, so a broken image would show up in review rather than here.

* docs: list the primitive and component pages in the site navigation

The pages this PR adds were unreachable: nothing in docs.json pointed at them.
This appends a Motion primitives group to the existing Catalog tab and a
Primitives tab, both built from main's navigation rather than replacing it.

Copying the source branch's docs.json wholesale was the first attempt and was
wrong. That file describes a different site, tabs Documentation / Catalog /
Primitives / Packages / SDK / Reference against main's Guides / Studio /
Catalog / Developers, and it references pages only that branch has, so the
preview server reported six dead links.

Verified by running the preview and resolving every entry: 484 page refs, 0
dead, no warnings. Group-relative refs are why a flat existence check is the
wrong validator here: cursor resolves through catalog/components and mcp
through guides, so checking docs/<ref>.mdx flat pruned 22 entries that were
fine.

* fix(registry): restore what the port took from main's components

Two regressions this branch introduced into items main already ships. Both were
found by the repo's own gates in packages/cli, not by reading the diff, and
neither is visible to the no-deletions check: no file was deleted, the contents
of files were changed.

The four liquid-glass blocks stopped installing their library. main lists
lib/liquid-glass.iife.js as a second file on each; the port wrote the older
manifest over main's and dropped that entry. The file is still in git and still
on disk, it simply stopped being something `hyperframes add` writes, so the
installed composition's <script src="lib/liquid-glass.iife.js"> would have
resolved to nothing. Every one of the 294 registry-item.json files this branch
touches was then audited against main: these four lost a file entry, and no
item lost a top-level key.

Fourteen caption components gained an empty <video>. The port added
`<video id="wp-video" ...></video>` — no src, no <source> — to each component
and its demo. It renders nothing and the registry linter rejects it as
media_missing_src. Removed rather than given a placeholder, because main's
version of each of these composes over whatever the host composition provides,
so the element only ever added a broken node; a made-up src would ship a
reference to footage that does not exist.

The removal is deliberately surgical. Four of the fourteen also carry a
substantial rewrite from the port, and only the media element and the rule that
styled it are touched, so a blunt revert cannot take the rewrite with it.

Verified: 2540 CLI tests pass, `bun run lint` exits 0. Before this, three tests
failed.

* docs(catalog): play the real composition, and show what can be changed

Four changes to generate-catalog-pages.ts, so all 445 pages stay consistent
rather than 445 files being edited by hand.

The preview plays the composition instead of pointing at a video. Every new
page pointed at static.heygen.ai/<name>.mp4 and every one of those answered
403, so the reader got a black box where the whole point of the page is to
show them the thing. The objects were never uploaded and rendering 445 of them
would have to happen again on every change. The player is already the thing
being documented, so the page embeds it: the item's directory is copied under
docs/public and an iframe loads it through a small wrapper. 444 of 445 pages
play; the remaining one is a texture item that uses its own preview panel.

The iframe is not decoration. Compositions set styles on `body`, so dropping
the element straight into the MDX would put a composition's global CSS in the
same document as the documentation around it.

Three things this got wrong first, all found by opening the page rather than
reading the output:

  - The wrapper loaded itself. `../<dir>/<name>.html` from inside preview/<dir>/
    resolves back into preview/<dir>/. The player embedded the player and the
    frame went black with a second set of controls shrinking into the corner.
  - Copying only demo.html was not enough. Most demos are a mount shell whose
    child carries data-composition-src="./<name>.html", so the sibling has to
    come with it. Every URL answered 200 and the frame was still empty.
  - `autoplay` and `loop` are not player attributes. Writing them did nothing
    and every preview sat paused on frame 0 — which is blank for any
    composition that animates in. The wrapper drives play() and loops on
    `ended` instead.

The Variables table. generateParams reads `params`; every item ported from the
video-primitives work declares `variables`, a richer schema with a type, a
default and a range. 112 items carry one and not a single page showed it, so
the most useful thing on the page was the one thing missing.

Nav groups. `if (entry.type === "component") return "Effects"` was the
catch-all, so Effects held 267 of 445 pages: an alphabetical wall. Rules keyed
on tags that already exist in the manifests split it; the largest group is now
73.

An install command with a visible copy button. A plain code fence renders one
on hover only, and it was absent from the accessibility tree entirely. This is
the one line every reader comes to take. navigator.clipboard is unavailable on
insecure origins, which is exactly the local preview these pages are written
against, so the fallback path is load-bearing and is what was exercised in
testing.

Verified: 888 preview URLs fetched, 0 failures. Regenerating three times in a
row produces no change, after a first attempt where "Variables" was added to
GENERATED_HEADINGS with a capital V — the set is compared lowercased, so each
run carried the previous section forward and appended a new one.

Not covered: the 445 pages were not read individually. Coverage here is that
every preview resolves and that a page from each of the block and component
paths was opened and watched.

* docs(catalog): put the code on the page

A reviewer with no stake in the work compared these pages against shadcn/ui's
component pages and motion.dev, and returned one gap: the pages carry almost no
code, so they are pointers to a file the reader does not have yet. Its sharpest
example was the Variables table — names, defaults and accepted values, headed
"set the ones you want to change on the element", on a page that never shows an
element or the syntax for setting anything on one.

Two additions, in the generator so all 445 pages get them.

A snippet under the Variables table: the real mount element with
data-variable-values filled in from the item's own defaults, so it is
copy-and-run correct before it is edited. That is the syntax the demos actually
use, not an illustration written for the page.

The item's source, in a collapsed Accordion. These files run 99 to 463 lines,
so inlining them raw would bury everything else; collapsed, the code is on the
page and one click away. Accordion is already what these docs use for this.

A second reviewer, fresh, confirmed the change landed: it called the table and
snippet actionable rather than filler and said the collapsed source earns its
place.

Also here: the preview retries play() until the clock moves. `ready` can flip
before the runtime the player injects for a mounted sub-composition has finished
wiring up, and a play() landing in that window silently does nothing.

Both reviewers additionally reported every preview frozen at 0:00 and called it
fatal. It is not. The player's clock runs on requestAnimationFrame
(direct-timeline-clock.ts), browsers suspend rAF in a hidden tab, and the
reviewing tab was hidden: document.visibilityState read "hidden" while the
player reported ready and not paused, and a one-second rAF loop never completed
a single tick. Seeking the same composition by hand renders it correctly at any
offset. So the retry stops after ~15s instead of spinning forever, and the
comment says why an automated check of a background tab will always read 0.

Not covered: the reviewers' other standing finding, that only some items carry
variables at all, so the pages do not have one shape. 125 of the 206 items
tagged as a primitive declare none, and giving them variables means authoring
them into each composition, not editing metadata.

Verified: lint exits 0, the no-deletions gate passes, nav resolves 598 refs with
0 dead, and regenerating three times running changes nothing.

* feat(registry): give 55 primitives variables that actually do something

The catalog pages listed variables for 112 of 454 items and nothing for the
rest, so most pages could show a reader what a piece looks like but not what
they could change about it. This adds them to 55 more, taking the count to 167.

These are not metadata. A variable is only real if the composition reads it, so
each one is declared on the root, validated in the composition's own script, and
wired to something visible: travel distance, blur radius, direction, density,
accent family, tone, label text. Declaring a knob the code ignores would put a
table in the published docs that lies about the piece, which is worse than
having no table.

Every one falls back to its declared default when the incoming value is missing
or unrecognised, so a bad override degrades to the shipped look rather than to a
broken frame. With no overrides at all, each item renders exactly as it did
before: that was checked per item against `git show HEAD:` in a real browser,
comparing computed styles rather than eyeballing.

Four things this ran into that are worth writing down.

An apostrophe anywhere in a description terminates the single-quoted
data-composition-variables attribute and breaks the HTML parse. Every
declaration in the registry now parses; that is checked, not assumed.

Where a timeline drives GSAP's own y/scale/filter, GSAP writes inline styles
that beat any CSS custom property, so those knobs cannot be won from the
composition. Most of these items keep their motion in a user-owned "Timeline
integration" comment rather than in code, so no timing variables were declared
for them at all. A direction knob on a wipe can still be wired honestly, by
remapping clip-path inset sides through multipliers whose defaults reproduce the
original exactly.

Colour tokens that only reach a :focus-visible outline, or an element sitting at
opacity 0 at rest, render identically in a video. Those were skipped rather than
shipped as knobs that appear to do nothing.

CSS shorthand defaults need care: `border: var(--x, 0 solid transparent)` moves
computed border-color off currentColor even at zero width. Defaults were chosen
to reproduce the original computed style, not merely to look equivalent.

Not covered: 72 primitives still have no variables, and the UI-primitive demos
that scripts/sync-ui-primitives.ts mirrors are now stale for the converted
items. Nothing runs that script in CI today.

Verified: every declaration parses and deep-equals its manifest array, every
declared id is read by the composition, no demo.html changed, and lint exits 0.

* feat(registry): variables for 16 more primitives, and stop the snippet clipping

Takes the count from 167 to 183 of 454. Same contract as the last batch: each
variable is declared on the root, validated in the composition's own script,
and wired to something visible, because a knob the code ignores would put a
table in the published docs that lies about the piece.

The snippet under each Variables table was clipping. Its data-variable-values
payload is one long line and the code block cut it off mid-value, with no wrap
and no scrollbar, so the one line on the page that exists to be copied could not
be read. The fence now carries `wrap`. Worth noting how that survived: the
generated markdown was correct and every mechanical check passed. It only failed
in a browser, which is where it was eventually seen.

Two techniques this round that are worth keeping.

Where an accent has a themed token family, the knob sets a new custom property
consumed by that one surface, with a fallback to the existing token, rather than
overriding the shared accent. Default therefore sets nothing, so an externally
themed accent is not clobbered, and the non-default options still follow the
theme in both light and dark.

Where GSAP owns the property outright and no CSS multiply can win — number-wheel
animates `y` inline — the knob is wired at build time instead: extra revolutions
lengthen the digit strip and move the target, so travel changes while the resting
frame stays identical. That is a real answer rather than a skipped knob.

Motion knobs that multiply a timeline-driven custom property collapse to identity
at rest, so every one of them was verified with that property pinned to a
mid-flight value rather than at t=0, where all options look the same by
construction.

Not covered: 56 primitives still have no variables.

Verified: every declaration parses and deep-equals its manifest array, every
declared id is read by the composition, no demo.html changed, lint exits 0, and
the wrap fix was confirmed on the rendered page rather than in the markdown.

* feat(registry): variables for 21 more primitives

Takes the count from 183 to 204 of 454. Same contract: declared on the root,
validated in the composition script, wired to something visible, defaults
reproducing the pre-edit render exactly.

Three kinds of knob were turned down this round rather than faked, and the
reasons are worth keeping.

A knob that contradicts its own motion. The sheet panel could be moved to the
left, but the recipe drives GSAP x from the right, so the panel would slide in
from the wrong side while the control claimed otherwise.

A knob that needs two defaults. A separator length means width horizontally and
height vertically, so one token would be wrong half the time.

An option that is not an option. Two components were given a green accent that
probed byte-identical to their default, because the theme accent already is that
token. A row in the docs table that does nothing is worse than a missing row, so
it was replaced with one that differs.

Accent knobs set a new property with a fallback to the shared token rather than
overriding it, verified by rendering with an external accent in place and
confirming the default still yields to it. Motion knobs multiply a
timeline-driven property so they never fight the inline styles GSAP writes;
because those collapse to identity at rest, each was checked twice, once at rest
against HEAD and once with the driven property pinned mid-flight.

Verified: every declaration parses and deep-equals its manifest array, every
declared id is read, defaults match HEAD on computed styles and on a pixel hash
of the rendered element, no demo.html changed, lint exits 0, and the
no-deletions gate passes.

* feat(registry): variables for 4 more primitives, and make manifests agree with their compositions

Takes the count to 207 of 454.

Four items carried a different description for their exit variable in
registry-item.json than in their own data-composition-variables. The catalog page
renders the manifest, so the published table described the knob one way while the
composition header described it another. The composition wins: it is the file
that implements the variable and the declaration is what the runtime reads.

The skill docs no longer describe a hosted tier, since the CLI now ships the two
local tiers only, and skills-manifest.json is regenerated to match.

* feat(registry): variables for 4 more primitives

Takes the count to 211 of 454.

Two knobs are worth calling out because they touch things the timeline also
touches. skeleton-block slide multiplies the driven row offset, so it is
identity at rest and only bends the middle of the move. slider value sets the
resting fill together with the readout text, aria-valuenow and aria-valuetext,
so all three agree; a composition that tweens the fill takes over from there and
owns the readout, which the comment header states plainly rather than hiding. A
multiplier was rejected there because a 0 to 1 tween would push the fill past
the end of the track.

Knobs on elements that sit at opacity 0 at rest were kept only where the shipped
recipe reveals them, and verified with the reveal forced on as well as at rest.
Skipped: an aria-label string knob that never renders, and an accent token
declared in one item CSS that nothing consumes.

* refactor(registry): drop the UI primitives, this is a video catalog

Removes 66 items tagged ui-primitive: accordions, buttons, inputs, dialogs, a
calendar. They are a shadcn-style interface component set that happens to be
expressible as HTML. None of them animate anything, so in a catalog whose job is
to offer moves for video they widen the surface without making it more useful,
and each one is a page a reader has to skip past to reach something that moves.

Every one is new on this branch. None exists on main, so nothing main ships is
being taken away; that was confirmed against origin/main before deleting rather
than assumed, and the no-deletions gate still passes.

Removed with them: registry/ui-primitives, the Operator Black token and contract
files only these items consumed, and the tooling that maintained them
(sync-ui-primitives.ts and scripts/lib/ui-primitives). No other registry item
declares a dependency on any of the 66, so nothing else loses a piece. The now
empty UI Primitives navigation rule goes too.

Generated output is pruned with the sources. The page generator writes files but
never removes ones whose source has gone, so a stale page would have survived and
404d its own preview. Verified: 0 orphan pages, 0 orphan previews, and the
navigation resolves 532 references with none dead.

This does discard variables authored for 54 of them earlier on this branch. That
work is in the history if these ever come back, and it is the right trade: they
should not have been in a video catalog to begin with.

The catalog is now 388 items. Lint exits 0 and 2522 CLI tests pass.

* feat(registry): every motion and transition primitive is now customisable

The last 16 primitives get variables, so none is left without them. 173 of 388
items now declare variables; the rest are blocks and showcases, which are whole
scenes rather than parameterised moves.

Same contract throughout: declared on the root, validated in the composition
script, wired to something visible, and falling back to the declared default on
missing or unrecognised input. With no overrides every item renders exactly as
it did before, verified per item against the pre-change render in a real browser
at rest and at pinned mid-flight states, comparing computed styles and rects and
in most cases a screenshot hash.

This round refused several knobs rather than shipping ones that only look real.

A tilt-card depth knob was written, measured, and thrown away: the card sets
overflow hidden, which forces transform-style flat, so the authored translateZ
is already inert and every option probed identical. It ships a glow knob
instead, which drives inset and visibly changes at rest and under the drift.

slot-machine-roll has no free travel knob because the roll is exactly one row
height and any multiplier lands the reel off-register; size scales row height
and roll distance together, which is the only honest version. soft-blur-in
offers up and down but not left and right, because the shipped tween resets y
and not x, so a horizontal offset would never animate away.

Two pre-existing bugs surfaced while checking honestly, both left alone as out
of scope but worth recording. zoom-through-transition and tracking-in each tween
a custom property that is never set, so CSS reads it as zero and the move starts
from zero rather than from its authored value. The depth and tracking knobs are
scoped around that and their headers say so, rather than pretending the tween is
what it appears to be.

Verified: 388 items, 0 primitives without variables, 0 items where the manifest
and the composition disagree, 0 declared-but-unread variables, nav resolves 532
references with none dead, no demo.html changed, lint exits 0, and the
no-deletions gate passes.

* feat(registry): raise the catalog quality bar, and add eight primitives

Cuts 37 components, adds 8, and writes down the standard both decisions were
made against.

The 37 removals are all new on this branch and absent from main, so nothing
shipped is withdrawn. Each was audited with two pieces of evidence: source
identity after name normalisation, and a composition-level contact sheet
showing the members animate identically.

The largest group was 13 files byte-identical apart from an h3 and one
sentence. Nothing marqueed, panned, zoomed, deployed or dragged. An honesty
tiebreak decided survivors: frosted-glass-wipe has no backdrop-filter,
spring-scale-in has no spring, masked-slide-reveal has no mask,
short-slide-right travels up, and three-particle-ribbon differs from
three-orbiting-cards by one number while having neither particles nor a
ribbon.

Two independent audits agreed 10 out of 10 on a shared calibration sample,
in both directions, including three items a first pass wrongly condemned.

The rubric is the durable part. Fatal criteria are separated from fixable
ones, because no-timeline alone hits 97 items including some of the best;
promoting it would have cut 97 and left a worse catalog. It also records the
harness rules that make a verdict reproducible: render from the composition
rather than the demo, since demos carry content the installed item does not,
and mount sub-compositions rather than inlining them, since inlining renders
black frames indistinguishable from a dead item.

The eight additions target measured gaps. Camera language ranked first
because PSNR across 30 reference demos showed the most impressive
environments barely move: they are sets, not shots.

camera-shake carries nine lens-accurate profiles, amplitude scaled by focal
length so a wide lens shakes differently from a telephoto. rack-focus splats
each light through the aperture shape, so a defocused point becomes an image
of the iris, with flux conserved so highlights survive defocus.
camera-dolly-zoom solves focal length from distance, holding subject size to
0.000 percent drift while the background grows 53 percent. Plus
oscilloscope-trace with history-free phosphor persistence, bar-chart-race,
split-flap-board, spiral-galaxy and vfx-anamorphic-flare.

Each is verified by rendered frames and a seek-equals-playback check rather
than by check passing, which is not a visual gate.

* fix(registry): let the split-flap board finish flipping on screen

The board declared 8s but every flap had settled by 3.5s, so more than half the composition was a still frame and the catalog preview opened on it.

* fix(registry): keep the thread-message-stack payload parseable

A line wrap had put literal newlines inside the JSON string literals of the blocks data-hf-primitive-data payload, so JSON.parse threw in the browser and the composition never ran. The preview script hid it: it rewrote the payload in the temporary copy it captured from, so the catalog picture looked right while every installed copy stayed broken. That repair pass is gone and the payload is fixed where it ships.

Its two tests could not have caught this. Both were written against vitest in a directory the repo runs with node:test, so neither was in test:scripts and neither had ever run. They are converted and registered, along with a new one that JSON.parses every payload in the registry, and that one was checked against the re-wrapped shape before being kept.

* fix(registry): close the apostrophe that truncated a variables declaration

chromatic-aberration-wipe described its accent as "the incoming scene's gradient" inside a single-quoted data-composition-variables attribute, so the attribute ended mid-JSON and the tag never closed. The formatter refused to parse the file, which is how it surfaced, but the runtime would have read a truncated declaration.

Also formats the 159 registry and docs files the branch had left unformatted, regenerates the skills manifest, and drops docs/primitives: those 13 pages import /snippets/PrimitivePlayer.jsx and read docs/public/primitives/, neither of which is on this branch, so mint failed the build on them. Nothing links to them and they ship whole on feat-video-primitives.

CI ran test:scripts before building core, so the preview test added here failed on a missing dist rather than on anything it checks. It now runs after the builds.

* fix(registry): make the review findings real fixes

Ten items declared variables on their composition root but had no variables key in the manifest, so their generated pages shipped no explorer at all. Their manifests now mirror the root. Two more disagreed only in description text, and the root was the truthful side: both compositions paint an inset ring, not the slabs or colour pair the manifest described.

The caption <video> removal left 24 CSS rules addressing elements that no longer exist. Removed, excluding the four ids that were already orphaned on main.

thread-message-stack could not stay fixed: oxfmt reflows a divs contents and lands a newline inside a JSON string literal, so the payload broke again on the next format. A script tag is not an option because the runtime strips every script out of the mounted clone. The reader normalizes HTML whitespace instead, which is what makes it survive any reflow, and the guard test now asserts that contract rather than the byte layout.

downloadFile had lost its 30s timeout, DownloadOptions, and the mid-pipeline error plumbing in a rewrite that was only meant to fix redirects. Five callers were left with no stall guard. Restored, redirect handling kept.

warnUnknownEnumValues re-did the parse readDeclaredDefaults had already done. Both now share one readDeclarations, and the rest splits into compositionLabel, declaredOptions and unknownEnumValue. 1909 core tests unchanged.

Deletes build-qa-gallery, theme-gate and generate-primitive-pages: nothing invokes them, two read a coverage map four directories above the repo root, and the pages the third generates are no longer on this branch. Wires check-no-main-deletions, which is the opposite case, real and tested and never run.

* fix(registry): stop shipping a stale copy of the catalog-search work

This branch carried re-authored copies of the CLI search commits rather than the ones on their own PR, so merging it would have rolled back six later fixes: the vector cache that refuses a half download, the 0o700/0o600 modes, the rebuilt-from-registry index generator, the coverage gate and its CI job, and the scripts typecheck. Those files now come from that branch.

registry.json still listed 64 items whose directories the UI-primitive removal deleted, so hyperframes add would resolve a name and then fail on missing files. Regenerated from disk: 358 searchable items, 358 vectors, gate green.

Also drops an internal provenance block from thread-message-stack, along with the type and the two JSON schemas that existed only to describe it. It published an artifact id, a version id and a heygenverse:// URI, none of which mean anything to someone installing a block, and a public registry is the wrong place for them.

Typechecking scripts/ for the first time surfaced 45 errors in this branch. Fixed rather than suppressed: the geometry test reads positions through one accessor that names a missing index instead of letting NaN reach a tolerance compare, and the null-returning shape helper is asserted at its call sites, except in the test whose subject is the null.

* refactor(scripts): split the page builder into its numbered sections

generateItemMdx had grown to cyclomatic 26 across 196 lines while its own comments already named the seams. previewSection, usageSection and footerSection now own one each, taking it to 13. Regenerating all 358 pages afterwards produces a byte-identical tree, which is the check that matters for a generator.

* fix(cli): repair what the cross-branch file take broke

Taking files wholesale from the catalog-search branch reverted the downloadFile timeout restored one commit earlier, so five callers were back to no stall guard at HEAD. Restored on both branches this time, since that branch never had it either.

It also took that branch test:scripts line without the vitest it depends on, so the script exited 127 and the CI Test job would have failed on a missing binary rather than a test. vitest is a root devDependency now, and the run is scoped to scripts/catalog/ with the slash: without it the prefix also matched catalog-preview-temp.test.ts, a node:test file with no vitest suite in it. Both branches had that one.

Four registry items and their docs copies carried absolute paths from a working directory. A public registry is the wrong place for them and history is permanent, so the sentences now name the source without the path.

Skill docs came from before the code they describe: the catalog command reports unindexed and applies installability after ranking, and both SKILL.md files now say so.

Also drops a double type assertion and ten dead ?? NaN coalesces from the geometry test, the second of which reintroduced exactly the NaN-into-a-tolerance-compare that the checked accessor exists to prevent.

* fix(ci): resolve core from source and take only item directories

The scripts typecheck failed on generate-registry-items importing @hyperframes/core by package name. It resolves on a machine with a warm node_modules, which is why it passed locally, and not in CI. Every other script in the directory already imports core from source and says why in a comment.

The preview job derived its item list with a sed that needs a trailing slash, so registry/components/CATALOG.md never matched, survived as a full path, and was handed to the renderer as an item name. The grep now requires a directory component. Simulated against this PR: 219 items, none of them a path.

* refactor(registry): load gsap from the cdn like every other item

store-badge-lockup vendored gsap 3.14.2 as a 4,200 line minified file and installed it into the users project, while 540 other items load that exact version from jsDelivr. Repointed, the copy deleted and the manifest entry with it, so hyperframes add store-badge-lockup no longer writes a second copy of gsap into someone elses compositions directory. Re-rendered and re-generated: the preview still draws.

* feat(registry): swap in the detailed device models

Replaces the iPhone and MacBook models in the three device blocks. The old assets were untitled meshes with no keyboard on the laptop; these name every part and model the keycaps, speaker grilles, antenna bands and camera plateau.

Not a drop-in. The compositions found the screen by side effects, the material that happened to carry an emissiveMap for the phone and a mesh literally called matte for the laptop, and neither exists now. They select front-glass and display instead.

Both panels ship UVs authored for a tiling material, the laptop runs u -6.3 to 6.3, so one screen image clamped and smeared across the panel. Planar UVs are derived from each panel bounds at load.

The old phone display sat at the model minimum Z and the timeline spins assume a screen facing -Z. These face +Z, so the model is aligned by reading which of its own parts is front rather than re-timing the animation.

Removes the hand-drawn Apple logo from two blocks: the replacement ships apple-logo meshes, and the drawn one used coordinates read off the old lid, so it floated beside the device.

The preview copy only took top level files, so models/, lib/ and assets/ never reached docs/public and 38 items rendered there without their assets. That is the source of the non-blocking 404s in the preview job. It recurses now, which also brings vendored bundles across, so the generated tree is out of the lint scope.

The html-in-canvas notice is a Danger callout: without the flag the preview is a black rectangle, which is a prerequisite rather than a caveat.

* chore(registry): rebase onto the merged catalog search

This branch carried its own copy of the catalog-search work so that merging it in either order could not regress the other. That copy is now the older one: main has the consent fix, the contributor path for someone without the embedding model, the restored download test and the corrected gate message. Every file main owns is taken from main, and the three duplicated CLI commits are dropped rather than replayed.

Regenerated afterwards, because the registry it describes has changed: registry.json, the vector index, the catalog pages and the skills manifest.

* fix(scripts): stop the rebase reverting the preview pipeline

Resolving the rebase in favour of this branch took three files whose newer versions had already merged, so the branch quietly reverted them.

generate-catalog-previews.ts lost encodeForWeb, which exists because publishing masters directly put 25 Mbps files on the docs CDN and one 20-second preview was 60 MB. It also lost the ffmpeg transcode, so a jpeg capture was being written straight to a .png path while the comment above still said it transcoded, and it lost openOpaqueCapture, re-creating the second copy of a capture setup that was extracted precisely to stop there being two. This PR renders previews for over 200 items, so all three shipped at scale.

scripts/tsconfig.json regained exclusions that hole the gate, and generate-template-previews.ts went back to importing the producer by package name, which is the CI failure that import was changed to fix.

All three are taken from main. Also drops an alignScreenToMinusZ copied into the laptop block, which has no front and back to compare and never called it, and makes the preview copy lstat so a symlinked directory cannot send it outside the repo.

* fix(registry): clear the five items this PR added that the linter rejects

The registry linter is not wired into CI, so five items this PR adds were shipping with real render defects nobody would have seen fail.

caption-camera-follow and grade-split-reveal styled their root by its own class. Sub-composition CSS is scoped to [data-composition-id=...] <selector>, so a selector whose leftmost part is the root class becomes a descendant selector and stops matching the root: the scene renders unstyled at render time while looking correct in every static check and in preview. Both now key off the attribute the scoper already adds.

logo-brand-close tweened letterSpacing, which the browser snaps to integer device pixels, so the ease-out tail stutters under seek-by-frame capture. It is a scaleX now.

terminal-simulator named SFMono-Regular, which the renderer cannot resolve, so the text silently fell back.

oversized-cursor was a false positive: the rule scans raw source for head tags and a literal one written inside a JS comment paired with the real closing tag. Confirmed against a render, nothing leaks into frame, so the comment says head element rather than the tag.

Also stops generate-registry-items.ts dropping catalogArtifact.revision. build-local-vectors.ts stamps it so the CLI and the coverage gate can tell whether the published vectors still describe this registry; regenerating the item list erased it, and the gate then failed until someone rebuilt the index.

41 items still fail the linter, every one of them pre-existing on main.
2026-08-10 14:26:55 -04:00
Miguel Angel Simon Sierra 35231e964d fix(registry): connector geometry, family accent, and demo legibility
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.
2026-08-01 01:36:18 +02:00
Miguel Angel Simon Sierra a98c120383 style(registry): format mk / yt / hw family HTML with oxfmt
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.
2026-08-01 01:19:58 +02:00
Miguel Angel Simon Sierraandjbernard077 f252ec6d99 feat(registry): mk / yt / hw theme families — 29 items
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>
2026-08-01 01:03:39 +02:00
Miguel Angel Simon Sierra 58ef6aca67 Merge branch 'pr-1994' into review/registry-families
# Conflicts:
#	registry/registry.json
2026-07-31 22:18:42 +02:00
Miguel Angel Simon Sierra be76a932c9 Merge branch 'pr-1993' into review/registry-families
# Conflicts:
#	registry/registry.json
2026-07-31 22:18:26 +02:00
Miguel Angel Simon Sierra 4c54f45f46 Merge branch 'pr-1992' into review/registry-families
# Conflicts:
#	registry/registry.json
2026-07-31 22:17:05 +02:00
vanceingallsandClaude Fable 5 8bf939043f fix(registry): liberal emoji-pop brand colors, weight-shift fit fixes, 8192 clamp
- 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>
2026-07-24 06:10:00 +00:00
vanceingallsandClaude Fable 5 5c2981d066 fix(registry): reject non-numeric caption-data versions; boot fetch never clobbers a manual attach
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>
2026-07-24 05:55:21 +00:00
vanceingallsandClaude Fable 5 e2846eb7cc fix(registry): encapsulate caption template runtimes in IIFEs
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>
2026-07-24 05:25:41 +00:00
vanceingallsandClaude Sonnet 5 4f69947191 fix(registry): clear brand CSS custom properties on unbranded re-attach; remove dead italic path
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>
2026-07-24 00:03:46 +00:00
vanceingalls 5af6203ae7 fix(caption-weight-shift): remove O(n^2) redundant hide-all-others loop 2026-07-23 22:55:37 +00:00
vanceingalls 18de2b1f1d fix(caption-editorial-emphasis,caption-emoji-pop): remove O(n^2) redundant hide-all-others loop
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.
2026-07-23 22:42:14 +00:00
vanceingallsandClaude Sonnet 5 ed8973952d feat(registry): make caption-editorial-emphasis data-driven with emphasis heuristic
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>
2026-07-23 22:15:26 +00:00
vanceingalls 7d4e71d10b feat(registry): make caption-highlight data-driven with automatic grouping
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()).
2026-07-23 21:52:56 +00:00
vanceingallsandClaude Sonnet 5 392a9d251a feat(registry): make caption-emoji-pop data-driven with generic emoji lexicon
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>
2026-07-23 21:31:46 +00:00
vanceingalls 020c8986f4 fix(registry): force GSAP render at attach — seek(0) is a no-op on a fresh timeline 2026-07-23 21:01:24 +00:00