Each catalog card now shows two hover buttons:
- "Add" — inserts the block/component into the composition at the
current playhead position (existing behavior, now with + icon)
- "Agent prompt" — copies a contextual prompt to clipboard tailored
to the block's category (captions, transitions, VFX, etc.) so the
user can paste it into their AI agent for guided customization
Blocks and components now start at the current playhead position
instead of being appended after all existing content. If the new
element extends beyond the root composition's data-duration, the
root is automatically extended to fit.
insertTimelineAssetIntoSource now detects the parent indent level and
adds the new element with matching child indentation. Block attributes
are written one-per-line for readability.
Blocks were using their own native dimensions (e.g. 1920x1080) instead
of the host composition dimensions (e.g. 1280x720), causing them to
overflow the viewport and break the layout. The block's iframe scales
its content to fit the container, so using host dimensions is correct.
Replace fragile regex z-index parsing with getComputedStyle on the
preview iframe elements — the same source of truth the inspector uses.
Rename "Layer" to "Z-index" in the design panel for clarity.
The DomEditOverlay sits at z-10 with pointer-events:auto over the
preview, intercepting all drag events before they reach NLEPreview's
viewport. Move block drop handling from NLEPreview up to the wrapper
div in NLELayout that contains both the preview and the overlay, so
drag-and-drop from the Catalog panel onto the preview area works
regardless of inspector state.
VITE_STUDIO_* env vars set in the user's shell had no effect when
running `hyperframes preview` because the pre-built studio bundle had
them baked at Vite build time.
The embedded Hono server now collects VITE_STUDIO_* vars from
process.env and injects them as a `window.__HF_STUDIO_ENV__` script
tag into index.html. The client merges this runtime object on top of
the baked `import.meta.env`, so flags like
VITE_STUDIO_ENABLE_BLOCKS_PANEL=1 work as expected at runtime.
- Rename "Blocks" tab to "Catalog"
- Replace fullscreen hover popup with inline preview in main area
- Fix z-index: newly added blocks/components use max existing z-index + 1
instead of element count, ensuring they appear on top
Blocks dragged from the Blocks panel and dropped onto the composition
preview now land at the position where they were dropped, instead of
always being placed at (0, 0).
The preview viewport converts screen coordinates to composition space
using the stage element's bounding rect, accounting for zoom and pan.
A visual drop indicator (dashed border overlay) appears while dragging
over the preview.
When the Code tab is active and a user clicks an element in the preview,
the code editor now auto-scrolls to the corresponding HTML source. This
removes the need for Alt+click — the existing click-to-source mechanism
fires automatically when the Code tab is already open.
- Add getTab() to LeftSidebarHandle for reading the active tab
- Add getSidebarTab getter to useDomEditSession
- Add effect that calls openSourceForSelection on selection change
when Code tab is active
Previous baseline was generated on arm64 host, causing 87 failed
frames in CI (amd64). Regenerated with --platform linux/amd64 to
match CI's Chrome/FFmpeg pixel output.
gsap.fromTo(target, fromVars, toVars) animates to toVars, not to
the current CSS value — so fromTo({opacity:0}, {opacity:1}) with
CSS opacity:0 is a legitimate 0→1 fade-in, not a noop. The rule
was false-positiving on these calls with error severity, which
would block the render pipeline.
Drop the fromTo branch from the trigger guard and add a test case
that proves fromTo does not fire.
Per James and Miguel's review feedback:
James: "you can only reference the skill or recreate things"
Miguel: "I'd just reference the skill without trying to copy
everything"
James: "we can't vendor their artifacts in our own repo"
The 48 vendored text-effects JSON files were direct copies from
pixel-point/animate-text (verified: typewriter.json matches the
upstream byte-for-byte at duration_ms: 240, stagger_ms: 46,
easing: steps(1, end)). That repo has no LICENSE file, so the
files default to "all rights reserved" — Apache 2.0 redistribution
in our repo is a compliance violation.
The earlier "rewrite from scratch" attempt produced JSON that
didn't render correctly (7 of 24 effects broke when I built a demo
HTML to validate them). Rather than keep iterating on a rewrite
that doesn't match the upstream's calibration, this commit drops
the vendored files entirely and updates skill prose to REFERENCE
the upstream skill instead.
### Changes
- Delete `skills/hyperframes/assets/text-effects/` (48 JSON files
+ the dual-directory structure, 8451 lines).
- Rewrite `skills/hyperframes/references/text-effects.md` as a
reference page that:
- Names pixel-point/animate-text as the source of truth
- Explains why we don't ship the catalog (licensing gap)
- Tells sub-agents to load `/animate-text` via Claude Code skill
invocation or `npx skills add pixel-point/animate-text`
- Lists the 24 effect IDs by category as vocabulary (so agents
can name effects in STORYBOARD.md before loading the upstream)
- Documents the fallback path: implement from name + GSAP
knowledge if the upstream skill isn't available
- Update 5 cross-references to reframe the catalog as upstream-
referenced instead of repo-bundled:
- skills/hyperframes/references/techniques.md (TOC pointer)
- skills/hyperframes/references/beat-direction.md (text-anim
guidance bullet)
- skills/website-to-hyperframes/references/capabilities.md
(registry-overview entry)
- skills/website-to-hyperframes/references/step-3-storyboard.md
(storyboard template + Text Animations section format)
- skills/website-to-hyperframes/references/beat-builder-guide.md
(references table row)
### What sub-agents see
Before: read `text-effects.md` → see 24 named effects → read each
spec from in-repo `assets/text-effects/effects/<id>.json`.
After: read `text-effects.md` → see 24 named IDs and a load
instruction → invoke `/animate-text` (or `npx skills add
pixel-point/animate-text`) → read each spec from
`.agents/skills/animate-text/assets/effects/<id>.json` (the
upstream's own files, not vendored copies).
End-user UX cost: one extra `npx skills add` for projects that
want the deterministic catalog. Or accept the fallback where
sub-agents implement from effect name + GSAP knowledge — works
fine for simple effects, less ideal for the 3 layout-aware ones
(kinetic-center-build, short-slide-right, short-slide-down).
Net diff on #991: -8488 lines.
Two follow-ups caught by a post-restructure audit pass:
- skills/hyperframes/references/transitions.md:46 had a parenthetical
"(derived from visual-vocabulary.md)" pointing at a file deleted
earlier in this stack. Drop the parenthetical; the surrounding
sentence reads cleanly without it.
- skills/hyperframes/SKILL.md:476 still said "20 visual techniques"
and listed 7 entries that the techniques.md trim removed (frosted
glass, impact lines, device mockups, aurora gradients, floating
particles, terminal UI, moodboard layouts). Updated to the actual
13 primitive techniques + a pointer to registry/blocks/ for the
pre-built UI templates that used to be conflated with techniques.
Removed 7 entries that are not techniques. They're either UI style
treatments, hand-rolled CSS templates, or named effects — different
kinds of artifacts that don't belong under the same "primitive
animation techniques" frame:
- 12. Frosted Glass Panels → style treatment
- 15. Impact Line on Text Drop → named effect
- 16. Device Mockups (Laptop + Phone) → hand-rolled CSS template
- 17. Aurora Gradient Backgrounds → style treatment
- 18. Floating Particles → style treatment
- 19. Terminal UI with Typing → hand-rolled CSS template
- 20. Moodboard / Editorial Layout → hand-rolled layout
The 13 entries that remain (SVG drawing, Canvas 2D, CSS 3D, Per-Word
Kinetic Type, Lottie, Video Compositing, Char Typing, Variable Font,
MotionPath, Velocity-Matched Transitions, Audio-Reactive, Clip-Path
Reveal, WebGL Shader Art) are real primitives — animation building
blocks an agent composes into beats, not finished recipes to lift
verbatim.
Updated the header to say 13 and point at `registry/blocks/` for
pre-built UI templates instead of pretending they're techniques. ToC
table and section numbering both updated. Net: -361 / +6 lines.
Per review feedback, these 5 files should not change in this stack:
- visual-styles.md — keep main's existing version
- house-style.md — keep main's light/dark prescription
- references/typography.md — keep main's tone + Banned/Guardrails framing
- references/video-composition.md — keep main's density guidance
- CLAUDE.md — drop the local-CLI Capture/Snapshot section added in
this branch; the local-CLI dev-instruction block for adding CLI
commands stays (was already on main, unrelated to skill prose).
Net: removes ~545 lines of churn from PR #991's diff.
Three concrete bugs found while auditing PR #991:
1. html-in-canvas-patterns.md (#1 in catalog, 3D Rotation with Bloom):
The code example used `new THREE.EffectComposer(renderer)` UMD-style
namespace access while the ESM imports right below pull them in as
bare named imports. Three.js r150+ removed the UMD `examples/js/`
globals, so as written the example throws `TypeError:
THREE.EffectComposer is not a constructor`. Switched to the bare
names matching the imports. THREE.Vector2 stays as-is — Vector2 is
on the THREE namespace.
2. techniques.md (#5, Lottie Animation): The CDN path
`@lottiefiles/dotlottie-web/dist/dotlottie-player.js` returns 404.
`@lottiefiles/dotlottie-web` is the JavaScript SDK, not a web
component — its `main` is `dist/index.cjs`. The web-component
package is `@lottiefiles/dotlottie-wc` and the custom element is
`<dotlottie-wc>`, not `<dotlottie-player>`. Updated both.
3. techniques.md (5 occurrences across Lottie / lottie-web /
Video / @font-face examples): asset paths used the `../capture/`
pattern that PR #989's `invalid_capture_path` lint rule emits an
error for. Replaced all with root-relative `capture/...`. PRs #989
and #991 are no longer self-contradictory.
Rewrites the standalone `hyperframes` skill (the main authoring
skill used by every hyperframes user, not just the
website-to-hyperframes pipeline) to remove prescriptive lookup
tables that drove monoculture output, restore tone, and bundle
24 named text animation effects directly into the skill so
agents don't need a separate install.
This is a +9951/-567 change touching 61 files in `skills/hyperframes/`.
It deserves its own review separate from the capture pipeline and
the website-to-hyperframes pipeline because it affects every
hyperframes user — not just the website-to-video flow.
**Prescriptive tables removed / restructured**
External rater feedback across two rounds identified six lookup
tables agents were pasting wholesale as recipes:
- `visual-styles.md` YAML blocks — completely replaced. Old version
had 8 styles with full YAML token blocks (colors / typography /
motion / transition names). Agents copy-pasted. New version
renames to actual design traditions (Swiss / Late-Modernist
Editorial / Punk / Maximalist / Computational / Humanist /
Vernacular / Cinematic) and replaces YAML with prose: "what it
teaches / where it resonates / pitfalls when borrowing." No
lookup table.
- `motion-principles.md` — complete rewrite. Old version opened
every section with "You know these rules but violate them. Stop."
/ "You will try to use 14px. Don't." New version: "Common defaults
that produce monoculture" framing. All load-bearing GSAP rules
preserved verbatim (those are correct and critical).
- `beat-direction.md` rhythm table — removed. Replaced with
questions that derive rhythm from brand + storyboard. Verb table
regrouped by physical character (Impact / Directional / Reveals /
Organic / Mechanical) without energy labels.
- `transitions.md` Energy → Transition table + Mood → Type table —
removed named transitions, replaced with motion-quality
descriptions (Soft/organic, Directional/purposeful,
Percussive/instant). Mixing documented: CSS crossfade + shader
in the same HyperShader composition (verified working).
- `dynamic-techniques.md` energy table — restructured with
explanatory principles (highlight amplitude, exit style, cycle
variation) before showing the table as calibration reference.
- `techniques.md` "When to Use What" table — deleted. Replaced
with "choose techniques based on beat concept, not video genre."
- `typography.md` — "Guardrails / You know these rules but violate
them" → "Defaults to watch for." Banned fonts gain a caveat:
if the brand actually uses one of these fonts, use it.
- `video-composition.md` — fixed density contradiction
("8–10 visual elements" removed; sparse beats are intentional).
**Text-effects bundle (new)**
24 named text-animation effects shipped as paired specs:
- `assets/text-effects/effects/<id>.json` — GSAP-specific recipe
agents can paste verbatim
- `assets/text-effects/specs/<id>.json` — portable motion contract
(engine-agnostic, so the same effect can be re-implemented in any
animation library)
Catalog at `references/text-effects.md`. Storyboards reference effects
by name (typewriter, kinetic-center-build, shimmer-sweep, …) instead
of saying "fades in," which produced inconsistent typography across
beats.
Effects organized by target:
- Per-character (7): soft-blur-in, per-character-rise, typewriter,
bottom-up-letters, top-down-letters, stagger-from-{center,edges}
- Per-word (8): per-word-crossfade, spring-scale-in, shared-axis-y,
blur-out-up, kinetic-center-build, short-slide-{right,down},
depth-parallax-words
- Per-line (2): mask-reveal-up, line-by-line-slide
- Whole element (7): micro-scale-fade, shimmer-sweep, fade-through,
shared-axis-{x,z}, scale-down-fade, focus-blur-resolve
Sources adapted from `pixel-point/animate-text`; copied into the
repo so users don't need a separate install.
**Misc cleanups**
- `house-style.md` — light/dark prescription removed; defer to brand.
- `prompt-expansion.md` — `design.md` → `DESIGN.md` casing fixed.
- `html-in-canvas-patterns.md` — Three.js 0.147.0 (legacy
`examples/js/`) → 0.181.2 (`examples/jsm/` ESM imports);
`Math.random()` in the shatter example → mulberry32 seeded PRNG
so output is deterministic.
**.gitignore + CLAUDE.md**
- `.gitignore` catches per-brand video project directories agents
leave at the repo root (`huly-*/`, `raycast-*/`, `*-demo-*/`,
`test-runs/`, `test-outputs/`) plus the `videos/` folder
conventions.
- `CLAUDE.md` documents the local CLI for `capture` + `snapshot`
(since the published `npx hyperframes` doesn't yet include the
capture pipeline improvements from this stack) and the local
shader-transitions build copy convention.
Five fixes from Ular's first-pass workflow run:
1. step-1-design.md Fonts section — sub-agents pointed @font-face for
"ES Build Neutral" at the Inter .woff2 files because DESIGN.md
only named families, never emitted exact src: paths. Now the
Fonts section example shows per-family + per-weight file paths
AND a copy-verbatim @font-face block sub-agents can paste, so
there's no inference step. Adds an explicit narrative of the
real failure mode and how to avoid it.
2. beat-builder-guide.md FONTS rule — was "brand fonts with
capture/assets/fonts/ path need @font-face in <style>." Now:
"copy the @font-face block VERBATIM from DESIGN.md. Do NOT guess
which .woff2 file belongs to which family — capture filenames
are content-hashed and there is no visible mapping. If DESIGN.md
doesn't include exact src: paths per family, STOP and ask the
main agent; never pair an arbitrary .woff2 with a family name
from memory."
3. step-1-design.md Colors section — Sub-agents reproduced brand
colors faithfully and hit WCAG AA failures on dark surfaces
(#68686A on #18191B = 3.16:1). Now the Colors section example
computes per-pairing contrast ratios with ✅/⚠/❌ markers,
documents the dark-surface substitute color when the brand's own
palette fails, and points at the /hyperframes-contrast skill for
ratio computation. Sub-agents pick text colors by surface
context, not by "this is the brand's secondary text color."
4. capabilities.md flash-through-white entry — the "ideal as
invisible bridge at duration: 0.01" framing caused agents to
scatter white flashes through every composition as transition
bridges. The fix was documented in the branch's HANDOFF but
never landed. Now: "Fade through white midpoint — a visible
white flash between scenes. Use only when the brand specifically
calls for a white-flash beat boundary; this is NOT a neutral
'default' transition."
5. step-6-validate.md Warnings list — adds a paragraph on WCAG
contrast false positives. The validator samples at fixed
timestamps; elements at opacity:0 / mid-fade get measured as if
fully visible, producing spurious failures. Tells the agent to
verify visually before changing colors to clear a WCAG warning
— bumping a color to fix a sampling artifact changes brand
identity for no real benefit.
Second-batch audit cleanup after Ular's "logo isn't a requirement,
just a nice default" correction. Three related places still framed
captured-asset-primary beats as rare exceptions and the brand-floor
rules as hard MUSTs — both overstatements that contradict the rest
of the dial-back. Plus a TOC-only callout on capabilities.md.
- step-3:300 "for the RARE beat where a captured asset is the
primary visual ... defaulted to the slideshow pattern this
workflow exists to break" — rewritten. Captured-asset-primary
beats are a normal valid choice. The narrow no-go is just pasting
product-UI screenshots full-bleed.
- step-3:351 "Each one has a composed visual that carries it" —
rewritten to "Each one has a primary visual that carries it
(composed UI, captured asset, kinetic typography, WebGL, etc.)".
- step-3:353 "assets decorate concept-defined beats; they do not
seed them" — kept "do not seed" (correct: don't write a beat
because of a cool asset); dropped the "decorate" framing
(overgeneralized — assets can be primary too).
- step-3 brand-inflection floor section: relabeled from "REQUIRED
minimums" to "Brand defaults (nice-to-haves for most brand
videos)". "MUST appear" softened to "for most brand videos,
the logo lands in the opener and the closer" with explicit
"skippable when the storyboard's concept calls for it" language.
- step-3:379 "The bar:" bullet: "brand-floor minimums ... the
minimum, not the ceiling" → "brand-defaults section covers most
brand videos but isn't a hard requirement."
- step-5:413 "Brand-floor check" section in the per-beat read
protocol: relabeled "Brand-defaults check", reframed each item
as a default not a fail-condition; agent checks against the
storyboard's intent rather than enforcing a hard rule.
- capabilities.md top: added a "Scan the TOC; do NOT read this file
linearly" callout — it's a 700+ line inventory; agents should
jump to the section a beat needs, not read top-to-bottom.
Three follow-ups caught by a post-restructure audit pass. All three
were places where the earlier "compose primary, asset is accent"
framing survived after the step-3 and step-5 paragraphs already got
the primitive-toolkit rewrite. Cleans up the contradiction so the
skill speaks with one voice: captured assets can be primary content;
the narrow no-go is just pasting product-UI screenshots.
- step-2-brief.md:80 — the "flip it" example said agents should
reframe "the hero illustration centers the opener" into "kinetic
typography ... hero illustration as ambient depth." That reverses
the dial-back: captured illustrations CAN center an opener. The
flip-it rule now applies narrowly to product-UI screenshots; for
captured logos/illustrations/hero art, no flip is needed.
- step-2-brief.md:149 — option-template guidance said "primary
content is 'the screenshot of X'" was forbidden. Narrowed to
"primary content is a pasted product-UI screenshot." Other
captured assets (SVG logos, illustrations, hero art) are valid
primaries when the concept calls for them.
- step-3-storyboard.md:314 — Common-accent-uses bullet implied
accents are always layered on "composed UI." Reframed: list
accent uses for when the primary is something else; when the
captured asset IS the primary (logo opener, hero parallax),
document it under Composition, not Accents.
Cleans up two related overcorrections that crept across the skill
prose: (a) "compose UIs from divs/SVG/CSS" repeated 6+ times in
step-1, anchoring agents to website-shaped beats; (b) "every beat's
primary visual stays composed from divs / SVG / CSS / GSAP" and
"captured assets are accents — they decorate, they don't carry"
overstatements in step-3 and step-5 that contradicted the dial-back
done earlier in this stack.
The real framing: a beat composes from whatever primitives the scene
needs — HTML/CSS, SVG, captured assets, WebGL, Canvas, Three.js,
kinetic typography, Lottie — alone or in combination. They're inputs
to one output (the video frame). No rule maps intent → primitive.
The narrow no-go is one rule: never paste a product-UI screenshot as
load-bearing content (the slideshow pattern).
step-1-design.md (8 edits):
- L5 intro: drop "composed from divs/SVG/CSS at build time" detail.
- L7 length: drop "compose UIs from scratch (divs/SVG/CSS)" framing;
merge L290's "over-investing in prose" caveat in.
- L97: "composing UIs from divs in Step 5" → "building beats".
- L161: "compose the X UI" → "a beat featuring the X".
- L290: duplicate length bullet — deleted.
- L293: "sub-agents compose UIs at build time from divs/SVG/CSS..."
→ "No separate Components section — Quick Reference is where
components live."
step-3-storyboard.md (3 edits):
- L3 (intro): "alongside composed UIs" → "alongside composed beats".
- L276 ("Compose the load-bearing visuals yourself") paragraph
replaced with the primitive-toolkit framing — toolkit is open, the
only no-go is product-UI screenshots as load-bearing content.
- L381–383 ("The bar:") three bullets collapsed to one bullet:
primary visuals use whatever combination the scene needs; accents
are optional; brand-floor minimums are the minimum.
step-5-build.md (2 edits):
- L104 stacked-beats intro: "composed from divs, SVG, canvas, and
CSS. Never a full-bleed screenshot." → "composes from whatever
primitives the storyboard called for ... Narrow no-go: never a
full-bleed product-UI screenshot as load-bearing content."
- L147: "Build the UI element from divs and CSS" → "Build the
element from divs and CSS" — drops the UI bias since this rule
applies only when the asset IS a product-UI screenshot.
Net result: "compose from divs/SVG/CSS" mentions drop from 10+ to 0
as a generalized framing; the term survives only in concrete
examples (e.g. "cards-as-divs" when the beat is specifically a
kanban demo) where divs/CSS IS the right answer.
Step 0 had bloated to 91 lines that did the work of Steps 1–3:
viewing contact sheets cell-by-cell, reading 8 data files, listing
promising assets, inferring product purpose / audience / value prop
/ brand voice. That meant the agent did all the heavy lifting
upfront, produced summaries that went stale before they were used,
and the actual "run the capture" instruction was buried.
Step 0 now owns only what Step 0 is: run the capture command,
sanity-check it succeeded, hand off. 91 → 55 lines.
Moved (composed into destination files, verified each was the right
home before adding):
- Read tokens.json + design-styles.json → step-1-design.md replaces
the passive "you read these in Step 0" line with an active
"Read these now — primary data source for Sections 3–6."
- Contact-sheet "every cell, name 5 assets per page" anti-glance
prose → step-3-storyboard.md asset-discovery bullet (which already
covered contact-sheet viewing generally, now strengthened with
the anti-glance rule).
- Strategic site summary (product / audience / voice / value prop)
→ step-2-brief.md absorbed this; the brief itself IS the summary.
Replaced "After presenting the site summary (from Step 0)" with
step-2 grounding itself by reading DESIGN.md + asset-descriptions
+ visible-text directly.
Step 0's new structure:
- Run the capture (CLI command + project-dir convention) — unchanged
- Confirm it succeeded (1-line summary, error-out on bad capture)
- Reference table mapping each capture/ file to the step that
first reads it (explicit "DO NOT read these here")
- Gate: capture exits 0 + counts non-zero
SKILL.md grew to 192 lines from a 124-line baseline. Most of the
bloat was content duplicated in the step reference files it points
to. Removed 6 sections that duplicated step content, composed 2
small additions into the step files where they actually belonged.
Removed from SKILL.md (already covered elsewhere):
- "Take your time" / "Quality matters more than speed" paragraph
— operational philosophy already implicit in step-6-validate's
cell-by-cell review prose.
- "Creative Tension Principle" section — step-3-storyboard.md:21
already has the exact "What makes this video different from a
generic [video type] for any [industry] brand?" single-sentence
test. Duplicate removed; storyboard is the right home.
- "Step -1: What we're actually making" (30 lines: anti-patterns,
video grammar, shot framing, camera moves) — duplicates step-3-
storyboard.md:197+ (shot types), :229–232 (anti-patterns), and
beat-builder-guide.md:126+ (shot framing).
- "Sub-agent mode" + "No sub-agents" preamble — step-5-build.md:286
–292 already handles both parallel and serial runtimes.
- "Image-viewing capability" warning — operationally implicit in
step-0 ("View the contact sheets") and step-6 ("View snapshots/
contact-sheet.jpg cell-by-cell").
- "User Interaction Points" table — redundant with the inline 💬
markers on Steps 3 and 4.
Composed into step files (content that wasn't there yet):
- step-1-design.md "Target length" paragraph: added the fast-pacing
/ billboard-per-beat exception (50-line DESIGN.md is enough when
beats are single hero elements on full-bleed backgrounds, not
full UIs).
- step-2-brief.md "Surprise me" section: added the global-propagation
rule — when the user signals autonomous mode at Step 2, every 💬
gate downstream (Step 3 storyboard approval, Step 4 TTS choice) is
also skipped.
Step 5 SKILL.md gate paragraph trimmed from a 6-clause description
of the per-beat read to one line that points at step-5-build.md
for the full checklist.
Updated the techniques.md reference counts from "20" to "13" in
SKILL.md, beat-builder-guide.md, and step-3-storyboard.md to match
the techniques.md trim in the upstream branch.
Net: SKILL.md 192 → 131 lines.
- Delete `references/visual-vocabulary.md` and scrub the four call
sites that referenced it. The 6-axis lookup framing it introduced
contradicted the rest of the skill's "design from the brand, not
from a table" stance.
- Replace all `npx tsx packages/cli/src/cli.ts <cmd>` invocations
with `npx hyperframes <cmd>` in step-0-capture.md, step-5-build.md,
step-6-validate.md, and beat-builder-guide.md. The capture- and
snapshot-pipeline improvements that previously required the local
CLI now ship in the published CLI via the stack's PRs #987 and
#988, so once the stack lands the published CLI is the right
invocation for the skill prose.
- Remove the now-contradictory "ALWAYS use the local CLI — never
npx hyperframes" warnings in step-0-capture.md and step-6-validate.md.
Rewrite of the website-to-hyperframes skill that came out of 11
evaluation rounds. The honest read of those evals: prose-only
guidance had hit its ceiling — sub-agents kept reporting "0 errors,
looks good" without doing the work, producing slideshow-quality
videos with mismatched brand colors, missing logos, and beats that
didn't serve the storyboard. This restructure addresses the
failure modes that real videos showed, not theoretical ones.
**Step structure (replaces 7-step layout with concept-first 6-step)**
Old: capture → design → script → storyboard → vo → build → validate
New: capture → design → brief → storyboard → vo → build → validate
The brief step (Step 2) is new: a conversation-shaped step that
aligns message + audience + arc before any beat-writing happens.
Concept-first throughout — message → arc → beats that serve the arc
→ which assets and techniques bring each beat to life.
**Step 0 (capture)**
- "View the contact sheets — carefully, every cell, not a glance"
closes the failure mode where agents reported "viewed the contact
sheet" after one scroll and later wrote beats referencing assets
that didn't exist or missed the brand logo.
- Names the right artifacts to read in order (tokens.json →
design-styles.json → asset-descriptions.md → fonts-manifest.json),
with read-on-demand guidance for the rest.
**Step 1 (design)**
- DESIGN.md authoring guide. Restored component CSS sections
(Component Stylings, Spacing & Layout, Depth & Elevation) that
earlier batches over-collapsed.
**Step 2 (brief)**
- Strategy/messaging step. Clear instruction for "Surprise me" /
minimal direction: state the minimum context (where the video
runs, who it's for) and proceed bold.
**Step 3 (storyboard + script)**
- Concept gate at the top — answer "what makes this video distinct"
before writing beat 1.
- Brand-floor MUST rules (logo in opener + closer; signature visual
somewhere in the video).
- Captured assets (SVG logos, illustrations, hero art, gradients)
are first-class beat content alongside composed UIs — many of
them carry beats outright. The constraint is only that you start
from the message, not the asset inventory.
**Step 4 (vo)**
- TTS ranking: HeyGen first (auto word timestamps), ElevenLabs
second, Kokoro free. Audio timing reconciliation gate: if actual
audio duration ≠ storyboard planned ±15%, rescale beats or trim
script before Step 5.
**Step 5 (build) + beat-builder-guide.md**
- Sub-agent template now pastes brand values inline rather than
telling the sub-agent to re-read DESIGN.md. Targeted file reads
with specific sections + line ranges.
- "Patterns that ARE shots" affirmative list (captured logo
draw-on, hero illustration push-in, captured screenshot with
parallax layers, kinetic typography over captured asset).
- Webpage-mimicry patterns (full CSS browser chrome, parked-camera
composition, ±2px breathing motion) marked ⚠ rather than ❌ —
fine when the storyboard genuinely calls for them as the subject.
- Required cinematography per beat: shot type, camera move, depth
strategy, purpose.
**Step 6 (validate) — per-beat read protocol**
This replaces the previous "spawn verify-beats CLI" gate. A grep
of composition HTML can catch structural lies (missing hex codes,
wrong asset paths) but it can't catch boring beats, off-screen
logos, GSAP timelines that only cover the first 2 seconds, or
camera moves that don't match the storyboard. Those failures only
surface when somebody opens the file and reads it.
Per-beat verdict template names the brand hex codes used, captured
asset paths referenced, headline `font-size`, GSAP timeline
coverage, and storyboard alignment. Critic sub-agent scores a
"Captured asset utilization" dimension specifically so the eval
captures whether captured SVGs/illustrations carried beats or got
recreated as divs.
**Asset bundle**
- 20 Pixabay-licensed SFX files with `CREDITS.md` documenting
provenance. SFX assignment moved to Step 3 (creative decision)
so Step 5 implements rather than improvises.
- Capabilities reference + html-in-canvas-patterns updated:
Three.js 0.181.2 + ESM jsm imports, mulberry32 seeded PRNG for
deterministic shatter, 24-effect text-animation catalog
referenced (catalog itself lands in the hyperframes-skill PR).
- Visual vocabulary rewritten: replaces user-word lookup tables
with brand-first derivation across 6 axes; user words land as
modifiers, not replacements.
Two new composition lint rules catching failure modes that recurred
across the 11-round website-to-video eval. Both ship with vitest
coverage; total lint suite goes from 148 to 151 tests.
**`fonts.ts` (new) — two warnings**
- `google_fonts_import`: composition loads fonts from
`fonts.googleapis.com` via `<link>` or `@import url(...)`. External
font requests fail in sandboxed/offline renders and add latency.
Fix hint points to root-relative `capture/assets/fonts/...woff2`
with a local `@font-face` declaration.
- `font_family_without_font_face`: CSS uses a font-family that
isn't declared with `@font-face` and isn't in the auto-bundled
font set (Inter, JetBrains Mono, etc.). Text would silently fall
back to system-ui — the visual fidelity loss the eval kept hitting.
Fix hint points to the captured woff2 files.
**`composition.ts` invalid_capture_path (new) — one error**
Sub-compositions live in `compositions/` but get served with the
project root as their base URL. `<img src="../capture/...">` works
on disk but 404s in Studio and renders. Errors with a fix hint
saying replace `../capture/` with root-relative `capture/`.
Three vitest cases: `<img>` triggers, multi-occurrence url()s are
counted, root-relative paths stay clean. Registry source files and
installed blocks are exempted.
**Wiring**
`hyperframeLinter.ts` runs the new fonts rules alongside the existing
rule set; the composition rule was added inline so it picks up
automatically.
Detects when an element has CSS `opacity: 0` (inline or style block) AND
is targeted by gsap.from({opacity: 0}). Since from() animates FROM the
specified value TO the CSS value, this produces a 0→0 animation where
the element never becomes visible.
Root cause of all-black renders from the product-launch-video skill:
every text element had opacity:0 in CSS + gsap.from({opacity:0}),
making all text permanently invisible despite the timeline "working."
Fires as error (not warning) to block the render pipeline. Includes
actionable fix hint. 4 test cases: inline style, style block, clean
code (no false positive), and gsap.to() exit (no false positive).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Required by the contact-sheet pagination code added on this PR
(uses Sharp APIs that landed in 0.34.5). Originally bumped on
#987 by mistake — moved here per Copilot review.
Capture pipeline work that came out of the 11-round website-to-video
eval branch. The wins that actually moved quality were the artifacts
agents read (contact sheets, design-styles) and the snapshot tool
visual-verification fixes; the rest are smaller follow-ons.
**Contact sheets (`contactSheet.ts`, new)**
- Replaces the embedded one-image-per-asset listing with paginated
labeled grids (3-col screenshots / 4-col raster / 5-col SVG). Each
page contains 9–15 cells with filename labels baked in via SVG
text overlay (`escapeXml` covers `&<>"'`).
- `fit: "contain"` keeps every asset visible at its real aspect
ratio; the old `fit: "cover"` cropped to the first image's box.
- Returns `string[]` (page paths) — single-page captures get one
file, multi-page produce `contact-sheet-1.jpg`, `contact-sheet-2.jpg`,
etc.
- `createSvgContactSheet` scans both `assets/svgs/` (inline-extracted
SVGs) and `assets/` root (external SVGs from `<img src="*.svg">`)
and de-dupes by filename. Sites with all-external SVGs (huly.io)
now get coverage they previously didn't.
**Design styles extractor (`designStyleExtractor.ts`, new)**
- Walks the live DOM and reads computed styles to produce
`extracted/design-styles.json`: typography hierarchy (every text
role with exact font-size / weight / line-height / letter-spacing),
button variants (background / padding / radius / shadow), card /
container / nav styles, spacing scale with base unit, border-radius
scale, box-shadow values with usage counts.
- Primary data source for DESIGN.md authoring at Step 1. Replaces
the prior "guess from screenshots" workflow.
**Snapshot tool (`snapshot.ts`)**
- HyperShader pre-rendering used to swallow the entire snapshot
capture window (every frame after the first showed the loading
overlay or final-opacity-zero exit fades). Wait signal is now
`window.__hf.shaderTransitions[].ready` (set after both warm and
cold cache paths complete); local-time seek for sub-comps means
exit fades read at their own t=0..duration, not global time.
- Gemini vision per-frame analysis runs by default (`descriptions.md`
next to the contact sheet). `--describe "custom Q"` overrides the
prompt; `--describe false` opts out.
- 3-column contact sheet generation for snapshot frames so reviewers
see all beats at a glance.
**Screenshot capture (`screenshotCapture.ts`)**
- Replaces `querySelectorAll('*') + getComputedStyle` overlay scan
with a TreeWalker that early-exits on cheap rect checks before
reaching the expensive style read. Caps at 5000 elements per page.
- Cookie/consent dismissal selectors are scoped under cookie /
consent / gdpr ancestors so we don't click "Accept invitation" or
similar unrelated buttons.
**Agent prompt (`agentPromptGenerator.ts`)**
- Auto-discovers contact-sheet page count (matches base name plus
paginated `-NNN` variants only, with regex escaping on the base
name and numeric sort for 10+ pages).
- `inferColorRole`: classifies extracted hex colors as bg-dark /
bg-light / accent / surface / neutral via luminance + saturation,
so the agent prompt shows `#533AFD (accent)` instead of bare hex.
- `design-styles.json` row is gated on `existsSync` — the upstream
write is wrapped in try/catch and may skip on failure, so the
prompt only points to files actually on disk.
**Other CLI ergonomics**
- `cli.ts`: auto-load `.env` from CWD on startup so subcommands like
`snapshot` don't need explicit `export GEMINI_API_KEY=…`. Handles
`export FOO=bar`, quoted values, inline `# comments`.
- `commands/transcribe.ts`: default output dir is the input file's
directory, not CWD. Stops the "wrote transcript.json somewhere
unexpected" footgun.
- `assetDownloader.ts`: improved asset naming uses catalog context;
de-duplicates inline SVG filenames.
- `contentExtractor.ts`: captions SVGs via Gemini (code-as-text) and
integrates them into asset descriptions.
- `tokenExtractor.ts` + `types.ts`: SVG bounding box dimensions and
new DesignStyles schema added.
Follow-up to the playhead-preservation fix: clean up the now-unreachable
crossfade infrastructure that was only triggered by refreshKey changes.
- Remove retiringKey state, retiringTimerRef, handleNewPlayerLoad
- Remove the retiring Player render block and conditional onLoad/style
- Drop refreshKey from getPreviewPlayerKey signature and NLEPreviewProps
- Stop passing refreshKey from NLELayout to NLEPreview
- Update NLELayout comment to reflect current iframe.src reload model
- Simplify getPreviewPlayerKey test
The text-rendering:geometricPrecision rule (b7bd9565) shifted glyph advances
~1% in headless Chrome. Baselines for png-sequence, heygen-promo-preview-assets,
and sub-composition-video were already regenerated — sub-comp-t0 was missed.
Also improves the fixture's visual contrast:
- Background: #07110d (near-black) → #0f172a (slate-900)
- Hook scene: background #1e293b, text #ef4444/#ffffff
- Later scene: background #334155, text #ffffff
- Absolute positioning for text elements (compiler inlines sub-comp
content at intrinsic width, collapsing the flex container)
Baseline regenerated inside Dockerfile.test per CLAUDE.md. Test passes:
0 failed frames, 100/100 checkpoints green.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Stop NLEPreview from including refreshKey in the Player's React key.
Previously, a refreshKey change caused a full Player teardown + remount,
which destroyed the playback adapter before refreshPlayer() could save
the seek position — so the playhead always reset to 0:00.
Now refreshKey changes only trigger refreshPlayer()'s lightweight
iframe.src reload path, which correctly captures the current time via
saveSeekPosition() before reloading the iframe content.
Closes#996
Send `stack_trace` (up to 4KB) on crash, unhandled_error, and
unhandled_promise_rejection events so PostHog captures the full JS
call stack for debugging. Also bump `component_stack` from 500→2000
chars, and add `error_name` to promise rejections.