mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-11-03 12:17:03 +00:00
fix/variable-src-per-copy
127
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
38afc0b013 | fix(deps): update esbuild for the security advisory (#6277) | ||
|
|
e6fba28b66 |
fix(registry): centre mk-callout-highlight with flex instead of a script-time measurement (#5118)
* fix(registry): centre mk-callout-highlight with flex instead of a script-time measurement The text box becomes a flex item of a full-frame wrapper (#mk-ch-center), so its position is laid out by the browser rather than read from a getBoundingClientRect() taken at script time. That measurement depends on which font faces are loaded when the script runs; the fonts gate covers only faces that static markup already uses, so script-built text measures against the fallback face (#5112). A non-null CONFIG.x / CONFIG.y becomes a margin offset on that axis. The GSAP y entrance stays on the box, which carries no CSS transform. Also drops the scene-exit visibility tail (the runtime hides the clip at its window end; the 0.02 s tail never reached a frame at 30 fps) and removes data-layout-allow-occlusion from #mk-ch-box: nothing beneath the block is text, and the attribute made the contrast audit skip the block's own words whenever a later full-frame host answered the hit-test probe. Renders are bit-identical to the measured version when the face is loaded (nine-scene project at v0.8.135, 1380/1380 frames). CONFIG and the sweep mechanic are unchanged. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(registry): keep only the flex centring in mk-callout-highlight Address review: restore the scene-exit visibility tail and the layout occlusion attribute, which belong to family-wide conventions and are out of scope here, and note the x + maxWidth clip edge in CONFIG. Refs #5112 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
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 |
||
|
|
b059600083 | feat(registry): seven more WebGPU shader backgrounds, from sun burst to color wheel (#5341) | ||
|
|
9dbbca7459 |
feat(registry): seven more WebGPU shader backgrounds, from ripples to thin film (#5190)
* feat(registry): seven more WebGPU shader backgrounds, from ripples to thin film * fix(registry): describe the three shader orbs as orbs |
||
|
|
46f6cb3567 |
feat(registry): eight more WebGPU shader backgrounds, from blob to fractal noise (#5192)
* feat(registry): eight more WebGPU shader backgrounds and a bundle with thirty new shaders * refactor(registry): simpler bundle copy step in the shader build * test(registry): every shader block ships the same bundle and names a shader in it * refactor(registry): one block loader for the shader block tests * test(registry): name the stale block and match only shader names * feat(registry): eight more WebGPU shader backgrounds, from blob to fractal noise * chore(registry): shorter licence line at the top of the shader bundle * chore(registry): bring in the latest shader bundle branch * chore(registry): shorter licence line at the top of the shader bundle * test(registry): every shader block installs the bundle's licences |
||
|
|
63a7ec692f |
feat(registry): eight more WebGPU shader backgrounds and a bundle with thirty new shaders (#5191)
* feat(registry): eight more WebGPU shader backgrounds and a bundle with thirty new shaders * refactor(registry): simpler bundle copy step in the shader build * test(registry): every shader block ships the same bundle and names a shader in it * refactor(registry): one block loader for the shader block tests * test(registry): name the stale block and match only shader names * chore(registry): shorter licence line at the top of the shader bundle * test(registry): every shader block installs the bundle's licences |
||
|
|
7cf042d74f | feat(registry): six WebGPU shader backgrounds from the Shaders library (#5138) | ||
|
|
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. |
||
|
|
d2398d686a |
feat(registry): eight ad templates show their video preview in the catalog (#4979)
* feat(registry): eight ad templates show their video preview in the catalog * refactor(scripts): the preview backfill reuses the shared entrypoint guard |
||
|
|
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> |
||
|
|
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 |
||
|
|
8798e40da8 |
fix(registry): carousel cards fall back to their sample image inside the assets folder (#4543)
* fix(registry): carousel cards fall back to their sample image inside the assets folder When an image variable resolves to nothing, the 25 carousel blocks loaded the sample card by bare file name, which 404s beside the block and draws a white card. The fallback now keeps assets/carousel-images/, the folder every card image is installed into. A guard test fails on any block with a bare fallback. * test(registry): the carousel fallback guard covers every block that uses it The guard now picks each block that reads an image variable and fails when its fallback no longer matches, instead of silently skipping it, and ignores stray files in the blocks folder. |
||
|
|
ec7f473d84 |
fix(registry): cuboid-carousel plays from the start when mounted (#4455)
* feat(core): a composition's script can load its own files with __hyperframes.assetUrl * test(core): a template composition inside a mounted file resolves assets from that file * docs: assetUrl is for paths beside the composition, not project-root paths * docs: say assetUrl misses a project-root path * fix(core): a mounted composition's import map and module scripts run as such * refactor(core): scope a mounted module script under the same condition as a classic one * fix(core): a mounted module script loaded by src stays a module * fix(core): a mounted import map keeps the trailing slash of a prefix address * fix(registry): six blocks load their own files and variables when mounted * fix(registry): keep frost's variable declarations on its html element * fix(registry): cuboid-carousel plays from the start when mounted * fix(registry): orbit-card uses its own variables; frost font and catalog maps follow * chore(core): keep the module-script helpers' comment share at the base * fix(catalog): frost's payload runs its driving script, not the asset-base one * test(catalog): check import-map keys by membership, not a built regex |
||
|
|
b25fd4f388 |
fix(registry): three blocks keep their settings when mounted (#4463)
* fix(sdk): read a composition's variables from its root when its html has none * fix(registry): three blocks declare their variables on their own root so they work mounted * 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 |
||
|
|
53e2e20a15 |
fix(registry): six blocks load their own files and variables when mounted (#4452)
* feat(core): a composition's script can load its own files with __hyperframes.assetUrl * test(core): a template composition inside a mounted file resolves assets from that file * docs: assetUrl is for paths beside the composition, not project-root paths * docs: say assetUrl misses a project-root path * fix(core): a mounted composition's import map and module scripts run as such * refactor(core): scope a mounted module script under the same condition as a classic one * fix(core): a mounted module script loaded by src stays a module * fix(core): a mounted import map keeps the trailing slash of a prefix address * fix(registry): six blocks load their own files and variables when mounted * fix(registry): keep frost's variable declarations on its html element * fix(registry): orbit-card uses its own variables; frost font and catalog maps follow * chore(core): keep the module-script helpers' comment share at the base * fix(catalog): frost's payload runs its driving script, not the asset-base one * test(catalog): check import-map keys by membership, not a built regex |
||
|
|
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 |
||
|
|
8dabc8af63 |
fix(registry): glass-shard-title renders when mounted, and 4 skipped blocks install again (#4423)
* fix(registry): glass-shard-title renders when mounted, and 4 skipped blocks install again * fix(registry): glass-shard-title loads in Studio and ?play, and its docs payload fails loudly * test(scripts): pin the glass payload's hdr guard for any quote style |
||
|
|
8055961740 |
feat(registry): a Lottie character walk-in, and looping Lottie that keeps playing (#4316)
* fix(core): looping Lottie animations keep cycling instead of freezing after one pass * feat(registry): add a Lottie character walk-in and document the character route * chore(registry): index lottie-character-walk in the catalog and search vectors * chore(docs): list lottie-character-walk in the catalog gallery * docs(skills): say that lottie-web loops when loop is left out * fix(core): a finished Lottie holds its last frame instead of disappearing * fix(core): hold a finished Lottie on its exact last frame * chore(skills): refresh the manifest hash for the Lottie skill edits * fix(core): wrap looping Lottie by frame so a loop never holds at its seam * fix(core): a looping Lottie starts each cycle on frame 0 despite float error |
||
|
|
05d09a7452 |
chore(registry): remove low-use catalog items and variant tails (#4274)
* chore(registry): remove low-use catalog items and variant tails * fix(registry): remove stale references to deleted catalog items * style: format catalog fixture * fix(ci): use surviving catalog preview canary * chore(ci): allow intentional catalog deletions * fix(ci): scope fallow audit for catalog fixtures * chore(catalog): regenerate local search vectors * chore(catalog): update vector revision * fix(catalog): validate registry set delta |
||
|
|
d5e00c0e66 |
feat(registry): week in merges leaderboard block (#4264)
* feat(registry): add a week-in-merges leaderboard block Authors race across the row at a speed set by their merge count, bouncing at both edges. * chore(registry): cover week-in-merges in the catalog search index * style(registry): format the week-in-merges block Wrap the lines oxfmt rejects so the format check passes. * fix(registry): regenerate the week-in-merges catalog payload The format pass left the committed payload off the generator output. * feat(registry): cap week-in-merges at twelve rows The block races at most rows_max authors, highest merge count first, and writes one line for everyone left out. The default is 12, so a long list cannot shrink the rows. * fix(registry): make week-in-merges speed follow merge count A row now travels the leader's pace times its merges over the leader's count. Only a zero stays on the floor, so a near-tie no longer looks like last place. |
||
|
|
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 |
||
|
|
a2a5af8e19 |
fix(registry): stop gsap tweens from clobbering baked-in css transforms (#4090)
* fix(registry): stop gsap tweens from clobbering baked-in css transforms transitions-cover, transitions-other, and us-map-bubble each set a static css transform (translate/scale) on an element, then animate x/y/scale on it with gsap. gsap owns the full transform matrix once it touches an element, so the initial css value is a static risk the runtime StaticGuard flags. establish the initial state via gsap.set() instead, matching the pattern already used elsewhere in the registry. us-map-bubble had a real visible defect: bubbles rendered stuck near scale-0 instead of animating in. the other two items render pixel- identical before and after since gsap already decomposes their pure single-axis transforms correctly on first touch; fixed anyway since the lint rule and StaticGuard are correct that it is a latent risk. * style(registry): run oxfmt on transitions-cover |
||
|
|
9bb4f5cd57 |
feat(catalog): browse the catalog as a live grid, not a static index (#4051)
* feat(catalog): browse the catalog as a live grid, not a static index
The Catalog overview page now mounts a browse grid (posters, tags,
per-category rows, expandable sections) generated over the existing
~380 registry items, instead of one static hero video plus two links.
docs/snippets/catalog-gallery.jsx and docs/catalog-gallery.css are
ported from a reference implementation of this same UX; the CSS keeps
its original comments as-is (NO_ESSAY_COMMENTS_OK: vendored file).
scripts/sync-docs-catalog.mjs and build-docs-gallery-nav.mjs are new,
generating the gallery's data and the Catalog tab's nav directly from
this repo's registry/*/registry-item.json and docs.json, so no sparse
clone or snapshot of another project is needed. scripts/check-docs-catalog.mjs
proves every catalog page from before this change still exists, on
disk and in the sidebar, with an identical page list.
No catalog page or snippet is removed; the previous overview content
(catalog-overview-player.jsx) is unused on this page but stays in the
repo.
* refactor(catalog): share the docs-catalog helpers across the sync scripts
Three independent adversarial reviews (reuse, quality, efficiency) all
converged on the same shape: sync-docs-catalog.mjs, build-docs-gallery-nav.mjs
and check-docs-catalog.mjs each hand-wrote the same docs-root resolution,
the same catalog-gallery-data.mdx parse, and the same Catalog-tab lookup.
Extracted into scripts/docs-catalog-shared.mjs.
sync-docs-catalog.mjs also ran build-docs-gallery-nav.mjs as a dynamic
import purely for its side effect, forcing a same-process re-read and
re-parse of the data file it had just written. build-docs-gallery-nav.mjs
now exports buildNav(docs), called directly with the path already
resolved; its CLI entry is guarded for standalone use.
slug() dropped the /^-+|-+$/ trim in favor of the character-scan trim
packages/core/src/tokenSlug.ts already uses for the same reason (CodeQL
flags that regex as polynomial ReDoS, js/polynomial-redos); not imported
directly since tokenSlug.ts is compiled TS with a different domain
(CSS variable naming) and no existing scripts/*.mjs imports packages/core
source at plain node runtime.
Fixed one bug found while re-verifying after the refactor: the recursive
nav walk wasn't idempotent against its own generated output — re-running
sync-docs-catalog.mjs a second time saw the "Catalog" wrapper buildNav()
adds and misattributed every flat, single-section group under it, 9
groups collapsing to 7. walk() now treats "Catalog" as transparent, the
same way it already treated "Overview". Verified: three consecutive runs
from a fresh origin/main checkout now produce identical 9 groups / 400
items each time, and check-docs-catalog.mjs still passes.
* chore(catalog): scrub an internal decision code from a code comment
* fix(catalog): stop losing a section's label when its nav is flattened
An independent adversarial review found real data loss: build-docs-gallery-nav.mjs
collapsed a group with exactly one section into a flat page list (a clean sidebar
for the common case), but sync-docs-catalog.mjs's walk() derives an item's section
from nav depth, so a second sync run over that flattened nav could no longer
recover the section name and silently fell back to the group's own label. Data &
charts / Data was the one real casualty in this catalog; Carousels and Blocks
happened to already have a section named the same as their group, so the loss
was invisible there.
Fix: only flatten when the section label equals the group label, so nothing
non-recoverable is thrown away. Verified with a deliberate mutation (reverted the
condition, reproduced the exact bug from a clean origin/main checkout, confirmed
the new check-docs-catalog.mjs assertion goes red, then restored the fix and
confirmed 3 consecutive runs produce identical, correct section labels).
check-docs-catalog.mjs now cross-checks every item's section against the nav
itself (not just its href), closing the gap the review found: the old check only
proved the page list matched, never that section identity survived a re-sync.
Also deletes docs/snippets/catalog-overview-player.jsx: unreferenced anywhere in
the repo now that catalog/index.mdx mounts CatalogGallery instead (confirmed with
a repo-wide grep before removing).
* chore(catalog): allowlist the deliberate catalog-overview-player.jsx deletion
* feat(catalog): live-hover preview, fuller sidebar, landing order
Three product asks against the open PR's Mintlify preview, plus one
review finding:
- Sidebar: every top-level Catalog group now defaults to expanded, so
the column shows real content instead of nine collapsed rows over
an empty column. Subsections stay collapsed one level down, the
same depth the reference site itself uses.
- Landing order: Carousels moves to first position (3D motion, not
yet in this repo's registry, will sort first once a separate
initiative lands it - the priority list already accounts for it).
Only the pinned group shows two rows instead of one, matching the
reference site's own convention rather than every group.
- Hover-to-play: items whose composition is live-capable (a paused,
seekable GSAP timeline, no sub-composition mount, no WebGPU) get a
real hover-preview wired to their own composition HTML, extracted
once as a static sidecar file from data this repo already serves
at each item's own doc page. Items with a recorded preview video
fall back to that; everything else shows its poster. Verified no
WebGL/three.js item is ever marked live (238 live, 30 video, 132
poster-only of 400).
- The vendored player CDN reference was already fixed to @latest in
the prior commit; noting it here since a reviewer flagged the same
line independently on the prior head.
docs/public/catalog/gallery-live/**/*.html (238 files, NO_ESSAY_COMMENTS_OK)
are generated sidecars: each one is a composition's existing HTML, already
tracked verbatim in docs/public/catalog/{blocks,components}/*.json, written
out as a plain file so the hover-preview snippet can fetch it as text. Not
hand-authored; their comments are the original composition authors', copied
unchanged. Regenerated by scripts/sync-docs-catalog.mjs on every run.
* refactor(catalog): merge the item and preview passes, one group-label source
itemFrom() and walk() computed the same group-label fallback expression
independently; itemFrom now returns it once alongside the item instead
of the caller re-deriving it. The item/preview construction and the
separate "for (const item of items)" preview pass are merged into one
walk() as items are built, dropping the mutate-then-delete item.live
scratch field. Byte-identical generated output, confirmed by diff.
* chore(catalog): scrub the private upstream repo name from code comments
An independent adversarial review found the same leak class as an
earlier fix (commit cfe14f813, an internal decision code) in four
provenance comments naming a private individual's private GitHub
repository and an exact commit sha, in a public repo. Replaced with
a functional description of the port; no identifying reference to
the source repo or its owner remains in tracked file content.
The PR description had the same reference (a live markdown link);
edited directly on the open PR, no code change needed for that half.
* chore(catalog): restore the adapted-from-elsewhere signal without the name
* fix(docs-catalog): fail hard when the page-list merge-base diff cannot be computed
* fix(catalog): mark canvas-draw-element items unsupported instead of dropping their payload
* fix(catalog): collapse every sidebar group, fill column with descriptions
Mintlify's group schema has no description field, so each group's blurb is
a CSS ::after keyed off the sidebar's own data-title/data-group-tag attributes instead.
NO_ESSAY_COMMENTS_OK: false positive.
The real /* */ block is 3 lines. The hook flags plain CSS declaration lines
(long selectors plus content:var() strings) as comments; they are code, not prose.
* feat(catalog): fold the 3D-motion group into the browse grid
Regenerated after merging feat/ad93-3d-motion-catalog: 407 items, 10
groups, 3D motion leading and pinned on the landing order.
* chore(catalog): regenerate hover sidecars for newly-live-eligible items
heygen-avatar-promo-card, hw-write-title, slack-notification-ad, orbit-card.
NO_ESSAY_COMMENTS_OK: generated composition HTML, not hand-authored.
* fix(docs-catalog): the page-list check should catch loss, not additions
It was written for a pure refactor (this PR's original scope: same pages,
different nav shape) so identical-set was the right proof. Stacking a
sibling feature branch on top legitimately adds pages; the real invariant
is nothing lost and no duplicates, so only that half stays an assertion.
* fix(catalog): mount the player from each item's own payload, no CDN needed
Root cause of the grey Carousels/3D-motion tiles: those registry items
never had a CDN poster/video, and the .html hover sidecar 404s on the
deployed docs host (Mintlify drops .html/.js/.css from docs/public with
no error). Cards now fetch the same repo-served .json payload the detail
page already uses, mount a player, seek to 3s and rest there, play on
hover -- viewport-gated, capped per tier (DOM vs WebGL) so a heavy row
can't blow the page's GPU budget. Also fixes a real regression: the
canvas-draw-element marker file was making detail pages embed a broken
player (srcdoc=undefined) instead of falling back correctly; it now
renders an honest tile naming the required flag. Deletes the now-dead
.html sidecar mechanism and its vendored gating helper.
* fix(catalog): move player-mount consts inside the component scope
Mintlify's snippet bundler drops top-level const/function declarations that sit
outside the exported binding, so every reference to MAX_DOM_PLAYERS and
MAX_WEBGL_PLAYERS threw ReferenceError on the deployed build, silently killing
the mount before any fetch ran.
Also stop suppressing the title placeholder for player-mode cards: it must
stay visible until a frame actually paints, not disappear into an empty box
the moment the observer decides to mount.
* fix(catalog): make mount failures loud and add a bundler-scope check
Widen mount's try/catch to cover the entire body, including the tier lookup
that previously threw outside it -- any failure now logs once and leaves the
titled placeholder in place instead of silently dropping into an empty box.
Add a check-docs-catalog.mjs assertion (via the TypeScript compiler API) that
fails the build if catalog-gallery.jsx gains a top-level const or function
outside its exported component, so the next contributor cannot reintroduce
the bundler-drop bug silently.
* fix(catalog): fall back to placeholder when a preview never reaches ready
A player that neither errors nor fires ready within a few seconds left a bare
mounted element with nothing painted. Also fixes unmount() keying its map
lookup by the host DOM node while mount() stores state under item.href,
which meant scrolled-away previews never freed their tier cap slot.
* docs(catalog): trim an essay-length comment in docs-catalog-shared.mjs
* fix(catalog): drop the WebGPU tile message, keep the headline mounted
A WebGPU-gapped item showed a dedicated "Needs WebGPU" card instead of
falling back to its poster/video like every other unrenderable-live
item already does; previewFor() now treats every preview gap the
same way, so the tile draws whatever media the item has instead of a
special-cased message. Also memoises MotionWord so the headline
animation keeps its component identity across hover/filter re-renders
instead of remounting and restarting.
* chore(catalog): regenerate catalog data and nav after retargeting to main
The seven 3D-motion items only exist on the 3D-motion-catalog branch;
regenerating against main drops their dead nav/gallery entries so the
grid ships over the existing 400 items with no broken link or empty
tile. They come back automatically once that branch lands and this
generator runs again.
* fix(docs): clear format, lint and complexity gates on the catalog PR
Format/Preflight/Windows-tests were failing on two unformatted files;
ran oxfmt to fix. Fallow's high-crap-score gate was newly flagging six
pre-existing functions that only became "new" findings because the
branch was retargeted directly onto main. Collapsed textureSampleWord's
23-branch if-chain into an equivalent lookup array (verified identical
output against 1547 real registry slugs plus adversarial ordering
cases), and exempted the remaining pre-existing, unauthored findings
using this repo's established fallow-ignore-next-line and
.fallowrc.jsonc health.ignore conventions.
* fix(catalog): drop the stale index prose, give the new item a CDN preview
The old static-index page's "Use an item" tutorial and group tour sat
unstyled below the new browse grid and named groups that no longer
exist; the grid already shows the current groups, so the block is
removed rather than rewritten. Kept the required Related topics
section.
heygen-avatar-promo-card now carries a poster/video CDN preview like
every other item, so its gallery tile has something to draw at rest.
* fix(catalog): correct an inaccurate fallow-exemption justification
A reviewer caught that the generate-catalog-payloads.ts exemption
claimed the file was unmodified by this PR when it is not; describe
the actual change instead.
* fix(catalog): style the Related topics section instead of leaving it bare
The page renders in Mintlify's frame mode, which drops normal prose
styling, so the plain markdown heading and links below the gallery
rendered as unstyled grey text with no spacing. Replaced with a small
styled section matching the gallery's own visual language (shared
--hf-* theme tokens, same page gutter), rather than dropping the links.
* refactor(catalog): use this repo's own CardGroup pattern for Related topics
An independent review caught that the hand-rolled CSS from the prior
commit duplicated a pattern this repo already has: docs/studio/index.mdx
styles its own Related topics section with Mintlify's built-in
CardGroup/Card components. Switched to that instead and dropped the
custom CSS. Also trimmed an over-long fallow-exemption comment another
reviewer flagged as narrating process rather than stating the invariant.
* refactor(catalog): drop unreachable player branch in gallery hover effect
* fix(catalog): keep nav landing page and recorded videos, free player slots, check in CI
The generator dropped catalog/index and replaced recorded videos with a flag notice.
A card scrolled away before ready released its player slot twice.
check-docs-catalog now runs in CI.
* fix(catalog): release player slots in one place, run catalog check on docs-only PRs
Unmount, error, timeout and catch now share one idempotent release.
The Lint job also runs when only docs or registry files change.
* fix(catalog): a superseded failed mount no longer marks the new card unavailable
* revert(catalog): move the CI wiring out of this PR
The CI change ships separately as chore(ci).
* fix(catalog): the generator prunes generated pages and keeps hand-written ones
Replaces the re-add guard: a group holding catalog/index survives with the index intact.
|
||
|
|
d6a0c407d7 | fix(registry): scope the 12 vscode-theme blocks' root id and fix their asset path (#4094) | ||
|
|
2279333990 |
fix(registry): register 4 async-built timelines only after they are built (#4086)
code-3d-extrude, code-particle-assemble, code-shader-dissolve and code-snippet-flight each registered window.__timelines[id] with an empty, paused timeline before document.fonts.ready populated it. The sub-composition readiness gate treats key presence as ready and nests the child once, so mounting any of these as a sub-composition rendered blank. Moved registration to the end of the fonts.ready callback and added the hfForceTimelineRebind re-nest call, matching the pattern already used by code-highlight, code-typing, code-morph and code-scroll. |
||
|
|
81ca5f4187 |
fix(registry): vfx-iphone-device stops gating its screens on rAF (#4083)
* fix(registry): vfx-iphone-device stops gating its screens on rAF The device screens only started rendering after two requestAnimationFrame ticks, which run on wall-clock time and are not guaranteed to fire in every render environment, risking a render that never registers its timeline. Swapped in the same canvas.requestPaint()/setTimeout paint-wait helper the liquid-glass items already use for the same one-time screen-capture handoff. * fix(registry): wait for both capture canvases on vfx-iphone-device The raf-desync fix only waited on the laptop canvas before capture, leaving the phone canvas with no explicit paint confirmation. |
||
|
|
637ce57994 |
fix(registry): svg dash animations measure the path after its d is set (#4082)
getTotalLength() was called on a path whose d attribute was set through a fresh getElementById(...).setAttribute(...) chain rather than the variable being measured. this is behavior-neutral at runtime (same DOM node either way, measured synchronously in the same task), but the lint's static rule can only verify a d assignment happened when it traces the same variable. hw-path-text and hw-text-cloud now assign d through the same variable they measure, matching how every other registry item with a drawn path already does it. |
||
|
|
2679ebdd0d |
fix(registry): corner brackets fade in on cue in north-korea-locked-down (#4081)
The corner brackets sat at CSS opacity:0 and the intro gsap.from() also targeted opacity:0, so it animated 0 to 0 (a no-op) instead of revealing them. They only appeared later via an unrelated tween, losing the intended synchronized pop-in with the scale animation. |
||
|
|
ff749b8705 |
fix(security): verify CDN script integrity before inlining (#3822)
* fix(security): verify CDN script integrity before inlining * fix(producer): normalize SRI algorithm case * fix(producer): abort compilation on integrity mismatch * fix(compiler): preserve nested script integrity requirements * fix(compiler): defer local inlining until integrity is known |
||
|
|
3a7fcd10e0 |
feat(registry): add 25 image carousel blocks (#3790)
* feat(registry): add 25 image carousel blocks (5 families × 5 variants) Five carousel families, each with 5 style variants: - Orbit (1–5): image cards on a spinning 3D Fibonacci sphere - Path (1–5): cards following animated CSS motion paths - Circle (1–5): circular carousel layouts - Vision (1–5): Apple Vision-style spatial presentations - Text Circle (1–5): circular carousels with text overlays All blocks are 1920×1080 at 6s, with 12–24 configurable image slots. Includes catalog preview thumbnails for each block. Co-Authored-By: Jake Moran <jake.moran@heygen.com> * refactor(registry): host carousel block images on the CDN The registry is served straight out of this repository (DEFAULT_REGISTRY_URL points at raw.githubusercontent.com), so every byte a block ships is permanent history. The 25 carousel blocks added 421 JPEGs, 27.7 MiB in a checkout, and made the diff 472 files. Only 23 of those images were distinct: the same 12-24 placeholders were copied into every block. files[] entries gain an optional `url`. When set, the installer fetches the bytes from there instead of joining the registry base. `path` does not change and still says where the file lands relative to the item, so composition HTML, target mirroring and `hyperframes add` behave exactly as before. Keys are content-addressed, so the 396 manifest entries resolve to 23 objects, and a changed image gets a new URL rather than a stale one cached behind `immutable, max-age=31536000`. The catalog preview renderer copies an item's directory and renders it, so it needs the same materialisation step. Without it the preview draws every card blank and reports success, which is worse than failing. Also drops registry/catalog/, 25 hand-made thumbnails referenced by nothing; catalog previews are rendered by CI and served from docs/images/catalog. Verified: all 23 objects return 200 from the CDN with hashes matching their keys; `hyperframes add carousel-orbit-1` against a local registry installs 24 real JPEGs; the preview render produces the album art, and produces blank cards when the fetch step is removed. * style(registry): format the carousel composition HTML `oxfmt --check .` covers the whole tree, and these 25 files were never run through it. The pre-commit hook only formats staged files, so nothing local caught it. * feat(catalog): publish the carousel blocks without republishing their images These 25 blocks had no Catalog page. Every other item in the registry has one, so they shipped invisible: installable by name, unfindable by browsing. Generating them naively undid the change they were added by. The Catalog payload copies an item's assets into docs/public/, which is tracked, so the 396 images this PR just removed came back as 43 MB one directory over — worse than the 3 MB they started as, because each block got its own copy. The copy exists because these compositions assemble `img.src` at run time out of a variable value, so there is no `src="..."` in the markup for the payload's asset scan to resolve. An unpredictable path can only be satisfied by serving every file beside it, which is what `needsOwnDirectory` asks for. An absolute URL needs no directory: the scan already skips any `https:` reference. So for the payload path only, hosted files are left undownloaded and the composition's variable defaults are rewritten to their URLs. The preview renderer still downloads them, because it paints real frames and a missing file is a blank card. The explorer posts every value to the preview frame on mount, including untouched ones, so the page's variable list carries the URLs too. Left as local paths they would have overridden the payload's own defaults and asked the frame for a file that was deliberately never published. Result: 25 pages, 25 payloads, zero bytes of image added. Verified: a spike item declaring no assets at all rendered its 24 covers from the CDN, proving the variable-default path; payload generation for a carousel block now writes no item directory and no shared asset; the preview render still produces the album art. mint validate and mint broken-links pass on the new pages. test:scripts is green. * refactor(catalog): split the hosted-asset step out of prepareProjectDir Two functions rather than one: finding the composition and rewriting its variable defaults are separate jobs, and inlining the mode branch pushed prepareProjectDir past the complexity gate it was already sitting on. Behaviour is unchanged. Re-verified both paths after the split: the payload for a carousel block still writes no item directory and no shared asset, and the preview render still produces the album art. * feat(catalog): give the carousels their own shelf 25 image carousels landed in Showcases and were 53% of it, so the scenes that shelf exists for disappeared underneath them. That is the same shape the 24 editor themes made, and it gets the same fix they got. Keyed on the first tag, which is this file's stated grouping rule, rather than on the name. `screen-flow-carousel` leads with `product-demo` and stays on the shelf that says what it is for; a future carousel that is not named `carousel-*` still lands here. Showcases 47 -> 22, Carousels 25, and no existing item changed shelf. * fix(registry): centre the circle-5 carousel path in its composition Its ring was centred at x=3832.6 in a 3840-wide composition, so it sat on the right edge and most of it fell outside the frame. Only a few cards were ever visible, cropped, with two thirds of the composition empty. The exported path carried absolute coordinates from a layout that was never recentred. Shifting the four vertices by (-1912.597, -4.340) puts the ring on the composition centre. Handles are relative, so only the anchor points move and the shape is unchanged. carousel-text-circle-5 shares the identical path and had the identical fault. The other three circle variants sit within 12% of centre, which reads as authored placement rather than the same bug, so they are left alone. * fix(catalog): rebuild the circle-5 payloads after recentring the path The Catalog preview plays the payload, not the composition on disk, so recentring the source changed nothing a reader sees. The payload still carried the old vertices and the ring still hung off the right edge of the frame. Verified the consumer this time, not just the producer: both payloads now resolve to a path centre of x=1920. The other 23 rebuild byte-identical, so the formatting pass did not reach them. * feat(catalog): promote Carousels to its own section It was a shelf inside Scenes & demos, which is where a scene type belongs by kind but not by weight. At 25 items it is larger than Data & charts (17) and Blocks (13), each of which is already a section holding a single shelf, so the catalog's own precedent puts it one level up. Pulling it out also takes the largest section in the catalog from 120 items to 95, which is the reason the shelf was added in the first place. The two circle-5 pages change because their embedded source block carries the recentred path; nothing else in them moved. --------- Co-authored-by: Jake Moran <jake.moran@heygen.com> Co-authored-by: Miguel Angel Simon Sierra <miguel.sierra@heygen.com> |
||
|
|
aab3bfcd10 | fix(registry): clear flowchart selection after click-away (#3262) | ||
|
|
254fe4bd8a |
feat(registry): say what a promoted template's slots mean, not where they sit (#3562)
* feat(registry): say what a promoted template's slots mean, not where they sit
Every declaration described its slot by position — appName was "small credit in the corner
of every banner" — and every editing contract described the supplied brand only by what it
may not touch, chatgpt-exchange going as far as "only the subject discussed inside the
conversation". An agent filling those slots has nothing telling it that the video is an
advertisement for that brand, and remixes shipped with an invented brand instead of the
real one.
Adds `portrays` to the 35 identity-bearing variables across seven templates: what the slot
stands for, as a list, kept orthogonal to `role` (which says which aspect of the
composition a knob affects, not what a value means to a viewer). Rewrites those variables'
descriptions to state meaning, and gives every TEMPLATE.md the affirmative sentence it was
missing. notes-reveal now records honestly that it declares no identity slot at all, which
is why a remix of it cannot carry a brand.
`portrays` is optional metadata: rendering ignores it, the promoted contract still projects
{id, type, role}, and packages that predate it keep working. Variable ids, types, roles,
labels, and defaults are untouched, so installs and renders see the same contract.
* feat(notes-reveal): give the template somewhere to say whose ad it is
This was the one promoted template that declared no identity slot at all, and the
previous commit could only record that honestly. A remix of it could not name the
advertiser, could not carry its domain, and had nowhere to place its mark — so the
ad it produced was unattributable no matter how good the copy was.
The closing card ends at y=1708, leaving 212px of dotted scene. A sign-off strip
lives there: `brandLogo` (portrays subject_logo) above `brandDomain` (portrays
subject_domain), in the card's own plum and Courier Prime. Putting it outside the
card means the hand-lettered headline and the checklist keep the geometry they were
designed with — verified by `hyperframes check`, which reports the same 0 errors and
the same warning counts as origin/main, with one more contrast check (367/367 AA).
No name slot: this template has no text slot that can hold a bare brand name.
`cardTop`'s marker line continues into the fixed words "OF ONE FILE", and a name set
beside a wordmark reads twice — rendering it proved that before this was committed.
The mark plus the domain carry the attribution, which is what the other end-card
templates do.
Rendered to check rather than assumed: the packaged default reads as the HyperFrames
wordmark over hyperframes.heygen.com, and a resolved symbol mark (Notion's cube over
notion.com) sits correctly in the same strip.
* fix(notes-reveal): make the note the ad, and stop the headline leaving the card
Three defects, all visible in the reported gamma remix.
The seven body lines carried their characters as pre-split `<span class="ch">`
elements so the reveal animation could address them by id. That made the note
permanently unwritable: 46% of the 24.9s runtime, and the only place the note
says anything, was fixed copy about making videos with HyperFrames -- shipped
inside an advertisement for a Dutch DIY chain. The spans are now built at
runtime from seven declared variables, keeping the same ids, so the animation
is untouched and the copy is the brand's.
`cardMid` and `cardBottom` ("OF", "ONE FILE") were literals with no variable at
all, so a remix could only ever change the first of the card's three lines.
Every text slot sat at a fixed left edge on one line, guarded only by a
character cap -- the wrong unit. `cardTop` allowed 12 characters; "THE POWER"
(9) fits and "CREATE DECKS" (12) does not, which is why both reported gamma
remixes shipped with the closing headline over the edge of the card. Slots are
now fitted to their box at render time, shrinking type only as far as needed and
never past the designed size. The card headlines also carried
`data-layout-allow-overflow`, switching off the one check that would have caught
it; those waivers are gone. The note's title and body keep theirs, because
`#note-body` is translated across the whole scene and leaves the canvas by
design.
Timing is preserved rather than regenerated: each body line is revealed inside
the frame window the packaged rhythm already used, so `JUMPS` and the hand-tuned
`SCROLLS` curves stay valid and the composition is still 24.867s for any length
of copy. A line whose length matches the packaged copy keeps its rhythm frame
for frame.
Verified: rendered with the packaged defaults, SSIM 1.000000 across all 746
frames against the previous template -- the design is bit-identical. Rendered
with the gamma values that overflowed, and with a stress set of long and
one-word values: both stay inside the card and the note, both still 24.867s.
`hyperframes check` passes, with layout warnings down from 28 to 8 and no new
issue class. Note that `check` takes no variable values, so it guards the
template's defaults and the fit is what guards a remix.
* fix(notes-reveal): make the fit floor and the character caps agree
A cap that admits a string still too wide at the floor font size puts the text
back outside its box -- the exact bug the fit was added for. With the floor at
0.6 and the title cap at 40, a 40-character title still overflowed `#note-body`
by 30px, because "Things nobody told me" is 21 characters and 900px at 88px, so
42.9px per character, and 40 characters need to reach about 0.45 of the design
size to fit.
Floor to 0.45 and the caps in: titles 34, body lines 52, cardTop and cardBottom
16. Every cap is now satisfiable with room to spare, and the common case stays
far from the floor.
Found by probing all eight promoted templates with values about 1.8x the length
of their packaged defaults, which is the length a real remix produces; short
sentinel values only ever prove a slot is wired.
Defaults are unaffected: SSIM 1.000000 across all 746 frames, still 24.867s,
`hyperframes check` still passes.
* fix(exchange): scroll the composer to follow the caret
`prompt` is an editable slot in both templates, but `.comp-text` is a
fixed-width single line with `white-space: pre` inside a composer that clips,
and the packaged prompt already reaches the mic -- it has no headroom at all.
A longer prompt therefore slid under the mic and the send button and lost its
tail, with the caret hidden behind them: the remix read as having typed into
nowhere, cut mid-word.
Both now scroll the typed text left to keep the caret in view, the way a real
single-line input does, on the very per-character times the reveal already uses.
The limit is measured to the opaque round button rather than the mic, because the
packaged prompt's caret already sits 2px past the mic's left edge in
chatgpt-exchange -- measuring to the mic would scroll the packaged composition.
Shrink-to-fit was the other option and it is worse: with no headroom it shrinks
the packaged prompt too, and it still cannot absorb a prompt twice as long
without unreadable type. It was tried, measured, and dropped.
A prompt that fits emits no keyframes and the element is never handed to GSAP at
all -- even a zero translate stamps a transform, which changes how the text
rasterizes.
claude-exchange puts this inside its existing `build()`, which already re-runs on
`document.fonts.ready`, so it measures the final font. chatgpt-exchange builds
once, so its scroll keyframes are kept in a list and rebuilt on the same event.
Verified on the packaged defaults: claude-exchange SSIM 1.000000 across 1284
frames. chatgpt-exchange is not frame-deterministic -- rendering it twice
unchanged gives 0.999959 with 249 frames under 0.9999, and before-vs-after gives
0.999967 with the same 249 -- so the change sits inside its own noise. Durations
unchanged at 14.9s and 21.4s. `hyperframes check` reports byte-identical results
to the unchanged templates.
* fix(notes-reveal): fit the checklist cells instead of counting their characters
The three checklist rows were the only text slots with no width fit, so
their character cap was doing the job a measurement should: it was set to 8
because that was the widest the packaged strings needed, and a gamma.com
remix delivered "DIRECT MEE" and rendered "DIRECT M".
Fit the labels and values like every other text slot -- dividing the
label's scaleX(0.86) back out of its column, since fitWidth measures the
untransformed width -- and return the cap to a backstop at 18.
"DIRECT MEE" needs no shrink at all: it ends at x=694 against a tick at
x=715, so the truncation was never about room. Measured: the packaged
defaults still render at their authored 44px and 50px, and all three values
clear the tick.
* fix(chatgpt-exchange): nest the caret scroll so its rebuild can be undone
`tl.to()` returns the TIMELINE, not the tween it created, so `caretScrollTweens`
held 49 references to the master timeline. The fonts-ready rebuild then called
`.kill()` on the master timeline 49 times and re-added its keyframes on top of
the ones it meant to replace. Measured with an overflowing prompt:
master timeline detached from the global timeline (tl.parent -> null)
scroll tweens on #cge-typed: 49 before the rebuild, 98 after
49 timestamps left with two competing tweens on the same property
GSAP's `Animation.kill()` interrupts and detaches rather than killing children,
so the composition kept rendering under an explicit seek -- which is why the
packaged-defaults SSIM check passed and never saw this. It is still wrong: the
timeline is off the ticker, and the stale keyframes were measured against the
fallback font.
Only an overflowing prompt reaches it, i.e. exactly the remix the scroll exists
for. The packaged prompt emits no steps, so nothing is created and nothing is
killed.
Now one nested child timeline holds the steps: a real object that can be killed
and replaced, and killing it cannot reach `tl`. Children sit at the same absolute
times and the nest is added at 0, so the motion is unchanged.
Verified: overflowing prompt holds at 49 scroll tweens across the rebuild with no
duplicated timestamps and the timeline stays attached. Packaged defaults against
the pre-feature baseline -- SSIM 0.999998 over 894 frames, 13 frames below
0.9999 against a 249-frame noise floor for this template, duration 14.9s
unchanged.
Found by Magi in review of #3562.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat(lint): catch a timeline's return value being treated as a tween
Magi asked for a regression that makes the caret-scroll failure unable to return.
A render fixture cannot be that guard, and neither can a unit test. Both were
measured before choosing this:
- The unit-test DOM has no layout. In happy-dom `getBoundingClientRect()` is
{left: 0, width: 0} and offsetWidth/offsetLeft are 0, so the template's own
guard computes limit = -8 and returns before collecting a single keyframe,
whatever the prompt length. `document.fonts` does not exist there either, so
the rebuild never runs. Both preconditions are unreachable -- the test would
pass on the broken code and the fixed code alike. Rames flagged this; verified.
- A render fixture would not fail either. Rendering the long prompt through the
real pipeline on the broken vs fixed template gives SSIM 0.999994 over 894
frames with ZERO frames below 0.999. The renderer drives the composition by
explicit seek, and the duplicated keyframes are added last so they win, which
is exactly why `.kill()` on the master timeline stayed invisible. A golden mp4
would have cost a fixture and a shard and guarded nothing.
What is testable is the root cause, and it is a static property: `tl.to()` returns
the TIMELINE, `gsap.to()` returns a Tween, and the two read identically. So the
guard is a lint rule, which needs no DOM, fires on the source, and covers every
composition in the registry rather than this one template.
The rule keys on the timeline handles `collectTimelineVarNames` already finds,
which is what keeps `gsap.to()` and `Array.from()` out of it. Pushing a timeline
return into an array is flagged outright -- that is the collect-to-discard shape.
A bound return is flagged only once something tween-scoped is aimed at it, so a
pointless-but-harmless capture stays quiet.
Verified against the real files, not just synthetic ones: the pre-fix
chatgpt-exchange raises exactly one error and names the offending line; the fixed
file is clean. Across all 642 HTML files under registry/ and examples/ the rule
finds nothing, and lint output on the eight promoted blocks is identical to
origin/main error-for-error and warning-for-warning, so error severity costs no
existing template. 540 lint tests pass, 6 of them new.
Requested by Magi in review of #3562; unit-test DOM measurement by Rames Jusso.
|
||
|
|
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> |
||
|
|
71e8e92ae9 | fix: document safe template default editing | ||
|
|
1e11608d44 | fix: expose promoted template media slots | ||
|
|
dac8f9f912 |
feat: add promoted template edit contracts (#3407)
* feat: add promoted template edit contracts * fix: address template contract review feedback |
||
|
|
b9a4dfcbe5 |
feat(registry): add avatar promo and Slack notification templates (#3279)
* feat(registry): add avatar promo and Slack notification templates * fix(registry): remove local emoji font dependency * fix(docs): regenerate Slack template catalog source |
||
|
|
0474d4ae26 | feat(registry): add ChatGPT and Claude exchange templates (#3244) | ||
|
|
93fbb6d7fd | fix(registry): normalize ai chat portrait canvas (#3235) | ||
|
|
c69692464e | fix(registry): align notes reveal typing (#3234) | ||
|
|
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. |
||
|
|
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. |
||
|
|
c86d4013f5 |
Revert "feat(registry): the video-primitive moves, documented and customisable (#3090)" (#3162)
This reverts commit
|
||
|
|
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.
|
||
|
|
79c4056a3f | docs: make Catalog visual and reproducible | ||
|
|
ce7d75dbaa |
fix(registry): animate mk card offsets with transforms, not top/left
mk-background and mk-clone-wall-transition tween the card's `top`/`left`. Layout properties snap to integer device pixels, so the move stutters under the seek-by-frame capture engine (lint: gsap_non_transform_motion). Both cards sit at top:0/left:0 in CSS, so the values carry straight over to x/y, and in clone-wall the later scale composes cleanly with the translate. Re-rendered both and diffed frames against the previous output — identical, as intended: this changes how the motion is computed, not how it looks. Adds scripts/lint-registry-items.mjs (bun run lint:registry-items), which mounts each item into a throwaway project and lints it. Registry items ship as `<name>.html`, so `hyperframes lint <dir>` fails with "No composition found" and these items had never actually been linted — which is how both errors reached main. Verified the script reproduces the original failure on the pre-fix source. Left as a local command rather than a CI gate for now; wiring it up needs two prior fixes, noted in the PR. |
||
|
|
d3d286a1a5 |
feat(registry): add beat-freeze-cut
Beat-driven speed ramp, freeze-frame hit, and hard cut for music-led promos and montages. Contributed as #2957; applied here as content so the whole catalog set lands together. Verified by rendering: 6s, clean, deterministic (the one Math.random hit is a comment above a fixed pattern array). Opaque root, so no demo.html is needed for its catalog preview. Co-authored-by: akiyoshisan1218 <akiyoshisan1218@users.noreply.github.com> |
||
|
|
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. |
||
|
|
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. |