Commit Graph
6 Commits
Author SHA1 Message Date
James Russo 0474d4ae26 feat(registry): add ChatGPT and Claude exchange templates (#3244) 2026-08-12 11:59:34 -07:00
WaterrrForever 4347af3fe1 feat(registry): add five vertical ad-template blocks and their motion primitives (#3217)
* feat(registry): add the notification-cascade block and banner-push-in component

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

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

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

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

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

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

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

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

Two chat primitives extracted from reference footage of real conversations.

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

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

Both demos pass the browser gate.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Mirrored catalog payloads and pages regenerated for the four touched items.
2026-08-11 16:03:42 -04:00
Miguel ÁngelandMiguel Angel Simon Sierra 1ae2067b8d feat(catalog): put the variables panel back, on payloads (#3199)
* feat(catalog): put the variables panel back, on payloads

The panel drove its preview by loading an .html from docs/public, a type the
host does not publish, so it showed an empty frame in production and was
parked when the catalog was re-landed.

It now mounts the same JSON payload the plain player uses and re-mounts it as
values change, injecting them as window.__hfVariables into the composition head
before any of its scripts run, which is where the runtime reads overrides from.
Doing it in the markup rather than after load is what stops the composition
initialising with the wrong values first.

172 items with variables get the panel back; the playhead carries across a
change so a tweak mid-shot does not jump back to frame zero.

* fix(docs): drop the unused url form and the needless escapes

* fix(docs): the panel cannot reference a binding beside the export

* feat(catalog): make importing an SVG the obvious move

A reader arrives at this control with a shape, not with path data, and the
panel asked for the coordinates first. Import is now the primary action in a
drop target you can see is a drop target, and the raw path sits behind a
disclosure for anyone who wants it.

* feat(cli): let a fruitless catalog search report the gap

An agent that searches by meaning and finds nothing worth installing knows
something we do not: the name of a move the catalog is missing. There was no
way to tell us, so that knowledge was lost at the end of every run.

hyperframes feedback --search-miss "<query>" --wanted "<the move>" records it.
It carries no rating, so it never lands in the rating metric, and it is a
separate deliberate command rather than something catalog --query does on its
own: plain search still sends nothing, which is what the CLI promises.

--rating stops being required at the arg level, since a miss has no rating to
give. The check moved into the run body, where an absent one is now handled
rather than crashing on undefined.

* feat(cli): carry tuned variable values into the install snippet

Someone who tunes a block on its catalog page had no way to keep those values:
the install command was the same one everybody gets, and the tuning stayed on
the page.

hyperframes add <item> --vars '<json>' now prints a mount element carrying
data-variable-values, so the values land where the block is used.

They ride on the host rather than being written into the installed file. That
keeps the composition on disk byte-identical to the registry's, so a later
reinstall can still tell an edit from an update, and it lets two mounts of the
same block carry different values.

* fix(catalog): serve the item's own directory so runtime paths resolve

Some compositions assemble their asset URLs at run time —
"compositions/components/" + texture + ".png" for the texture masks, a font the
compiler pulled into _remote_media — and no scan of the markup can see a string
that does not exist until a script concatenates it. Those items either rendered
black or were dropped to a video that had never been uploaded.

Each item that needs it now has its prepared directory published, and its
payload carries a <base> pointing at it, so any relative path the composition
invents resolves. caption-texture renders its masks again, and
variable-font-flex has a preview at all for the first time: its MP4 and poster
are both 403.

Both layouts are published, because which one a composition asks for differs
per item, and a directory only earns that if it is under 2 MB. The 12 MB
texture sheet keeps the recorded video it already had.

* fix(catalog): let the variables panel actually drive the composition

Every control on the panel was inert. The values reached the composition and
nothing repainted, because the payload had already been compiled: compiling
inlines a mounted component and resolves its variables into the markup and CSS,
so by the time a reader turns a knob there is nothing left to change.

An item that declares variables now ships uncompiled, keeping the mount the
runtime loads at run time, which is the only state where data-variable-values
still means anything. The component travels inline as a data URI rather than a
sibling file, because .html is the one type the docs host will not publish. The
demo's own pinned values come off, so the reader's choices reach the mount
instead of losing to the values the demo picked to show itself off.

Measured on the rendered frame rather than the DOM: green rgb(98,207,144),
blue rgb(6,6,199), violet rgb(177,147,230), and back to green.

docs/public/catalog drops from 48 MB to 35 MB along the way, since an
uncompiled payload carries far less than an inlined one.

* feat(catalog): keep variable changes in the url

A reader who tuned a piece lost it on reload, and had nothing to send anyone.
The values now live in the query string, scoped by composition id so two links
never read each other,and only the ones that differ from the defaults are
written, so changing one knob gives a short URL rather than every variable
spelled out.

replaceState rather than pushState: dragging a slider should not leave a trail
of history entries. An unreadable value is ignored rather than thrown, so a
truncated or hand-edited link opens the piece at its defaults.

* fix(catalog): only rewrite the url when a value actually changed

* refactor(catalog): memoise the declared defaults on their content

* feat(catalog): offer an install command carrying the tuned values

The Install block is generated before anyone touches a knob, so it can only
ever print the plain command. Someone who spent a minute tuning a piece copied
it and got the defaults back.

The panel now carries its own command in the Snippet tab, with --vars holding
exactly the values that differ. An untouched piece still offers the same short
command, so nothing gets noisier for the common case.

* fix(catalog): a piece with nothing to render is a skip, not a failure

caption-blend-difference is a stylesheet and a paragraph of prose — a class you
add to your own captions, with no standalone scene to show. The generator
treated that as a build failure, so every run ended by reporting something
broken when nothing was.

It now reports the shape it is and keeps its recorded video, which is the only
honest preview such an item has. A genuine render failure still throws.

* fix(catalog): restore variables from the url on a cold load

A shared link opened at the defaults. The first render happens on the server,
where there is no window to read the query string from, and React then hydrates
against that markup and never revisits it — so the values only appeared once you
touched a control.

The URL is read again after mount, which is the first moment it exists. The
value is also escaped once now rather than twice: URLSearchParams already
decodes on the way out, and decoding a second time turned an SVG path full of
percent-escapes into something that no longer parsed, besides doubling the
length of every link.

* fix(catalog): mount the preview with the values a link carried

The frame was built from the declared defaults and the shared values were
posted to it afterwards, which is too late for anything the composition reads
once at init: a path arrived after the mark had already been drawn from the
default one, so a link looked right in the panel and wrong on screen.

* feat(catalog): the install command follows the values you tuned

Copying the Install line gave the plain command back, because that block is
generated before anyone touches a knob and had no way to know what changed. The
tuned command only existed in the panel Snippet tab, which is not where anyone
looks for it.

The line now reads the same query string the panel writes, so the two agree
without either component knowing the other exists, and a shared link carries the
right command too. replaceState fires no event, so the panel announces its own
writes.

* fix(catalog): send a text variable to the preview once it is finished

Every other control in the explorer reports a whole value on every event: a
slider at any position is a position, a swatch is a colour. A text field is
not. Typing v3 into a badge posted v first, so the preview remounted and
rendered a composition built from half a word.

The post now waits while a text field has focus and goes out when the edit is
committed, with Enter or by clicking away. The field itself is unchanged and
still tracks every keystroke.

---------

Co-authored-by: Miguel Angel Simon Sierra <miguelangelsi07@gmail.com>
2026-08-10 22:40:01 -04:00
Miguel Ángel 9734578e60 feat(registry): bring back the video-primitive moves (#3169)
Restores the 208 catalog items reverted after their previews 404'd in
production, this time on the payload mechanism rather than the .html files
that caused the outage.

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

The variables explorer is parked, not restored: it drove its preview through
the same unpublished .html path, so it would have shown an empty frame. Items
that declare variables get the live player plus the static variables table, and
reconnecting the explorer to payloads is a follow-up.
2026-08-10 18:46:03 -04:00
Miguel Ángel 0f76305191 fix(catalog): keep the recorded video for canvas drawElement previews (#3171)
Eight blocks paint DOM into a canvas with ctx.drawElementImage(), which sits
behind chrome://flags/#canvas-draw-element. A reader without the flag gets a
preview that mounts, plays, and shows an empty canvas, which is what the 3D
device blocks were doing even after their models were embedded correctly.

The recorded video was captured by a renderer that has the capability, so it is
the only preview these items can honestly show. All eight have a published mp4
and poster.
2026-08-10 18:30:28 -04:00
Miguel Ángel 536165b6ef feat(catalog): play the real composition on catalog pages (#3168)
* feat(catalog): play the real composition on catalog pages

Catalog previews were uploaded MP4s, published by hand to the CDN. Each page
now embeds the composition itself, running in <hyperframes-player>, so a
preview is the block rather than a recording of it.

The composition is delivered as JSON under docs/public/catalog, and the player
is mounted inside an iframe. Both are forced: the docs host publishes only JSON
and images out of docs/public, and its MDX renderer strips unknown custom
elements, so a player written into the page never reaches the DOM and an .html
payload 404s in production.

Assets are inlined as data URIs, which sidesteps the file-type restriction for
fonts, scripts and models alike. 164 of 168 items build a payload; the rest
keep their MP4 and say so.

* chore: rebuild docs preview

* chore: route isolation probe

* chore: drop the route isolation probe page

* chore: probe which asset types the docs host publishes

* refactor(catalog): host preview assets instead of inlining them

Fonts were being base64'd into every payload that used them, so a handful of
files cost tens of megabytes in the repository to say the same thing over and
over. Assets are now written once, content-addressed, under
docs/public/catalog/assets and linked.

Which types can be hosted was settled by fetching one file of each from a
deployed preview: woff2, wav, mp4, svg and the image formats are published,
glb is not. Types the host drops still travel inside the payload, because a
link that 404s is worse than a larger payload.

Also fixes the four type errors the scripts typecheck caught, all of them
unchecked index access on a split() result.

* refactor(catalog): share preview assets instead of repeating them

* style: format the asset module
2026-08-10 17:54:48 -04:00