diff --git a/docs/experiments.html b/docs/experiments.html new file mode 100644 index 0000000000..d1c3291572 --- /dev/null +++ b/docs/experiments.html @@ -0,0 +1,120 @@ + + + + + + The experiments: ten Wave M candidates + + + +
+

Wave M experiments / demand, frame study, and the scout

+

The experiments

+

Ten more candidates: the texture registers from Codrops, gmunk's machine vision, the type crafts, and the time laws. Watch, then choose.

+
+
+

ascii-render-pass

the texture king
+
+

A scene renders as living ASCII, resolving from coarse glyph noise to a legible fine grid.

+
+
+

ordered-dither-pass

premium lofi
+
+

Bayer dithering carries the scene from pure 2-tone noise to clean, or dissolves it the other way.

+
+
+

segmentation-flood

after gmunk
+
+

Machine-vision masks flood the subject in scanline steps, brackets and labels landing per region.

+
+
+

slit-scan-reveal

time as material
+
+

Every row of the frame lives at a different moment; the subject smears through time until now catches up.

+
+
+

variable-font-flex

breathing type
+
+

A word lands by inflating through real weight and width axes while its box never moves.

+
+
+

stitched-text-draw

embroidered
+
+

Letters sew themselves on: stitch dashes, a leading needle, a thread tail that settles.

+
+
+

ink-bleed-reveal

liquid
+
+

Ink blobs bloom through paper, merge like liquid, and resolve into the mark.

+
+
+

particle-text-dissolve

both directions
+
+

Text condenses from a particle cloud behind a wipe front, or scatters back into one.

+
+
+

modal-morph

the open-it-up beat
+
+

A small card grows into a full panel, its contents re-flowing continuously the whole way.

+
+
+

stop-motion-cadence

hand-made time
+
+

A collage throw at 8-12 quantized frames per second: squash on hits, boil on edges, sparkles that live three frames.

+
+
+
+ + + + + + diff --git a/registry/components/ascii-render-pass/README.md b/registry/components/ascii-render-pass/README.md new file mode 100644 index 0000000000..088e77dee8 --- /dev/null +++ b/registry/components/ascii-render-pass/README.md @@ -0,0 +1,75 @@ +# ascii-render-pass + +Wave M experiment (texture). A slotted scene renders as live ASCII: the +source is rasterized once at mount to an offscreen canvas, a luminance +summed-area table is built from it, and every frame draws one glyph per +cell from a density ramp charset. Resolution animates coarse to fine on a +stepped pitch ladder (5 steps, powers of sqrt(2)) at `resolve_at`, then +holds legible-fine. 4s authored, elastic HOLD (never time-scaled). + +## Mounting + +```html +
+``` + +## Scene slot + +The source scene is a slot. Place an inert template anywhere in the HOST +page: + +```html + +``` + +The slot is rasterized through an SVG foreignObject snapshot, so content +must be self-contained: inline styles or rules carried by host ` + +
+
+ +
+
+ + + + + + + diff --git a/registry/components/ascii-render-pass/demo.html b/registry/components/ascii-render-pass/demo.html new file mode 100644 index 0000000000..9c34ddaad5 --- /dev/null +++ b/registry/components/ascii-render-pass/demo.html @@ -0,0 +1,126 @@ + + + + + + + ASCII Render Pass Demo + + + + + +
+
+
+ + + + + diff --git a/registry/components/ascii-render-pass/registry-item.json b/registry/components/ascii-render-pass/registry-item.json new file mode 100644 index 0000000000..1c501b190c --- /dev/null +++ b/registry/components/ascii-render-pass/registry-item.json @@ -0,0 +1,81 @@ +{ + "$schema": "https://hyperframes.heygen.com/schema/registry-item.json", + "name": "ascii-render-pass", + "type": "hyperframes:component", + "title": "ASCII Render Pass", + "description": "A slotted scene renders as live ASCII: a canvas samples the source's luminance grid (rasterized once at mount) and draws glyphs from a density ramp charset; resolution steps coarse to fine as the piece resolves, then holds legible-fine.", + "tags": ["motion-primitive", "experiment", "texture", "ascii", "quantize"], + "jobs": ["reveal"], + "family": "effects", + "profile": "texture", + "evidence": "Wave M M1: Codrops 'Efecto: Building Real-Time ASCII and Dithering Effects with WebGL Shaders' (2026-01-04), the coded-motion ASCII wave from the wild scout", + "syncPoints": [{ "id": "resolve", "phase": "in", "offset": 1.4 }], + "files": [ + { + "path": "ascii-render-pass.html", + "target": "compositions/components/ascii-render-pass.html", + "type": "hyperframes:snippet" + } + ], + "variables": [ + { + "id": "charset", + "type": "string", + "role": "style", + "label": "Charset", + "description": "Density ramp, dark to light. A leading space keeps dark cells empty.", + "default": " .:-=+*#%@" + }, + { + "id": "grid", + "type": "enum", + "role": "style", + "label": "Grid", + "description": "Final cell pitch the resolve lands on.", + "default": "med", + "options": [ + { "value": "coarse", "label": "Coarse" }, + { "value": "med", "label": "Medium" }, + { "value": "fine", "label": "Fine" } + ] + }, + { + "id": "resolve_at", + "type": "number", + "role": "timing", + "label": "Resolve at", + "description": "Seconds from mount start when the coarse-to-fine resolve begins.", + "default": 1.4, + "min": 0.6, + "max": 10, + "step": 0.1, + "unit": "s" + }, + { + "id": "accent", + "type": "enum", + "role": "style", + "label": "Accent", + "description": "Glyph ink: green rides --brand, blue rides --accent, violet rides --accent-2.", + "default": "green", + "options": [ + { "value": "green", "label": "Green" }, + { "value": "blue", "label": "Blue" }, + { "value": "violet", "label": "Violet" } + ] + }, + { + "id": "exit", + "type": "enum", + "role": "timing", + "label": "Exit", + "description": "How the frame leaves. None holds the fine render.", + "default": "none", + "options": [ + { "value": "none", "label": "None" }, + { "value": "fade", "label": "Fade" }, + { "value": "up", "label": "Up" } + ] + } + ] +} diff --git a/registry/components/ink-bleed-reveal/README.md b/registry/components/ink-bleed-reveal/README.md new file mode 100644 index 0000000000..b2834dc507 --- /dev/null +++ b/registry/components/ink-bleed-reveal/README.md @@ -0,0 +1,48 @@ +# ink-bleed-reveal + +Liquid ink blooms through paper to reveal a mark: several ink blobs bleed outward from the drop point, merge like liquid under the gooey filter (feGaussianBlur + a hard feColorMatrix alpha threshold), then the puddle contracts and dies as the crisp mark resolves beneath it. A seeded, static paper-grain overlay (feTurbulence, fixed seed) rides the whole piece. + +4s authored, elastic HOLD, exit `none` by default. Wave M experiment (watch page only, not on the hero shelf). + +## Variables + +| id | type | default | notes | +| --- | --- | --- | --- | +| `blobs` | number | `5` | How many ink blobs bleed and merge, clamped 4 to 6. | +| `accent` | enum | `green` | Ink tint: green rides `--brand`, blue rides `--accent`, violet rides `--accent-2`. The ink body is the accent mixed toward `--fg` so it reads as pigment. | +| `exit` | enum | `none` | `none` holds until the cut; `fade` departs opacity-only; `up` rises out. | + +## Mark slot + +The mark is a slot. Place an inert template anywhere in the HOST page (templates never render, and the runtime wipes the host clip's own children on mount, so the slot lives at document level): + +```html + +``` + +With no slot, the primitive renders a token monogram: one display glyph in an ink-colored ring, contract tokens only. Light paper is the home register, but every color rides a token so themes restate it. + +## Mount + +```html +
+``` + +Elastic root: no `data-width`/`data-height`; it fills whatever box the host clip gives it. Timeline registers under the literal `ink-bleed-reveal` key. + +## Notes + +- The gooey read comes from the blur-to-threshold ratio, not the blur alone: a Gaussian blur (radius scaled from the host box at mount) softens the blob silhouettes together, then the colormatrix threshold (alpha slope 22, intercept -9) snaps the merged alpha back to a crisp liquid edge. Skipping the threshold step yields smudge, not goo. The chain is the classic SVG `feGaussianBlur` + `feColorMatrix` recipe, computed in a low-resolution canvas work buffer each repaint: Chrome's live SVG/CSS filter raster patches damaged tiles with seek-history-dependent seam antialiasing, while a cleared, fully redrawn canvas lands byte-identical frames. +- Closed-form field, no physics: one per-blob parameter table is built once from fixed LCG seed `0x1b1eedca`; every painted frame positions each blob as a pure function of (table row, timeline time) inside the anchor tween's `onUpdate`. Zero per-blob tweens, no velocities, no integration, so eventful seeks (`suppressEvents=false`) land identical frames in any order and either direction. +- By the end of IN every blob is dead and the goo canvas is one clear rect; the paper grain is painted by a fixed-seed `feTurbulence` and never animated. The HOLD is therefore byte-still: the crisp mark on quiet paper. +- Cost note: each repaint blurs and thresholds a third-resolution buffer (one `getImageData` readback per frame); fine at watch-page sizes, but avoid mounting several instances of this primitive on one frame. diff --git a/registry/components/ink-bleed-reveal/demo.html b/registry/components/ink-bleed-reveal/demo.html new file mode 100644 index 0000000000..4bfebff46b --- /dev/null +++ b/registry/components/ink-bleed-reveal/demo.html @@ -0,0 +1,118 @@ + + + + + + + Ink Bleed Reveal Demo + + + + + +
+
+
+ + + + + diff --git a/registry/components/ink-bleed-reveal/ink-bleed-reveal.html b/registry/components/ink-bleed-reveal/ink-bleed-reveal.html new file mode 100644 index 0000000000..5960022147 --- /dev/null +++ b/registry/components/ink-bleed-reveal/ink-bleed-reveal.html @@ -0,0 +1,435 @@ + + + + + + Ink Bleed Reveal + + + + + diff --git a/registry/components/ink-bleed-reveal/registry-item.json b/registry/components/ink-bleed-reveal/registry-item.json new file mode 100644 index 0000000000..fd427587da --- /dev/null +++ b/registry/components/ink-bleed-reveal/registry-item.json @@ -0,0 +1,59 @@ +{ + "$schema": "https://hyperframes.heygen.com/schema/registry-item.json", + "name": "ink-bleed-reveal", + "type": "hyperframes:component", + "title": "Ink Bleed Reveal", + "description": "Liquid ink blooms through paper under a gooey blur-plus-threshold filter, merges, then contracts as a crisp slotted mark resolves beneath it, with static seeded paper grain riding the result.", + "tags": ["motion-primitive", "texture", "reveal", "gooey", "svg-filter", "deterministic", "experiment"], + "jobs": ["reveal"], + "family": "texture", + "profile": "materialize", + "evidence": "Wave M7 experiment: liquid-threshold gooey recipe (wild-scout #3, Jakubowski / Ethode referents), closed-form blob field, no physics state", + "syncPoints": [{ "id": "mark-locked", "phase": "in", "offset": 2.8 }], + "files": [ + { + "path": "ink-bleed-reveal.html", + "target": "compositions/components/ink-bleed-reveal.html", + "type": "hyperframes:snippet" + } + ], + "variables": [ + { + "id": "blobs", + "type": "number", + "role": "style", + "label": "Blobs", + "description": "How many ink blobs bleed and merge (4 to 6).", + "default": 5, + "min": 4, + "max": 6, + "step": 1 + }, + { + "id": "accent", + "type": "enum", + "role": "style", + "label": "Accent", + "description": "Ink tint: green rides --brand, blue rides --accent, violet rides --accent-2.", + "default": "green", + "options": [ + { "value": "green", "label": "Green" }, + { "value": "blue", "label": "Blue" }, + { "value": "violet", "label": "Violet" } + ] + }, + { + "id": "exit", + "type": "enum", + "role": "timing", + "label": "Exit", + "description": "Optional departure. Default none: the revealed mark holds until the frame cuts.", + "default": "none", + "options": [ + { "value": "none", "label": "None" }, + { "value": "fade", "label": "Fade" }, + { "value": "up", "label": "Up" } + ] + } + ] +} diff --git a/registry/components/modal-morph/README.md b/registry/components/modal-morph/README.md new file mode 100644 index 0000000000..332bb05d96 --- /dev/null +++ b/registry/components/modal-morph/README.md @@ -0,0 +1,53 @@ +# modal-morph + +The shared-element grow (Wave M9 experiment): a small card expands into a full panel with its children re-flowing continuously. FLIP-style: both layouts are real DOM measured exactly once at mount; the morph interpolates the container rect and applies it as transform only (translate + scaleX/scaleY, never width/height tweens) while keyed children counter-scale against the container and travel onto the lerp of their own two measured rects, all as pure functions of timeline time. + +4s authored, elastic HOLD, exit `none` by default. + +## Variables + +| id | type | default | notes | +| --- | --- | --- | --- | +| `from_scale` | number | `1` | Multiplies the authored card footprint before the one-time measurement, clamped 0.5 to 1.5. | +| `expand_at` | number | `1.2` | Seconds after mount when the grow begins. | +| `register` | enum | `calm` | `calm` is a smooth inOut cubic; `snappy` is faster with a settle that never overshoots the target rect beyond 1.02. | +| `accent` | enum | `green` | green rides `--brand`, blue rides `--accent`, violet rides `--accent-2`. | +| `exit` | enum | `none` | `none` holds until the cut; `fade` departs opacity-only; `up` rises out. | + +## Card content slot + +Place an inert template anywhere in the HOST page: + +```html + +``` + +The slot content is cloned into BOTH the card body and the panel head, so every `data-morph-key` has a measured rect in each layout and re-flows between them during the morph. Slot content without any keys is wrapped and morphs as one block. With no slot, the primitive renders token defaults (accent dot, display title line, mono meta line) that re-flow from a stacked card to an inline panel header row. + +Panel furniture (skeleton tile grid, footer chips) is panel-only and fades up over the back half of the morph. + +## Mount + +```html +
+``` + +Elastic root: no `data-width`/`data-height`; it fills whatever box the host clip gives it. Timeline registers under the literal `modal-morph` key. + +## Notes + +- The FLIP law: measure once, then transform-only. Container scale is `lerpRect(card, panel, ease(p))` against the panel's natural size; each matched child sets `scale = want / (home * containerScale)` and a translate expressed in the scaled container space, so content re-flows instead of stretching. +- The card and panel are separate authored layers that crossfade in the first 30% of the morph (both run the same rect math, mirrored), so each endpoint is pixel-crisp in its own styling. +- `snappy` uses a back-out ease with c1 = 0.7 (analytic peak 1.0176 of the rect delta), under the 1.02 cap. +- Deterministic: no Math.random, no wall clock, no incremental state; measurement happens synchronously at mount and every later frame is recomputed from `tl.time()` alone, so eventful seeks land identical frames in any order and either direction. diff --git a/registry/components/modal-morph/demo.html b/registry/components/modal-morph/demo.html new file mode 100644 index 0000000000..8783e878d2 --- /dev/null +++ b/registry/components/modal-morph/demo.html @@ -0,0 +1,86 @@ + + + + + + + Modal Morph Demo + + + +
+ +
+ + + + + diff --git a/registry/components/modal-morph/modal-morph.html b/registry/components/modal-morph/modal-morph.html new file mode 100644 index 0000000000..ec6d0c5f06 --- /dev/null +++ b/registry/components/modal-morph/modal-morph.html @@ -0,0 +1,526 @@ + + + + + + Modal Morph + + + + + diff --git a/registry/components/modal-morph/registry-item.json b/registry/components/modal-morph/registry-item.json new file mode 100644 index 0000000000..ed5a828dd6 --- /dev/null +++ b/registry/components/modal-morph/registry-item.json @@ -0,0 +1,76 @@ +{ + "$schema": "https://hyperframes.heygen.com/schema/registry-item.json", + "name": "modal-morph", + "type": "hyperframes:component", + "title": "Modal Morph", + "description": "A small card expands into a full panel, shared-element style: both layouts measured once at mount, the container morphs by transform only (never width/height tweens) while keyed children counter-scale and re-flow onto their own measured targets as pure functions of time.", + "tags": ["motion-primitive", "structure", "flip", "shared-element", "modal", "deterministic", "experiment"], + "jobs": ["demonstrate"], + "family": "structure", + "profile": "shared-element", + "evidence": "Wave M9 experiment: the FLIP shared-element grow (Codrops recipes, wild-scout)", + "syncPoints": [{ "id": "expand", "phase": "in", "offset": 1.2 }], + "files": [ + { + "path": "modal-morph.html", + "target": "compositions/components/modal-morph.html", + "type": "hyperframes:snippet" + } + ], + "variables": [ + { + "id": "from_scale", + "type": "number", + "role": "style", + "label": "From scale", + "description": "Multiplies the authored card footprint, clamped 0.5 to 1.5.", + "default": 1 + }, + { + "id": "expand_at", + "type": "number", + "role": "timing", + "label": "Expand at", + "description": "Seconds after mount when the card grows into the panel.", + "default": 1.2 + }, + { + "id": "register", + "type": "enum", + "role": "timing", + "label": "Register", + "description": "Morph feel. Snappy is faster with a settle capped at 1.02 of the target rect.", + "default": "calm", + "options": [ + { "value": "calm", "label": "Calm" }, + { "value": "snappy", "label": "Snappy" } + ] + }, + { + "id": "accent", + "type": "enum", + "role": "style", + "label": "Accent", + "description": "Accent color: green rides --brand, blue rides --accent, violet rides --accent-2.", + "default": "green", + "options": [ + { "value": "green", "label": "Green" }, + { "value": "blue", "label": "Blue" }, + { "value": "violet", "label": "Violet" } + ] + }, + { + "id": "exit", + "type": "enum", + "role": "timing", + "label": "Exit", + "description": "Optional departure. Default none: the settled panel holds until the frame cuts.", + "default": "none", + "options": [ + { "value": "none", "label": "None" }, + { "value": "fade", "label": "Fade" }, + { "value": "up", "label": "Up" } + ] + } + ] +} diff --git a/registry/components/ordered-dither-pass/README.md b/registry/components/ordered-dither-pass/README.md new file mode 100644 index 0000000000..dd2daae7f4 --- /dev/null +++ b/registry/components/ordered-dither-pass/README.md @@ -0,0 +1,79 @@ +# ordered-dither-pass + +Wave M experiment (texture / transitions). Bayer-matrix ordered dithering +quantizes a slotted scene: the source is rasterized once at mount to a +reduced-resolution buffer (one dither pixel is about 3 display px, blitted +with image smoothing off), and every frame re-derives each output pixel +from (source pixel, Bayer threshold, the animated dither amount). With +direction `in` the image emerges from pure 2-tone noise (theme bg vs +accent) into posterized color and finally clean; `out` runs the ramp in +reverse and holds the noise. Ordered, parallel, frame-independent; never +error diffusion. 3.5s authored, elastic HOLD (never time-scaled). + +## Mounting + +```html +
+``` + +## Scene slot + +The source scene is a slot. Place an inert template anywhere in the HOST +page: + +```html + +``` + +The slot is rasterized through an SVG foreignObject snapshot, so content +must be self-contained: inline styles or rules carried by host ` + + + + +
+
+
+ + + + + diff --git a/registry/components/ordered-dither-pass/ordered-dither-pass.html b/registry/components/ordered-dither-pass/ordered-dither-pass.html new file mode 100644 index 0000000000..a2a707fc3c --- /dev/null +++ b/registry/components/ordered-dither-pass/ordered-dither-pass.html @@ -0,0 +1,485 @@ + + + + + + Ordered Dither Pass + + + + + diff --git a/registry/components/ordered-dither-pass/registry-item.json b/registry/components/ordered-dither-pass/registry-item.json new file mode 100644 index 0000000000..e745e5b3b2 --- /dev/null +++ b/registry/components/ordered-dither-pass/registry-item.json @@ -0,0 +1,84 @@ +{ + "$schema": "https://hyperframes.heygen.com/schema/registry-item.json", + "name": "ordered-dither-pass", + "type": "hyperframes:component", + "title": "Ordered Dither Pass", + "description": "Bayer-matrix ordered dithering quantizes a slotted scene (rasterized once at mount): the image emerges from pure 2-tone noise to clean, or dissolves the reverse. Stateless per frame, never error diffusion.", + "tags": ["motion-primitive", "experiment", "texture", "dither", "transition", "quantize"], + "jobs": ["reveal", "bridge"], + "family": "effects", + "profile": "texture", + "evidence": "Wave M M2: Codrops 'Building a Real-Time Dithering Shader' (Niccolo Fanton, 2025-06-04), the ordered-not-error-diffusion pass from the wild scout", + "syncPoints": [{ "id": "ramp", "phase": "in", "offset": 0.4 }], + "files": [ + { + "path": "ordered-dither-pass.html", + "target": "compositions/components/ordered-dither-pass.html", + "type": "hyperframes:snippet" + } + ], + "variables": [ + { + "id": "matrix", + "type": "enum", + "role": "style", + "label": "Matrix", + "description": "Bayer matrix size: 2 is the coarsest crosshatch, 8 the smoothest ordered grain.", + "default": "4", + "options": [ + { "value": "2", "label": "2 x 2" }, + { "value": "4", "label": "4 x 4" }, + { "value": "8", "label": "8 x 8" } + ] + }, + { + "id": "direction", + "type": "enum", + "role": "timing", + "label": "Direction", + "description": "in resolves noise to clean; out dissolves clean to noise and holds it.", + "default": "in", + "options": [ + { "value": "in", "label": "Noise to clean" }, + { "value": "out", "label": "Clean to noise" } + ] + }, + { + "id": "levels", + "type": "number", + "role": "style", + "label": "Levels", + "description": "Quantization levels per color channel during the dithered phase.", + "default": 2, + "min": 2, + "max": 6, + "step": 1 + }, + { + "id": "accent", + "type": "enum", + "role": "style", + "label": "Accent", + "description": "Light tone of the 2-tone noise phase: green rides --brand, blue rides --accent, violet rides --accent-2.", + "default": "green", + "options": [ + { "value": "green", "label": "Green" }, + { "value": "blue", "label": "Blue" }, + { "value": "violet", "label": "Violet" } + ] + }, + { + "id": "exit", + "type": "enum", + "role": "timing", + "label": "Exit", + "description": "How the frame leaves. None holds the final state.", + "default": "none", + "options": [ + { "value": "none", "label": "None" }, + { "value": "fade", "label": "Fade" }, + { "value": "up", "label": "Up" } + ] + } + ] +} diff --git a/registry/components/particle-text-dissolve/README.md b/registry/components/particle-text-dissolve/README.md new file mode 100644 index 0000000000..92605d0dea --- /dev/null +++ b/registry/components/particle-text-dissolve/README.md @@ -0,0 +1,38 @@ +# particle-text-dissolve + +Text assembles FROM a seeded particle cloud, or dissolves TO it: per-particle target positions are sampled ONCE at mount from the rendered text bitmap (the token-styled line is rastered to an offscreen canvas and read back with getImageData), then the field runs table-driven with zero per-particle tweens and one `onUpdate` painter. Direct donor: particle-image-reveal. + +4s authored, elastic HOLD, exit `none` by default. Wave M experiment (watch page only, not on the hero shelf). + +## Variables + +| id | type | default | notes | +| --- | --- | --- | --- | +| `text` | string | `Dissolve` | The line that assembles or dissolves. Sized to fit at mount (up to 86% of the stage width). | +| `direction` | enum | `in` | `in` assembles the line from the cloud and holds it crisp; `out` erases the line left to right into the cloud and holds empty. | +| `density` | enum | `med` | Particle count cap: `low` (900), `med` (1700), `high` (2800). | +| `accent` | enum | `green` | Text and particle color: green rides `--brand`, blue rides `--accent`, violet rides `--accent-2`. | +| `exit` | enum | `none` | `none` holds until the cut; `fade` departs opacity-only; `up` rises out. | + +## Mount + +```html +
+``` + +Elastic root: no `data-width`/`data-height`; it fills whatever box the host clip gives it. Timeline registers under the literal `particle-text-dissolve` key. + +## Notes + +- Bitmap sampling happens exactly once, synchronously at mount: the line is drawn to an offscreen canvas with the DOM line's computed font stack and a fitted pixel size, lit cells on a deterministic grid become targets, and the set is thinned to the density cap with fixed LCG seed `0x9d1550f7` (one draw per cell, grid order). The crisp DOM line locks to the same family and device-pixel size, so targets sit on the glyphs it draws. +- Every painted frame is a pure function of (table, timeline time): the anchor tween's `onUpdate` clears and repaints the whole field. No Math.random, no wall clock, no incremental state, so eventful seeks (`suppressEvents=false`, the engine's render path) land byte-identical frames in any order and either direction. +- The reveal (or erase) front sweeps left to right for both directions; the DOM line's clip-path wipe rides the same front. By the end of IN every particle's alpha is exactly zero: the HOLD canvas is one clear rect, holding either the crisp themable DOM line (`in`) or an empty stage (`out`). +- The canvas raster is sized once at mount from the host box and devicePixelRatio (capped at 2); the same host always yields the same raster and the same particle table. diff --git a/registry/components/particle-text-dissolve/demo.html b/registry/components/particle-text-dissolve/demo.html new file mode 100644 index 0000000000..b4afe75fa4 --- /dev/null +++ b/registry/components/particle-text-dissolve/demo.html @@ -0,0 +1,87 @@ + + + + + + + Particle Text Dissolve Demo + + + +
+
+
+ + + + + diff --git a/registry/components/particle-text-dissolve/particle-text-dissolve.html b/registry/components/particle-text-dissolve/particle-text-dissolve.html new file mode 100644 index 0000000000..bdf47f4ca6 --- /dev/null +++ b/registry/components/particle-text-dissolve/particle-text-dissolve.html @@ -0,0 +1,414 @@ + + + + + + Particle Text Dissolve + + + + + diff --git a/registry/components/particle-text-dissolve/registry-item.json b/registry/components/particle-text-dissolve/registry-item.json new file mode 100644 index 0000000000..d8caa2906c --- /dev/null +++ b/registry/components/particle-text-dissolve/registry-item.json @@ -0,0 +1,81 @@ +{ + "$schema": "https://hyperframes.heygen.com/schema/registry-item.json", + "name": "particle-text-dissolve", + "type": "hyperframes:component", + "title": "Particle Text Dissolve", + "description": "Text assembles from a seeded particle cloud (or dissolves into it): per-particle targets sampled once from the rendered text bitmap at mount, table-driven particles, zero per-particle tweens, one onUpdate painter.", + "tags": ["motion-primitive", "text-effects", "particles", "canvas", "deterministic", "experiment"], + "jobs": ["emphasize"], + "family": "text-effects", + "profile": "materialize", + "evidence": "Wave M8 experiment: particle-image-reveal donor (fixed-seed LCG, pure function of table and time), targets from an offscreen text-raster readback", + "syncPoints": [{ "id": "text-locked", "phase": "in", "offset": 2.8 }], + "files": [ + { + "path": "particle-text-dissolve.html", + "target": "compositions/components/particle-text-dissolve.html", + "type": "hyperframes:snippet" + } + ], + "variables": [ + { + "id": "text", + "type": "string", + "role": "content", + "label": "Text", + "description": "The line that assembles from (or dissolves into) the particle cloud.", + "default": "Dissolve" + }, + { + "id": "direction", + "type": "enum", + "role": "timing", + "label": "Direction", + "description": "in assembles the text from the cloud and holds it crisp; out erases the text into the cloud and holds empty.", + "default": "in", + "options": [ + { "value": "in", "label": "Assemble in" }, + { "value": "out", "label": "Dissolve out" } + ] + }, + { + "id": "density", + "type": "enum", + "role": "style", + "label": "Density", + "description": "Particle count cap for the field.", + "default": "med", + "options": [ + { "value": "low", "label": "Low" }, + { "value": "med", "label": "Medium" }, + { "value": "high", "label": "High" } + ] + }, + { + "id": "accent", + "type": "enum", + "role": "style", + "label": "Accent", + "description": "Text and particle color: green rides --brand, blue rides --accent, violet rides --accent-2.", + "default": "green", + "options": [ + { "value": "green", "label": "Green" }, + { "value": "blue", "label": "Blue" }, + { "value": "violet", "label": "Violet" } + ] + }, + { + "id": "exit", + "type": "enum", + "role": "timing", + "label": "Exit", + "description": "Optional departure. Default none: the end state holds until the frame cuts.", + "default": "none", + "options": [ + { "value": "none", "label": "None" }, + { "value": "fade", "label": "Fade" }, + { "value": "up", "label": "Up" } + ] + } + ] +} diff --git a/registry/components/segmentation-flood/README.md b/registry/components/segmentation-flood/README.md new file mode 100644 index 0000000000..c36c7c9b5f --- /dev/null +++ b/registry/components/segmentation-flood/README.md @@ -0,0 +1,74 @@ +# segmentation-flood + +Product demo / AI experiment primitive: a machine-vision read of a slotted +subject. Translucent accent segmentation masks FLOOD onto 2-4 regions in +scanline steps (rows arrive in 2-3 frame quanta: flood progress is +quantized to frame steps before any row is drawn), each completed region +gets a thin corner bracket plus a mono label chip, and a subtle HUD +flicker (a deterministic 2-frame luminance toggle) rides the pass, then +stops the instant the flood completes. Settles to a clean labeled read. +4.5s authored, elastic HOLD (never time-scaled). + +Reference feel: the gmunk segmentation sheets (red masks flooding under +HUD flicker), tokenized to the contract palette. + +## Mounting + +```html +
+``` + +## Subject slot + +The subject is a slot. Place an inert template anywhere in the HOST page: + +```html + +``` + +Direct `img` / `video` children are stretched to cover the stage; +arbitrary HTML fills it. A missing slot falls back to a token scene: a +surface field with muted shapes seated where the default regions land. + +## Variables + +| id | type | default | notes | +| -------- | ------ | ---------------------- | ------------------------------------------------------------------------- | +| regions | string | Figure, Signal, Ground | Comma label list; 2 to 4 used. One mask + bracket + chip per label. | +| flood_at | number | 0.8 | Seconds when the pass starts. Clamped so the pass and any exit fit inside the clip. | +| flicker | enum | on | on or off: the 2-frame HUD luminance toggle riding the pass. | +| accent | enum | green | green maps to --brand, blue to --accent, violet to --accent-2. | +| exit | enum | none | none holds the labeled read (frame roots own transitions); fade or up add a 0.45s departure. | + +## Choreography + +- 0.00s: the subject settles up quietly; no overlay. +- flood_at: region 1's mask starts flooding top to bottom in quantized + scanline rows (a brighter leading row, hairline gaps for texture); + regions stagger 0.35s apart, 1.0s of flood each. +- Each region completes: its corner brackets snap in and its mono chip + rises on the same beat. +- Flood end: the flicker window closes; brackets, chips, and masks hold as + one clean labeled read. +- HOLD: dead still, elastic with the clip duration. + +Sync point `flood` fires as the pass starts; align SFX to it. + +## Determinism + +Region geometry, per-region flood quanta (2 or 3 frames), and scanline row +counts come from one table computed once at build from fixed LCG seed +0x5e6f100d. One plain-object anchor tween feeds one painter that redraws +every mask row and the flicker wash from scratch as pure functions of the +timeline time; the flicker is on exactly when floor(frame / 2) is odd +inside the flood window. Eventful seeks land identical frames in any order +and either direction; the raster basis is the host box measured once at +mount. diff --git a/registry/components/segmentation-flood/demo.html b/registry/components/segmentation-flood/demo.html new file mode 100644 index 0000000000..2f8c1b3cc6 --- /dev/null +++ b/registry/components/segmentation-flood/demo.html @@ -0,0 +1,117 @@ + + + + + + + Segmentation Flood Demo + + + + + +
+
+
+ + + + + diff --git a/registry/components/segmentation-flood/registry-item.json b/registry/components/segmentation-flood/registry-item.json new file mode 100644 index 0000000000..0db38043fa --- /dev/null +++ b/registry/components/segmentation-flood/registry-item.json @@ -0,0 +1,76 @@ +{ + "$schema": "https://hyperframes.heygen.com/schema/registry-item.json", + "name": "segmentation-flood", + "type": "hyperframes:component", + "title": "Segmentation Flood", + "description": "Machine-vision read of a slotted subject: translucent accent segmentation masks flood on in quantized scanline steps, each region gets a thin bracket and mono label chip, a deterministic 2-frame HUD flicker rides the pass, then the labeled read settles clean.", + "tags": ["motion-primitive", "product-demo", "ai", "hud", "canvas", "deterministic", "experiment"], + "jobs": ["demonstrate"], + "family": "product-demo", + "profile": "machine-read", + "evidence": "Wave M3 experiment slate; gmunk segmentation contact sheet 2031767296331702694/sheet-02 (red masks flooding in scanline steps under HUD flicker), tokenized", + "syncPoints": [{ "id": "flood", "phase": "in", "offset": 0.8 }], + "files": [ + { + "path": "segmentation-flood.html", + "target": "compositions/components/segmentation-flood.html", + "type": "hyperframes:snippet" + } + ], + "variables": [ + { + "id": "regions", + "type": "string", + "role": "content", + "label": "Regions", + "description": "Comma-separated region labels; 2 to 4 are used, one translucent mask + bracket + mono chip each.", + "default": "Figure, Signal, Ground" + }, + { + "id": "flood_at", + "type": "number", + "role": "timing", + "label": "Flood at", + "description": "Seconds from mount start when the segmentation pass begins. Clamped so the pass and any exit stay inside the clip.", + "default": 0.8 + }, + { + "id": "flicker", + "type": "enum", + "role": "style", + "label": "Flicker", + "description": "Deterministic 2-frame HUD luminance toggle riding the flood pass.", + "default": "on", + "options": [ + { "value": "on", "label": "On" }, + { "value": "off", "label": "Off" } + ] + }, + { + "id": "accent", + "type": "enum", + "role": "style", + "label": "Accent", + "description": "Mask, bracket, and chip color: green rides --brand, blue rides --accent, violet rides --accent-2.", + "default": "green", + "options": [ + { "value": "green", "label": "Green" }, + { "value": "blue", "label": "Blue" }, + { "value": "violet", "label": "Violet" } + ] + }, + { + "id": "exit", + "type": "enum", + "role": "timing", + "label": "Exit", + "description": "Optional departure. Default none: the labeled read holds until the frame cuts.", + "default": "none", + "options": [ + { "value": "none", "label": "None" }, + { "value": "fade", "label": "Fade" }, + { "value": "up", "label": "Up" } + ] + } + ] +} diff --git a/registry/components/segmentation-flood/segmentation-flood.html b/registry/components/segmentation-flood/segmentation-flood.html new file mode 100644 index 0000000000..93566463e9 --- /dev/null +++ b/registry/components/segmentation-flood/segmentation-flood.html @@ -0,0 +1,513 @@ + + + + + + Segmentation Flood + + + + + diff --git a/registry/components/slit-scan-reveal/README.md b/registry/components/slit-scan-reveal/README.md new file mode 100644 index 0000000000..252faa4335 --- /dev/null +++ b/registry/components/slit-scan-reveal/README.md @@ -0,0 +1,60 @@ +# slit-scan-reveal + +Time experiment primitive: the frame's rows sample the subject at offset +times. An authored token mark (accent-ringed disc with an orbit dot) +sweeps in on ONE pure position function pos(t); a canvas painter draws the +frame as row (or column) slices, slice i showing the subject's state at +t - offset(i). Arrival smears through time, near bands leading, far bands +trailing; on the resolve cue the offsets collapse to zero with one eased +0.6s pass and the mark holds coherent. 4s authored, elastic HOLD (never +time-scaled). + +## Mounting + +```html +
+``` + +## Variables + +| id | type | default | notes | +| ---------- | ------ | ------- | ------------------------------------------------------------------------ | +| axis | enum | rows | rows smears across horizontal slices, cols across vertical slices. | +| spread | number | 0.45 | Maximum sampling offset in seconds across the frame (clamped 0.1 to 1.2). | +| resolve_at | number | 2.2 | Seconds when the offsets start collapsing. Clamped so the 0.6s collapse and any exit fit inside the clip. | +| accent | enum | green | green maps to --brand, blue to --accent, violet to --accent-2. | +| exit | enum | none | none holds the resolved mark (frame roots own transitions); fade or up add a 0.45s departure. | + +## Choreography + +- 0.25s: the subject starts its sweep from the wing (rows: in from the + left; cols: down from the top, so the slices always cut across the + travel): cubic-eased travel to dead center with a decaying two-cycle + sine bob (integer cycles, exactly zero at rest) and a settling scale. +- Throughout the sweep the slices smear it through time: slice 0 shows + now, the far slice shows spread seconds ago, with a seeded hairline + jitter per slice so the edge stays organic. +- resolve_at: the offsets collapse to zero over 0.6s (eased both ends); + the smear zips shut onto the resting subject. +- HOLD: dead still and coherent, elastic with the clip duration. + +Sync point `resolved` fires as the collapse completes; align SFX to it. + +## Determinism + +pos(t) is closed-form, so any sampled time t - offset is exact; per-slice +jitter is one table computed once at build from fixed LCG seed 0x51175c4e. +One plain-object anchor tween feeds one painter that clears and redraws +every slice from scratch as a pure function of the timeline time, so +eventful seeks land identical frames in any order and either direction. +The raster basis is the host box measured once at mount; sampled times +before mount clamp to the subject's start state (not-yet-appeared), and +after the collapse every slice samples the same resting state. diff --git a/registry/components/slit-scan-reveal/demo.html b/registry/components/slit-scan-reveal/demo.html new file mode 100644 index 0000000000..3902666585 --- /dev/null +++ b/registry/components/slit-scan-reveal/demo.html @@ -0,0 +1,85 @@ + + + + + + + Slit Scan Reveal Demo + + + +
+
+
+ + + + + diff --git a/registry/components/slit-scan-reveal/registry-item.json b/registry/components/slit-scan-reveal/registry-item.json new file mode 100644 index 0000000000..e8fdf367f2 --- /dev/null +++ b/registry/components/slit-scan-reveal/registry-item.json @@ -0,0 +1,76 @@ +{ + "$schema": "https://hyperframes.heygen.com/schema/registry-item.json", + "name": "slit-scan-reveal", + "type": "hyperframes:component", + "title": "Slit Scan Reveal", + "description": "The frame's rows sample an authored subject at offset times: a canvas painter draws row slices of one pure position function evaluated at t minus offset(row), smearing arrival through time until the offsets collapse to zero and the mark holds coherent.", + "tags": ["motion-primitive", "time", "slit-scan", "canvas", "deterministic", "experiment"], + "jobs": ["reveal"], + "family": "time", + "profile": "time-smear", + "evidence": "Wave M4 experiment slate; classic slit-scan sampling implemented as a closed-form position function drawn in per-slice clips, offsets collapsing at the resolve cue", + "syncPoints": [{ "id": "resolved", "phase": "in", "offset": 2.8 }], + "files": [ + { + "path": "slit-scan-reveal.html", + "target": "compositions/components/slit-scan-reveal.html", + "type": "hyperframes:snippet" + } + ], + "variables": [ + { + "id": "axis", + "type": "enum", + "role": "style", + "label": "Axis", + "description": "rows smears arrival across horizontal slices; cols across vertical slices.", + "default": "rows", + "options": [ + { "value": "rows", "label": "Rows" }, + { "value": "cols", "label": "Columns" } + ] + }, + { + "id": "spread", + "type": "number", + "role": "timing", + "label": "Spread", + "description": "Maximum sampling offset in seconds across the frame; larger smears further through time.", + "default": 0.45 + }, + { + "id": "resolve_at", + "type": "number", + "role": "timing", + "label": "Resolve at", + "description": "Seconds from mount start when the offsets start collapsing to zero. Clamped so the collapse and any exit stay inside the clip.", + "default": 2.2 + }, + { + "id": "accent", + "type": "enum", + "role": "style", + "label": "Accent", + "description": "Subject ring and orbit color: green rides --brand, blue rides --accent, violet rides --accent-2.", + "default": "green", + "options": [ + { "value": "green", "label": "Green" }, + { "value": "blue", "label": "Blue" }, + { "value": "violet", "label": "Violet" } + ] + }, + { + "id": "exit", + "type": "enum", + "role": "timing", + "label": "Exit", + "description": "Optional departure. Default none: the resolved mark holds until the frame cuts.", + "default": "none", + "options": [ + { "value": "none", "label": "None" }, + { "value": "fade", "label": "Fade" }, + { "value": "up", "label": "Up" } + ] + } + ] +} diff --git a/registry/components/slit-scan-reveal/slit-scan-reveal.html b/registry/components/slit-scan-reveal/slit-scan-reveal.html new file mode 100644 index 0000000000..acd714f4bf --- /dev/null +++ b/registry/components/slit-scan-reveal/slit-scan-reveal.html @@ -0,0 +1,334 @@ + + + + + + Slit Scan Reveal + + + + + diff --git a/registry/components/stitched-text-draw/README.md b/registry/components/stitched-text-draw/README.md new file mode 100644 index 0000000000..f74f00bba8 --- /dev/null +++ b/registry/components/stitched-text-draw/README.md @@ -0,0 +1,45 @@ +# stitched-text-draw + +Wave M experiment (M6). Text drawn as thread stitches: letters draw thread-first with a needle dot leading each stroke's reveal front, needle-hole dots appearing at every stitch boundary behind it, and a tiny thread-tail overshoot as each letter finishes (the thread sticks out past the stroke end along its exit tangent, then is pulled snug). + +4s authored, elastic HOLD, exit `none` by default. + +## Letter path approach + +A single-stroke display alphabet (A-Z 0-9, plus space) authored as a compact stroke table inside the primitive: each glyph is a list of strokes, each stroke built from line points and flattened elliptical arcs in a 70x100 glyph box. At mount every stroke is resampled to even stitch-scale steps and displaced by seeded perpendicular jitter (fixed LCG seed `0x57174c3d`), so every stitch sits at a slightly different angle: the hand-sewn wobble. Letter x-offsets are baked into the points so all paths share one flat SVG coordinate space and a single global needle can read `getPointAtLength` coordinates directly. + +## Draw mechanic (attribute dashoffset, no mask) + +The reveal is ATTRIBUTE-driven `stroke-dashoffset` on the stitched path itself (gotcha 8: the CSS property leaves stale paint under reverse seeks; and SVG masks do not reliably repaint under seeks, so no ``/``). Each path's dasharray is its stitch pattern (dash, gap, dash, ...) built to sum exactly to the measured path length `L` (via `getTotalLength`, never the `pathLength` attribute, never `non-scaling-stroke` on dashed paths), followed by one closing gap of `L` (total `2L`). Animating the dashoffset attribute from `L` to `0` sweeps the reveal front from start to end; the visible stitches slide the last few units into their final positions as the offset reaches zero, which reads as the thread being pulled through. + +## Variables + +| id | type | default | notes | +| --- | --- | --- | --- | +| `text` | string | `STITCH` | Uppercased; A-Z 0-9 and space, other characters render as spaces; clamped to 12 characters. | +| `stitch` | enum | `fine` | `fine` (dash 6.5 / gap 4.5 / width 2.6) or `coarse` (dash 10 / gap 7 / width 4.2) in glyph units, with matching jitter amplitude. | +| `accent` | enum | `green` | Thread color: green rides `--brand`, blue rides `--accent`, violet rides `--accent-2`. | +| `exit` | enum | `none` | `none` holds until the cut; `fade` departs opacity-only; `up` rises out. | + +## Mount + +```html +
+``` + +Elastic root: no `data-width`/`data-height`; it fills whatever box the host clip gives it. Timeline registers under the literal `stitched-text-draw` key. + +## Notes + +- Zero per-element tweens (gotcha 9): one inert anchor tween spans the authored duration; a painter recomputes every stroke's dashoffset attribute, every hole's visibility, and the needle/tail pose as pure functions of `tl.time()` on each update. Idempotent writes; eventful seeks (`suppressEvents=false`) land identical frames in any order and either direction. +- Letters draw strictly in sequence (windows proportional to stitch length, fixed needle-jump pause between letters), so one global needle serves the whole word. +- The per-stroke ease is a backOut: the ~10% overshoot region past the stroke end is what drives the thread-tail (up to 16 glyph units), which retracts to zero as the ease settles. +- The hold is truly still: every stroke sits at dashoffset 0, all holes visible, needle and tail at opacity 0. diff --git a/registry/components/stitched-text-draw/demo.html b/registry/components/stitched-text-draw/demo.html new file mode 100644 index 0000000000..43a3ad67a7 --- /dev/null +++ b/registry/components/stitched-text-draw/demo.html @@ -0,0 +1,85 @@ + + + + + + + Stitched Text Draw Demo + + + +
+
+
+ + + + + diff --git a/registry/components/stitched-text-draw/registry-item.json b/registry/components/stitched-text-draw/registry-item.json new file mode 100644 index 0000000000..21d1b4c70e --- /dev/null +++ b/registry/components/stitched-text-draw/registry-item.json @@ -0,0 +1,68 @@ +{ + "$schema": "https://hyperframes.heygen.com/schema/registry-item.json", + "name": "stitched-text-draw", + "type": "hyperframes:component", + "title": "Stitched Text Draw", + "description": "Text drawn as thread stitches: a single-stroke display alphabet draws via attribute-driven dashoffset with stitch-tuned dash patterns, seeded angle jitter, needle-hole dots, a leading needle, and a thread-tail overshoot per letter.", + "tags": ["motion-primitive", "experiment", "type", "craft", "stitch", "deterministic"], + "jobs": ["reveal"], + "family": "text-effects", + "profile": "thread-stitch", + "evidence": "Wave M experiment M6: wild-scout stitched-line text draw (score 16), thread-stitch wordmark reveal for craft/authenticity beats", + "syncPoints": [{ "id": "stitch-done", "phase": "in", "offset": 3.05 }], + "files": [ + { + "path": "stitched-text-draw.html", + "target": "compositions/components/stitched-text-draw.html", + "type": "hyperframes:snippet" + } + ], + "variables": [ + { + "id": "text", + "type": "string", + "role": "content", + "label": "Text", + "description": "Uppercased A-Z 0-9 and space, clamped to 12 characters.", + "default": "STITCH" + }, + { + "id": "stitch", + "type": "enum", + "role": "style", + "label": "Stitch", + "description": "Dash/gap scale, thread width, and jitter amplitude.", + "default": "fine", + "options": [ + { "value": "fine", "label": "Fine" }, + { "value": "coarse", "label": "Coarse" } + ] + }, + { + "id": "accent", + "type": "enum", + "role": "style", + "label": "Accent", + "description": "Thread color: green rides --brand, blue rides --accent, violet rides --accent-2.", + "default": "green", + "options": [ + { "value": "green", "label": "Green" }, + { "value": "blue", "label": "Blue" }, + { "value": "violet", "label": "Violet" } + ] + }, + { + "id": "exit", + "type": "enum", + "role": "timing", + "label": "Exit", + "description": "Optional departure. Default none: the stitched word holds until the frame cuts.", + "default": "none", + "options": [ + { "value": "none", "label": "None" }, + { "value": "fade", "label": "Fade" }, + { "value": "up", "label": "Up" } + ] + } + ] +} diff --git a/registry/components/stitched-text-draw/stitched-text-draw.html b/registry/components/stitched-text-draw/stitched-text-draw.html new file mode 100644 index 0000000000..1d4f91eb28 --- /dev/null +++ b/registry/components/stitched-text-draw/stitched-text-draw.html @@ -0,0 +1,620 @@ + + + + + + Stitched Text Draw + + + + + diff --git a/registry/components/stop-motion-cadence/README.md b/registry/components/stop-motion-cadence/README.md new file mode 100644 index 0000000000..0163293120 --- /dev/null +++ b/registry/components/stop-motion-cadence/README.md @@ -0,0 +1,37 @@ +# stop-motion-cadence + +The stepped-time law as a demo primitive (Wave M10 experiment). ONE driver quantizes timeline time FIRST, `ts = floor(t * fps) / fps`, and `ts` feeds ALL motion: four token paper-cut shapes throw-and-land in sequence at the quantized cadence, each squashing on its landing hit and recovering over the next steps, with a 2-frame boil jitter on edges (seeded per boil window, stable within it) and sparkle accents living exactly 2 or 3 steps on each hit, frame-alternating big / small-rotated like a drawn twinkle. Reference: the MotionMarkus stop-motion sheets. + +4s authored, elastic HOLD (the boil keeps breathing through the hold; `boil: off` holds dead still), exit `none` by default. + +## Variables + +| id | type | default | notes | +| --- | --- | --- | --- | +| `fps` | enum | `10` | Stepped-time cadence: `8`, `10`, or `12` frames per second. | +| `boil` | enum | `on` | 2-frame edge jitter hashed from `floor(step / 2)`. `off` gives a dead-still settle. | +| `accent` | enum | `green` | Lead shape color: green rides `--brand`, blue rides `--accent`, violet rides `--accent-2`; the other shapes take the remaining two tokens in rotation. | +| `exit` | enum | `none` | `none` holds until the cut; `fade` departs opacity-only; `up` rises out. | + +## Mount + +```html +
+``` + +Elastic root: no `data-width`/`data-height`; it fills whatever box the host clip gives it. Timeline registers under the literal `stop-motion-cadence` key. + +## Notes + +- The law: quantize before evaluating. Every hit is an integer step index (`landStep = round(t_land * fps)`), squash is a per-step envelope over `step - landStep`, boil seeds hash `floor(step / 2)`, sparkles are visible iff `0 <= step - landStep < life`. Because all state derives from the quantized driver, eventful seeks land identical frames forward, reverse, or shuffled. +- Squash uses `transform-origin: 50% 100%` so shapes compress into the floor contact; contact shadows grow and darken as each shape nears the ground. +- Deterministic: the jitter hash is a sin-fold over integer indices; no Math.random, no wall clock, no incremental state. Stage size is measured once at mount; per-update writes are transforms and opacity only. +- If the mount duration is shorter than the 3s choreography, the throw times compress together, but the fps grid itself never scales; the cadence is absolute. diff --git a/registry/components/stop-motion-cadence/demo.html b/registry/components/stop-motion-cadence/demo.html new file mode 100644 index 0000000000..ca0c092f82 --- /dev/null +++ b/registry/components/stop-motion-cadence/demo.html @@ -0,0 +1,86 @@ + + + + + + + Stop Motion Cadence Demo + + + +
+
+
+ + + + + diff --git a/registry/components/stop-motion-cadence/registry-item.json b/registry/components/stop-motion-cadence/registry-item.json new file mode 100644 index 0000000000..8fe0ed976f --- /dev/null +++ b/registry/components/stop-motion-cadence/registry-item.json @@ -0,0 +1,73 @@ +{ + "$schema": "https://hyperframes.heygen.com/schema/registry-item.json", + "name": "stop-motion-cadence", + "type": "hyperframes:component", + "title": "Stop Motion Cadence", + "description": "The stepped-time law as a demo primitive: one driver quantizes timeline time to floor(t * fps) / fps and feeds ALL motion; four token paper-cut shapes throw-and-land with squash on hits, 2-frame boil jitter seeded per step, and sparkle accents living exactly 2 to 3 frames.", + "tags": ["motion-primitive", "time-law", "stop-motion", "stepped", "boil", "deterministic", "experiment"], + "jobs": ["demonstrate"], + "family": "demonstrate", + "profile": "stepped-time", + "evidence": "Wave M10 experiment: MotionMarkus stop-motion sheets (2059406490981863865/sheet-01)", + "syncPoints": [{ "id": "first-hit", "phase": "in", "offset": 1.0 }], + "files": [ + { + "path": "stop-motion-cadence.html", + "target": "compositions/components/stop-motion-cadence.html", + "type": "hyperframes:snippet" + } + ], + "variables": [ + { + "id": "fps", + "type": "enum", + "role": "timing", + "label": "Cadence", + "description": "Stepped-time cadence in frames per second.", + "default": "10", + "options": [ + { "value": "8", "label": "8 fps" }, + { "value": "10", "label": "10 fps" }, + { "value": "12", "label": "12 fps" } + ] + }, + { + "id": "boil", + "type": "enum", + "role": "style", + "label": "Boil", + "description": "2-frame edge jitter, seeded per step. Off holds dead still.", + "default": "on", + "options": [ + { "value": "on", "label": "On" }, + { "value": "off", "label": "Off" } + ] + }, + { + "id": "accent", + "type": "enum", + "role": "style", + "label": "Accent", + "description": "Lead shape color: green rides --brand, blue rides --accent, violet rides --accent-2.", + "default": "green", + "options": [ + { "value": "green", "label": "Green" }, + { "value": "blue", "label": "Blue" }, + { "value": "violet", "label": "Violet" } + ] + }, + { + "id": "exit", + "type": "enum", + "role": "timing", + "label": "Exit", + "description": "Optional departure. Default none: the settled collage holds until the frame cuts.", + "default": "none", + "options": [ + { "value": "none", "label": "None" }, + { "value": "fade", "label": "Fade" }, + { "value": "up", "label": "Up" } + ] + } + ] +} diff --git a/registry/components/stop-motion-cadence/stop-motion-cadence.html b/registry/components/stop-motion-cadence/stop-motion-cadence.html new file mode 100644 index 0000000000..6632af3109 --- /dev/null +++ b/registry/components/stop-motion-cadence/stop-motion-cadence.html @@ -0,0 +1,405 @@ + + + + + + Stop Motion Cadence + + + + + diff --git a/registry/components/variable-font-flex/README.md b/registry/components/variable-font-flex/README.md new file mode 100644 index 0000000000..b40f1354ae --- /dev/null +++ b/registry/components/variable-font-flex/README.md @@ -0,0 +1,48 @@ +# variable-font-flex + +Wave M experiment (M5). A word lands while its variable-font axes flex: each character rises from hairline-condensed to black-wide with a per-character stagger and a small carve-in overshoot, then settles. The word's font-size eases inversely to the mean axis progress (optical compensation), so the letterforms gain mass while the word's box barely grows. + +3.5s authored, elastic HOLD, exit `none` by default. + +## Font source (pinned) + +Roboto Flex Variable via the Fontsource CDN build: `@fontsource-variable/roboto-flex@5.2.8`, latin `standard` subset, one woff2 carrying `wght` 100..1000 and `wdth` 25%..151%: + +``` +https://cdn.jsdelivr.net/npm/@fontsource-variable/roboto-flex@5.2.8/files/roboto-flex-latin-standard-normal.woff2 +``` + +Inter var was considered per the spec but Inter has no `wdth` axis; the width mode needs a real one, so Roboto Flex carries both. The pinned font IS the mechanic: this unit deliberately does not ride `--font-display`, because a theme font without matching axes would kill the effect. The landing weight (840) is the animated mechanic, not static register styling. + +## Variables + +| id | type | default | notes | +| --- | --- | --- | --- | +| `text` | string | `FLEX` | The word whose axes flex. | +| `axis` | enum | `both` | `weight` (wght 120 to 840, wdth held 100), `width` (wdth 40 to 128, wght held 620), or `both`. | +| `stagger` | number | `0.06` | Seconds between per-character flex starts, clamped 0 to 0.25. | +| `accent` | enum | `green` | Word color: green rides `--brand`, blue rides `--accent`, violet rides `--accent-2`. | +| `exit` | enum | `none` | `none` holds until the cut; `fade` departs opacity-only; `up` rises out. | + +## Mount + +```html +
+``` + +Elastic root: no `data-width`/`data-height`; it fills whatever box the host clip gives it. Timeline registers under the literal `variable-font-flex` key. + +## Notes + +- Number proxy, never the string: no tween ever touches `font-variation-settings`. One inert anchor tween spans the authored duration; on every update a painter recomputes each character's axis NUMBERS as a pure function of `tl.time()` and composes the `"wght" W, "wdth" D` string from them, clamped to the font's real ranges (the carve ease overshoots ~6% past target before settling). +- Optical compensation: word-level `font-size = base * (1 - COMP * meanProgress)` where COMP is 0.16 for `both`, 0.12 for `width`, 0.08 for `weight`. Mass grows, the box holds. +- Seek-safe: painter writes are idempotent and depend on timeline time alone; eventful seeks (`suppressEvents=false`, the engine's render path) land identical frames in any order and either direction. No `Math.random`, no wall clock. +- The base size fits the FINAL wide-black state (capped by `cqh` for short words on wide hosts); compensation only ever shrinks below it, so nothing clips mid-flex. diff --git a/registry/components/variable-font-flex/demo.html b/registry/components/variable-font-flex/demo.html new file mode 100644 index 0000000000..ba43d5077c --- /dev/null +++ b/registry/components/variable-font-flex/demo.html @@ -0,0 +1,85 @@ + + + + + + + Variable Font Flex Demo + + + +
+
+
+ + + + + diff --git a/registry/components/variable-font-flex/registry-item.json b/registry/components/variable-font-flex/registry-item.json new file mode 100644 index 0000000000..47e49b6738 --- /dev/null +++ b/registry/components/variable-font-flex/registry-item.json @@ -0,0 +1,77 @@ +{ + "$schema": "https://hyperframes.heygen.com/schema/registry-item.json", + "name": "variable-font-flex", + "type": "hyperframes:component", + "title": "Variable Font Flex", + "description": "A word lands while its variable-font weight and width axes flex from hairline-condensed to black-wide with per-character stagger; font-size eases inversely so the letterforms gain mass while the box holds.", + "tags": ["motion-primitive", "experiment", "type", "variable-font", "deterministic"], + "jobs": ["emphasize"], + "family": "text-effects", + "profile": "axis-flex", + "evidence": "Wave M experiment M5: variable-font axis animation with optical compensation; Roboto Flex Variable pinned via @fontsource-variable/roboto-flex@5.2.8", + "syncPoints": [{ "id": "flex-settled", "phase": "in", "offset": 1.6 }], + "files": [ + { + "path": "variable-font-flex.html", + "target": "compositions/components/variable-font-flex.html", + "type": "hyperframes:snippet" + } + ], + "variables": [ + { + "id": "text", + "type": "string", + "role": "content", + "label": "Text", + "description": "The word whose variable-font axes flex.", + "default": "FLEX" + }, + { + "id": "axis", + "type": "enum", + "role": "style", + "label": "Axis", + "description": "Which variable-font axes animate: weight, width, or both.", + "default": "both", + "options": [ + { "value": "weight", "label": "Weight" }, + { "value": "width", "label": "Width" }, + { "value": "both", "label": "Both" } + ] + }, + { + "id": "stagger", + "type": "number", + "role": "timing", + "label": "Stagger", + "description": "Seconds between per-character flex starts, clamped 0 to 0.25.", + "default": 0.06 + }, + { + "id": "accent", + "type": "enum", + "role": "style", + "label": "Accent", + "description": "Word color: green rides --brand, blue rides --accent, violet rides --accent-2.", + "default": "green", + "options": [ + { "value": "green", "label": "Green" }, + { "value": "blue", "label": "Blue" }, + { "value": "violet", "label": "Violet" } + ] + }, + { + "id": "exit", + "type": "enum", + "role": "timing", + "label": "Exit", + "description": "Optional departure. Default none: the carved word holds until the frame cuts.", + "default": "none", + "options": [ + { "value": "none", "label": "None" }, + { "value": "fade", "label": "Fade" }, + { "value": "up", "label": "Up" } + ] + } + ] +} diff --git a/registry/components/variable-font-flex/variable-font-flex.html b/registry/components/variable-font-flex/variable-font-flex.html new file mode 100644 index 0000000000..854f900ebe --- /dev/null +++ b/registry/components/variable-font-flex/variable-font-flex.html @@ -0,0 +1,308 @@ + + + + + + Variable Font Flex + + + + +