Commit Graph
81 Commits
Author SHA1 Message Date
ukimsanov c15819fe88 feat(studio-server): files route extensions
What: the studio-server files route at its final NLE-stack form, with its
test suite (25 tests).

Why: standalone package seam — the server-side dependency of the studio
asset workflow, reviewable in isolation.

How: additive route behavior; existing route consumers unchanged.

Test plan: bunx vitest run src/routes/files.test.ts in packages/studio-server;
tsc --noEmit in packages/studio-server; fallow audit clean.
2026-07-11 18:00:53 -07:00
ukimsanov 8f022d06ec fix(studio): remove selection overlay fill 2026-07-06 21:02:36 -07:00
ukimsanov b38c054baf docs: document Studio Color Grading 2026-07-06 15:29:37 -07:00
ukimsanov 04f96da169 fix(studio): share safe media path cleanup 2026-07-06 14:35:41 -07:00
ukimsanov e0090efbf6 fix(studio): avoid regex in media asset path cleanup 2026-07-06 14:15:30 -07:00
ukimsanov 30a944de34 fix(studio): split color grading inspector files 2026-07-06 13:56:37 -07:00
ukimsanov 139cf568b4 feat(studio): add color grading inspector 2026-07-06 13:40:20 -07:00
ukimsanov 413ee07da5 fix(studio-server): share background removal job runner 2026-07-06 13:27:45 -07:00
ukimsanov a3bf7eb995 feat(studio-server): add media processing routes 2026-07-06 13:25:04 -07:00
ukimsanov 644ae384a5 fix(runtime): satisfy color grading audit 2026-07-06 12:49:34 -07:00
ukimsanov 870964b0cf feat(runtime): render media color grading shaders 2026-07-06 12:44:43 -07:00
ukimsanov 7c14438496 feat(core): add media color grading contract 2026-07-06 02:36:17 -07:00
ukimsanov 52d586dd31 docs: fix 53 inaccuracies across all documentation
Exhaustive audit of every MDX file in docs/ against skill references
and package source code. Every API signature, default value, flag,
and technical claim verified against ground truth.
2026-07-05 21:26:41 -07:00
ukimsanov a85a507cdc fix(shader-transitions): page-side render dropped shader transitions and final-scene content
Page-side compositing (default on) silently dropped HyperShader.init shader
transitions in the engine render. The compositor clones the from/to scenes to
feed drawElementImage, but cloneNode copies the GSAP opacity-fade, and Chrome
won't paint hidden elements, so drawElementImage throws "No cached paint record"
and the shader degrades to a hard cut. Force the clones visible before capture,
as the html2canvas path already does via forceSceneVisibleInClone.

Also fixes the final scene's content dropping in the last beat: the core clip
runtime hides it shortly before the composition ends, and page-side screenshots
the live page (the layered path survives via forceVisible per-scene capture).
Un-hide the settled scene on non-transition frames.

Page-side only; retains the ~6.5x page-side speedup.
2026-06-20 21:19:55 -07:00
ukimsanov 969d6a334b fix(cli): default capture output to ./capture/ (auto-suffix capture-2/, capture-3/ on re-run)
`hyperframes capture <url>` (no -o) used to dump into `./captures/<hostname>/`,
which buries the project two levels deep and silently merges re-runs into the
previous dir — file-by-file, so leftover screenshots / assets from the prior
run stay mixed in and any later `glob` sees both.

Switch the default to `./capture/`. When it already exists, auto-suffix to
`./capture-2/`, `./capture-3/`, … (up to -99). Each capture is its own clean
directory — no crud, no friction, no clobber. The CLI prints a one-line note
when the suffix kicks in so the user sees which dir actually got written.
Explicit `-o <name>` is unaffected (still overwrite-tolerant).
2026-06-16 22:26:31 -07:00
ukimsanov 12955869ec refactor(studio): simplify color grading controls 2026-06-16 13:41:41 -07:00
ukimsanov 3661d51e6d refactor(studio): simplify color grading inspector 2026-06-16 13:41:41 -07:00
ukimsanov 8b92f37635 feat(studio): add color grading inspector controls 2026-06-16 13:41:41 -07:00
ukimsanov d1162cd1b1 refactor(runtime): trim color grading api 2026-06-16 13:41:32 -07:00
ukimsanov 48fb42e5f0 refactor(runtime): trim color grading helpers 2026-06-16 13:41:32 -07:00
ukimsanov 1ad158d2f0 feat(runtime): apply color grading in preview and render 2026-06-16 13:41:32 -07:00
ukimsanov 8aaaaf1812 refactor(core): trim color grading api surface 2026-06-16 13:41:28 -07:00
ukimsanov 90582d7faa refactor(core): trim color grading helpers 2026-06-16 13:41:28 -07:00
ukimsanov 3109acb88a feat(core): add color grading schema and lut parsing 2026-06-16 13:41:28 -07:00
ukimsanov f8d9f51245 fix(cli): restore hyperframes capture <url>; move video download to --video flag
PR #1447 added `capture video` as a citty subCommand. citty's runCommand
(node_modules/.bun/citty@0.2.2/.../dist/index.mjs:209-227) treats any non-flag
positional as a subcommand-name attempt and throws E_UNKNOWN_COMMAND when it
doesn't match — there's no fallback to the parent's positional args, so
`hyperframes capture https://vercel.com` died with "Unknown command https://vercel.com".

Per James's suggestion, surface video-download as `capture --video <project>`
(a mode flag) instead of a subcommand. Citty has no issue with a positional
URL coexisting with flags. `video.ts` now exports `runVideoMode()` instead of
a `defineCommand` default export.

- `hyperframes capture <url>` works again
- `hyperframes capture --video <project> --index N` downloads video
- `hyperframes capture --video <project> --list` lists manifest
- `hyperframes capture --video <project> --video-url <url>` downloads by URL
2026-06-15 16:03:27 -07:00
ukimsanov 6a024a367b feat(cli): capture-video on-demand fetcher + capture pipeline robustness
For the hyperframes.dev website-to-video flow. Real-AI-test runs against
heygen.com, huly.io, and heygen-showcase surfaced two gaps: (1) capture's
logo / asset-captioning signals missed modern React/Tailwind builds; and
(2) there was no CLI surface to pull the videos the manifest references.

New command:

  • `hyperframes capture-video <project>` — on-demand downloader for
    entries in capture/extracted/video-manifest.json. Capture writes the
    manifest + preview PNGs but skips the mp4s; this pulls one entry by
    `--index N` (matched against the entry's `index` field, NOT array
    offset — gaps are possible when a preview screenshot fails). SSRF-safe
    via safeFetch, 250 MB cap, content-type whitelist, race-free
    exclusive-create write. Layout-aware (handles both standalone capture
    and W2H project layouts).

Capture pipeline fixes:

  • Structural logo signals (assetCataloger + tokenExtractor): inBanner /
    inHomeLink / matchesTitleBrand. Class-substring alone caught 0/32 SVGs
    on heygen.com — modern builds don't put 'logo' / 'brand' in any
    className.

  • Content-hash SVG slugs (assetDownloader): `svg-<8char-sha1>.svg` —
    label-derived slugs mis-attributed partner-logo carousels
    (heygen-logo.svg actually contained Google, hubspot-logo.svg contained
    Trivago, etc.). Content-hash names are invariant by construction.

  • SVG → PNG rasterization before Gemini Vision (contentExtractor): the
    raw-SVG-as-text path was hallucinating wordmarks (VIVIENNE for HubSpot,
    'wrestling' for Workday). Adds polarity detection so a white-glyph SVG
    flattened to a blank PNG gets inverted before captioning. LOGO tag in
    asset-descriptions.md when structural signals fire (independent of
    Gemini key presence).

  • Double-escape \/ inside the page.evaluate template literal in
    assetCataloger + tokenExtractor: the original `/^https?:\/\/.../`
    collapsed to `/` mid-template and threw `Unexpected token ^`. Capture
    was 100% blocked on this until the escape was fixed.

  • `asset-descriptions.md` header branches on Gemini-key presence with
    an explicit 'Vision OFF — catalog-derived descriptions' warning.

New lint rule:

  • `lintMissingLocalAsset` (cli/utils/lintProject): scans <video> / <img>
    / <source> src for local files that don't exist in the project.
    Empirically the most common sub-agent mistake across multi-URL runs
    (~5+ per run). Uses `resolveExistingLocalAsset` so the existence check
    matches the bundler's notion of 'resolves'. Masks comment / style /
    script ranges before scanning so a literal `<img src=missing.png>`
    inside a tutorial comment isn't reported.

Tests: 17 new for capture-video (safeFilename decoding/sanitization,
VIDEO_CONTENT_TYPE_RE accept/reject, pickManifestEntry index-field lookup
with gaps, URL-mismatch + bad-index rejection, --index over --url
priority); 70 cases under lintProject.test.ts covering the new rule and
existing rules.

Sibling PRs in this stack:
  • #PR_A1 — fix(producer): __dirname ESM banner shim
  • #PR_A2 — fix(core/lint): findRootTag masks comment/style/script
2026-06-15 01:41:04 -07:00
ukimsanovandClaude Opus 4.7 3542a79b3a fix(skill): reconcile SFX drift tolerance to ±0.1s across script + prose
Vai (vanceingalls) caught a 10× tolerance mismatch between the script
and the prose. Rames confirmed as blocking:

  step-5-build.md:458 (per-beat evidence rule):  ±0.05s
  step-6-validate.md     (playback verification):  ±0.1s
  w2h-verify.mjs:29 (SFX_DRIFT_TOLERANCE_S):       0.5s

So an agent writing per-beat evidence at ±0.05s reports a 0.3s drift
as FAIL, while the script reports the same drift as PASS. The pasted-
verbatim report contradicts the agent's evidence block — exactly the
kind of internal contradiction this PR was built to eliminate.

Converged on ±0.1s everywhere:

- w2h-verify.mjs:29: SFX_DRIFT_TOLERANCE_S = 0.1 (3 frames at 30fps)
- step-5-build.md:458: ±0.05s → ±0.1s, with cross-reference noting it
  matches the script + step-6 playback floor

The other ±0.5s constants in step-6 are for total audio/video duration
and storyboard beat-range matching — those are coarser-grained timing
checks (not SFX-to-visual sync). Left as-is intentionally.

Regression check: huly-v3 now flags 4 SFX drifts instead of 3 — the
new one is glitch-1.mp3 at 0.20s drift (6 frames). The old 0.5s
tolerance was masking this real timing issue.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 12:15:39 -07:00
ukimsanovandClaude Opus 4.7 a5bc11632f fix(skill): address PR #1026 review — find $HOME, order-indep audioRegex, cached reads
Three issues from Miguel's + Rames's reviews:

**[Blocking] find / violates CLAUDE.md guidance (Miguel)**

CLAUDE.md says: "When running find, search from . (or a specific path),
not / — scanning the full filesystem can exhaust system resources on
large trees." I introduced 3 instances of `find /` in skill prose to
help sub-agents locate skill files from unknown CWDs. Replaced all 3
with `find "$HOME" ... -maxdepth 10`. Verified all 4 skill files
resolve correctly under $HOME on the testbed setup.

Files: step-3-storyboard.md (×2), step-5-build.md, step-6-validate.md.

**[Blocking] SFX audio regex assumed attribute ordering (Miguel + Rames)**

The v2 audioRegex required src= to appear lexically BEFORE data-start=
in the same <audio> tag. But capabilities.md:365 — in the same skill —
documents the canonical pattern with src= LAST:

  <audio id="..." data-start="..." data-duration="..." data-volume="..."
         data-track-index="..." src="...">

Real compositions following the docs would have audio tags that don't
match the regex → SFX reported as MISSING → false FAIL in the script
output → false alarm in the user-facing summary. Exactly what v2 was
supposed to fix.

Replaced with the same two-step shape that readBeatDurationsFromIndex
already uses correctly: match `<audio[^>]*?>` to grab the whole tag,
then extract src= and data-start= from the tag string with independent
regexes. Verified both attribute orderings (src first, src last) now
work via inline node test.

**[Minor] readBeatCompositions / readBeatDurationsFromIndex re-read on
every call (Rames)**

Added process-scoped caches to both helpers. The script is a one-shot
CLI so no invalidation needed — first call hits disk, subsequent calls
return the cached result. readBeatCompositions was called 3×,
readBeatDurationsFromIndex 2× — now 1× each.

**Regression checks**

- huly-v3: 4 PASS · 3 FAIL · 1 INFO (unchanged — same 3 real issues
  flagged: 48px wordmark, missing shaders, 3 SFX drifts)
- huly-launch-v4: 6 PASS · 0 FAIL · 2 INFO (unchanged)
- Lint + format: clean

2 files changed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 10:44:45 -07:00
ukimsanovandClaude Opus 4.7 f4a7961bc1 fix(skill): w2h-verify v2 — kill false positives from real-agent debrief
A fresh agent session ran the v1 verify script and the disclosure pasted
into their final summary showed 3 FAIL rows for things that weren't
actually defects:

  Headline font-size: flagged Beat 2 (wordmark SVG), Beat 3 (UI grid),
    Beat 5 (terminal). None of these legitimately have text headlines.
  Timeline coverage: flagged 5/6 beats because the script's regex only
    saw `tl.X(..., 2.5)` literal positions and missed forEach loops,
    variable-position tweens, and long-duration scaler tweens.
  Beat durations: flagged 2 beats because my "duration X.Xs near beat
    label" fallback false-matched non-beat durations
    (e.g., "shader runs — duration 0.7s" near a "Beat 1" mention).

The agent had to write ~5 paragraphs defensively justifying each false
FAIL. That's friction we can fix.

Tested against the agent's actual project (huly-launch-v4): went from
4 FAIL (3 false positives + 1 real bare-table parser miss) to 0 FAIL.
Also re-verified huly-v3 still correctly catches its 3 real issues
(48px wordmark, missing shaders, 3 SFX drifts) — no regression.

**Brand visuals check**

Switched from "≥30% asset usage" (gameable, rewards quantity over quality)
to "at least 1 beat references a captured hero/image/svg" — quality
signal that's cheap to satisfy when real, hard to fake. Excludes fonts,
logos, favicons, contact-sheets.

**Headline check**

Now only flags beats where the LARGEST font-size is in the 40–<80px
range — the "aspiring headline but too small" zone. Below 40px = beat
has no text headline by design (terminal, UI labels, SVG-only); skip.
≥80px = proper headline; pass. Eliminates the false positives on
SVG-dominated and UI-grid beats while still catching the real "headline
too small" failure (Beat 4 at 72px in this run; Beat 1 wordmark at 48px
in another).

**Timeline coverage check**

Three improvements:
1. Detects forEach loops + for-loops containing tl.X() calls — beats
   with these have events at positions the static parser can't read;
   mark as INFO-skipped rather than failed.
2. Detects long-duration tweens — if a single tween's duration covers
   ≥70% of the beat duration (camera dolly, breathing animation), the
   beat has full coverage via persistent motion; skip the position check.
3. New paren-balanced parser for extracting tl.X() position arguments —
   the v1 regex was matching `rgba(86,131,218,0.35)` and capturing 0.35
   as a tween position. The new parser walks paren depth and only
   captures top-level trailing numeric args. No more rgba false matches.

**Shader transitions check**

Two fixes:
1. Filter out inventory lines — lines listing 3+ shader names are
   "what's available," not "what's planned for use." Real use
   mentions one or two shaders per line.
2. Apply the same SFX-context exclusion to the declared side that the
   present-check side already had — "glitch" inside `sfx/glitch-1.mp3`
   no longer counts as a declared shader transition.

For huly-v3: was 6 declared (1 phantom from inventory + 5 + glitch
from SFX), now 2 declared (light-leak, cinematic-zoom) — matches the
storyboard's actual plan.

**Beat duration check**

1. Dropped the "duration X.Xs within 200 chars of beat label" fallback
   — too loose; matched shader durations, animation durations, anything
   labeled "duration". This was the source of the 0.70s misread in the
   debrief.
2. Added a bare-number timing-table parser for the format
   `| 1 | 0.00s | 5.20s | 5.20s | ... |` (with optional `>` blockquote
   prefix). Computes duration = end - start.
3. Added a negative lookahead so `\bB3\b` doesn't false-match "B3.1"
   sub-beats and grab the wrong row.
4. Filter buildBeatIds to only numbered beats — skips the root
   composition (`data-composition-id="main"`) so it doesn't inflate
   "parseable" count.

**Brand visuals + asset count**

Excluded fonts/ subdirectory (always-used via @font-face → would
always pass) and contact-sheet-*.jpg (pipeline outputs, not website
inputs). Both inflated the denominator and weakened the signal.

**Edge case fixes**

- Removed `basename` unused import (oxlint).
- Fixed shader-name substring overlap: longest-name-first matching so
  "cross-warp-morph" doesn't double-count as "cross-warp".
- SFX timestamps now collect ALL audio tags per file (multi-timestamp
  SFX like click×3); picks closest index timestamp to each storyboard
  timestamp instead of just keeping the last.

**Step 6 doc**

Updated the skill's "w2h-verify — the source of truth" section to
describe the new checks accurately and what failure mode each catches.

2 files changed, +486/-109. Format + lint clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 09:03:35 -07:00
ukimsanovandClaude Opus 4.7 190f1ec71a feat(skill): w2h-verify script + Step 6 DoD wiring — tooling-based enforcement
Three rounds of text-based enforcement plateaued. A third agent debrief
showed the same failures: 9% asset usage (vs ≥30% floor), shader
transitions declared in STORYBOARD.md but not in shipping index.html,
SFX timestamps drifted up to 12.4s, animation-map skipped, MP4 not
rendered, honest-disclosure section omitted from final summary.

The pattern is clear: language-only enforcement is selectively
interpretable by the agent under ship pressure. Move enforcement into
tooling — facts the agent can't fudge.

**New script: `skills/website-to-hyperframes/scripts/w2h-verify.mjs`**

Pure file-analysis script (no shell spawns). Computes six checks and
prints a PASS/FAIL/INFO table:

1. Asset usage — assets referenced in compositions ÷ assets captured;
   target ≥30%. Tested against videos/huly-v3: caught 6/74 = 8% FAIL.
2. Shader transitions consistency — STORYBOARD.md-declared shaders vs
   index.html. Longest-name matching to avoid substring false positives
   (cross-warp-morph not double-counted as cross-warp). Tested: caught
   6 declared / 1 present / 5 missing.
3. SFX timestamp drift — parses STORYBOARD.md table rows for
   `sfx/X.mp3` + time-with-`s`, parses index.html <audio data-start>,
   flags drift >0.5s. Tested: caught 12.4s drift on click.mp3 that
   the agent debrief didn't even mention.
4. animation-map.json existence — explicit file check.
5. Rendered MP4 existence — scans project root, output/, renders/.
6. Required artifacts — STORYBOARD.md, DESIGN.md, SCRIPT.md, index.html
   all present.

Exit code: 0 (all pass) or 1 (one or more fail). The script's output
becomes the Step 6 deliverable — paste verbatim into the user-facing
summary.

**Skill update: `step-6-validate.md`**

Adds `w2h-verify report` to the DoD checklist with the rule: paste the
FULL output verbatim into the final summary. Cherry-picking rows,
substituting adjectives for percentages, or omitting FAIL lines is
explicitly forbidden. If a row says FAIL, either fix it and re-run
until PASS or include the FAIL line verbatim in "What I did NOT
verify" with a one-sentence reason.

Test run against the project that prompted this:

```
SUMMARY: 1 PASS · 4 FAIL · 1 INFO
- Asset usage: FAIL 6/74 (8%) target ≥30%
- Shader transitions: FAIL 6 declared, 1 present, 5 missing
- SFX timestamps: FAIL 3 drifted >0.5s (max 12.4s)
- animation-map.json: FAIL missing
- Rendered MP4: INFO no .mp4 found
- Required artifacts: PASS
```

The agent could selectively ignore "the WCAG warnings are false
positives." The agent cannot selectively ignore a line that says
`6/74 (8%) — target ≥30%`.

2 files changed (+ 1 new script, ~330 lines). Format checks pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 00:45:42 -07:00
ukimsanovandClaude Opus 4.7 5594a8286c fix(skill): w2h enforcement round 2 — close the deeper shirking patterns
A second agent debrief (different session, with the prior enforcement edits
applied) revealed the most damning failure yet: the agent used 1 of 65
captured assets. They wrote their own "Asset Audit" table saying SKIP for
64 hero illustrations, brand SVGs, and signature visuals. The entire point
of capture is to USE the brand's assets — that gate was bypassed entirely.

The debrief also revealed 5 other patterns the prior enforcement missed.
This commit closes all 6.

**Pattern A — Asset Audit gate (step-3-storyboard.md)** — BIGGEST FIX

Adds a non-skippable Asset Audit section in Step 3 that requires viewing
every page of capture/assets/contact-sheet-*.jpg + svgs/contact-sheet-*.jpg,
pasting 5 distinctive assets per page (with descriptions of what's actually
pictured), and choosing USE/SKIP per asset with one-sentence justification
for each SKIP. Brand-defaults floor: at least one beat MUST use a captured
hero illustration/photograph/signature diagram — not just the logo. The
forbidden list explicitly calls out: reading asset-descriptions.md alone
without opening the contact sheets, and rebuilding signature graphics in
CSS when the brand's own SVG of that graphic is in capture/assets/.

**Pattern B — Auto mode scope (SKILL.md + step-2-brief.md)**

Clarifies that auto mode covers user-PREFERENCE gates (TTS provider,
voice, beat count, captions yes/no — where the agent decides on the
user's behalf) but NOT quality-VERIFICATION gates (Asset Audit, per-beat
HTML read, DoD checklist, honest disclosure). Adds explicit test for
distinguishing: if the answer changes the content of the video, it's a
preference; if the answer is "did the verification happen?", it's a
quality gate. The agent that skipped the captions question by reasoning
"auto mode says bias toward action" was misusing auto mode.

**Pattern C — 3-path audio/motion verification (step-6-validate.md)**

Replaces the prior "Path 1 or explicit deferred" with three explicit
paths: (1) Play preview in Playwright, (2) render low-res MP4 and read
frames at ≥5fps, or (3) explicit deferred disclosure with QUANTIFIED
coverage gap ("18/900 frames = 2% coverage"). The percentage in Path 3 is
mandatory — vague "deferred to user" was the loophole. Forbidden: claiming
"confirmed via snapshot" as audio/motion evidence; 18 PNGs from a 900-
frame video is 2% coverage, not verification.

**Pattern D — Sub-agent diagnoses are hypotheses (beat-builder-guide.md)**

When a sub-agent reports "this is a linter false positive" / "this is a
known bug", that is a HYPOTHESIS from one symptom — not a verified
finding. Before propagating the workaround to other beats, main agent
must EITHER read the source to confirm OR explicitly disclose the
unverified claim. The debrief showed the main agent applied beat-2's
"linter false positive" diagnosis to beat-4 without ever reading
packages/core/src/lint/utils.ts to confirm.

**Pattern E — Re-snapshot after parallel sub-agents (step-5-build.md)**

When sub-agents run in parallel, each snapshots a project where sibling
beats may not exist yet. Their snapshots at beat boundaries or during
shader transitions show the WRONG content (typically previous beat).
Required after all complete: a canonical project-wide snapshot via the
CLI — that's what Step 6's DoD uses. Sub-agents' intermediate snapshots
are sanity checks, not the deliverable.

**Pattern F — STORYBOARD.md must be updated when divergence accepted
(beat-builder-guide.md)**

When a sub-agent diverges from spec ("the real brand mark is lowercase
'huly' not uppercase 'HULY'") AND the main agent accepts the divergence,
the main agent MUST patch STORYBOARD.md to reflect reality. Otherwise
the spec lies and the next session reading it as ground truth gets the
wrong information. Examples covered: brand mark casing, cell size at
scale, SFX timing alignment.

6 files changed, +131/-11 (net +120 lines of enforcement).
Format checks pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 21:51:52 -07:00
ukimsanovandClaude Opus 4.7 18444decde fix(skill): w2h enforcement edits — close the 9 shirking patterns from agent debrief
After the audit-fix commit (e47bc6c6) landed clarity fixes, a real test run
revealed that the skill still got skipped at the gates that matter most.
The agent's honest debrief listed 9 distinct patterns where it judged
"deliver fast" over "verify what the skill said to verify."

This commit forces evidence at each of those gates so silent skips are no
longer possible without lying — at which point the gate fails by design.

**Patterns 1, 3, 8 — step-5-build.md**

- Pattern 1: "Read each beat HTML top-to-bottom" gate now requires a
  structured evidence block per beat with quoted CSS hex codes, headline
  font-size, captured asset paths, GSAP first/last events, and SFX trigger.
  "I read it and it looks fine" / "the sub-agent confirmed" are not
  acceptable. Snapshots are 3 frames out of 300+ in motion.
- Pattern 3: SFX timestamp computation rule. Every data-start MUST be
  computed (beat-local + beat global start = global timestamp), not
  estimated by eye. The agent typed `data-start="6.0"` for a storyboard
  moment at 5.0s — a 1-second drift, not a rounding error.
- Pattern 8: Recurring sub-agent workarounds must be surfaced under
  "Tooling issues encountered" — burying them means the next session
  hits the same bug.

**Patterns 2, 6, 7, 9 — step-6-validate.md**

- Pattern 2: WCAG contrast warnings now require per-warning verification
  with quoted validator output and opacity check at the sampled timestamp.
  Blanket dismissal as "mostly transition-window false positives" is
  explicitly forbidden.
- Pattern 6: animation-map.json check added to the DoD checklist —
  runs `skills/hyperframes/scripts/animation-map.mjs` and confirms
  per-beat event coverage.
- Pattern 7: Audio + motion verification is now a separate DoD item from
  snapshot verification. Snapshots are silent stills; you must actually
  play the preview and confirm SFX lands at storyboard timestamps within
  ±0.1s. CLI-only sessions must explicitly disclose this as deferred.
- Pattern 9: Honest disclosure section added — final user-facing summary
  MUST end with "What I verified" and "What I did NOT verify" blocks.
  "Looks great, ready to ship" with no disclosure now fails the gate.

**Patterns 4, 5 — beat-builder-guide.md**

- Pattern 4: Sub-agent FLAG protocol. Required phrasing for non-blocking
  issues is concrete and actionable with line numbers. Forbidden phrasing:
  "if X feels too long, you could...", "consider tweaking...", "might
  want to...". Main agent must address each FLAG or write a rejection.
- Pattern 5: Spec ambiguity escalation. If the storyboard names a
  transition without establishing the start state ("Row 1 transitions
  blue → orange" but Row 1's initial color isn't specified), sub-agent
  MUST flag it and ask for confirmation rather than guess. Picking an
  interpretation silently means the build "looks fine" while diverging
  from intent.

3 files changed, +170/-21 (net +149 lines of enforcement language).
Format checks pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 20:12:46 -07:00
ukimsanovandClaude Opus 4.7 e47bc6c6db fix(skill): w2h audit — resolve contradictions, path issues, missing fallbacks
10 fixes from an audit of skills/website-to-hyperframes/ targeting clarity for
AI agents following the pipeline. Each fix is a surgical edit; no behavior
changes for human readers.

**Critical contradictions resolved:**
- capabilities.md: `onUpdate`/`tl.call` was simultaneously documented as required
  (canvas/WebGL/typing patterns) and banned (determinism). Sub-agents reading
  the ban would silently strip working code from Canvas 2D and Three.js beats.
- step-1-design.md: Removed "Depth & Elevation" section template that the same
  file's Rules section forbade. Renumbered remaining sections (6→5).
- beat-builder-guide.md: Narrowed "no onUpdate for counters" rule so canvas
  rendering loops aren't caught by the prohibition.

**Path resolution fixes for sub-agents:**
Sub-agents run from `<project-dir>` (e.g. videos/foo/), not repo root, so
repo-relative paths like `skills/website-to-hyperframes/assets/sfx/manifest.json`
fail silently. Replaced with `find / -path ...` patterns that work from any CWD.
- step-3-storyboard.md: sfx/manifest.json + text-effects.md paths
- step-5-build.md: beat-builder-guide.md path

**Missing fallbacks added:**
- step-3-storyboard.md Gate: autonomous mode now propagates from Step 2 — gate
  no longer blocks on explicit approval when user said "surprise me".
- step-4-vo.md: timing-formula recalibration now has concrete steps for both
  "too short" (add pauses) and "too long" (cut highest-density beat).
- step-6-validate.md: agent-authored descriptions fallback when GEMINI_API_KEY
  is unavailable — DoD checklist no longer has a dead end.

**Quick Reference polish:**
- SKILL.md: step-1-design entry now mentions 50-line fast-path exception.

8 files changed, +32/-37 (net -5 lines). Format checks pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 17:57:08 -07:00
ukimsanov 0253c2ccc8 fix(skill)!: reference pixel-point/animate-text — stop vendoring
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.
2026-05-21 11:09:31 -07:00
ukimsanov ae9bac69a0 chore(skill): audit cleanup — stale visual-vocabulary ref + 20→13 techniques count
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.
2026-05-21 11:09:30 -07:00
ukimsanov 40774cb3ec refactor(skill): trim techniques.md to actual techniques (13 from 20)
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.
2026-05-21 11:09:30 -07:00
ukimsanov e4a7229298 revert(skill): revert 4 hyperframes files + CLAUDE.md to main
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.
2026-05-21 11:09:30 -07:00
ukimsanov 425b077a17 fix(skill): audit-found bugs in hyperframes core examples
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.
2026-05-21 11:09:30 -07:00
ukimsanov 0ba1df54ed feat(skill): hyperframes core — remove prescriptive tables, bundle text-effects
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.
2026-05-21 11:09:30 -07:00
ukimsanov 24aef5475c fix(skill): eval feedback — font src paths, WCAG pairings, flash-through-white regression
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.
2026-05-21 11:08:52 -07:00
ukimsanov e5160b6022 chore(skill): brand-floor → brand-defaults; captured-asset-primary is normal
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.
2026-05-21 11:08:52 -07:00
ukimsanov d31ef3f15b chore(skill): audit cleanup — captured assets are first-class primaries
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.
2026-05-21 11:08:52 -07:00
ukimsanov eed6dfb2f2 refactor(skill): primitive-toolkit framing across step-1, step-3, step-5
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.
2026-05-21 11:08:52 -07:00
ukimsanov 7f6bceb473 refactor(skill): step-0 owns capture, not analysis
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
2026-05-21 11:08:52 -07:00
ukimsanov 7093ee900b refactor(skill): slim SKILL.md to step-pointer index, push to step files
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.
2026-05-21 11:08:51 -07:00
ukimsanov 051c9c9de4 chore(skill): drop visual-vocabulary.md + use published npx hyperframes
- 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.
2026-05-21 11:08:51 -07:00
ukimsanov a1ffb6e7bf feat(skill): website-to-hyperframes — concept-first authoring + per-beat read protocol
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.
2026-05-21 11:08:51 -07:00
ukimsanov 2c9544f6d4 feat(lint): font loading + invalid capture path composition rules
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.
2026-05-21 11:08:38 -07:00
ukimsanov 65c5209be8 chore(cli): bump sharp ^0.34.0 → ^0.34.5
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.
2026-05-21 10:57:37 -07:00
ukimsanov 62b55171e9 feat(capture): pipeline improvements — contact sheets, design styles, snapshot
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.
2026-05-21 10:57:37 -07:00
ukimsanov 5e7a7a8956 fix(capture): address review feedback on font extractor
Five fixes from Copilot's inline review + Miguel's note on PR #987:

1. inferWeightFromSubfamily — only matched concatenated forms
   ("extralight", "semibold"). Spaced ("Extra Light") and
   hyphenated ("Extra-Light") variants fell through to the 400
   default, misreporting 200-weight fonts as 400. Now normalizes
   `[\s-]+` out of the subfamily before matching.

2. meta.tool — was hardcoded to "fontkit@2.0.4" but
   `packages/cli/package.json` allows ^2.0.4, so the manifest
   string would drift on every dep bump. Now records just
   "fontkit"; the version moves with the dep and can be discovered
   from package.json at debug-time if needed.

3. FontFileMetadata.rawFamily — docstring said "nameID 16 preferred,
   then nameID 1" but the code also derives from PostScript via
   deriveFamilyFromPostscript when both name-table fields are
   missing. Doc now reflects the actual three-step precedence.

4. FontFileMetadata.weight — docstring said "100-900" but the code
   emits 0 (when identified: false) and 950 (when
   canonicalizeFamily picks ExtraBlack/UltraBlack). Doc now
   documents both edge values explicitly.

5. sharp ^0.34.5 — bumped from ^0.34.0 on this PR but font
   extraction doesn't use sharp; the bump is needed by the contact
   sheet code in PR #988. Reverted on #987; will re-bump on #988
   where it's actually consumed.

Also adds vitest coverage:
- 34 tests in fontMetadataExtractor.test.ts
- Covers inferWeightFromSubfamily for concatenated, spaced, and
  hyphenated forms (including composite styles like "Bold Italic"
  and case-insensitivity)
- Covers canonicalizeFamily for unchanged families, stripped
  weight tokens, preserved width modifiers, and the 950 emit
- Integration tests for extractFontMetadata (non-existent dir,
  empty dir) verifying the meta.tool / generatedAt shape

Exported `inferWeightFromSubfamily` and `canonicalizeFamily` for
testing. Pure functions, internal helpers, but exporting is the
clean way to pin their behavior against regressions.
2026-05-20 17:54:32 -07:00
ukimsanov db94b505dd feat(capture): identify hashed fonts via OpenType name table
Modern frameworks (Next.js, Webpack) hash font filenames like
`f9b8e1e8d4c3f0a7-s.woff2`, so the capture pipeline can't tell which
file belongs to which family by reading the filename. Sub-agents
authoring DESIGN.md were guessing or falling back to system fonts.

This adds `fontMetadataExtractor.ts`: reads the binary OpenType `name`
table via `fontkit`, identifies each downloaded font by its real
family name, and writes `capture/extracted/fonts-manifest.json` with
per-file metadata + per-family aggregates (weights, variable-font
axes, file counts).

- Canonicalizes static-weight family-name packaging: "Inter Medium"
  resolves to family "Inter" with weight 500, "Semi Bold" normalizes
  to "SemiBold", etc. Width modifiers ("Tight", "Condensed") are NOT
  stripped — they denote separate typographic families.
- Reads variable-font axes from `fvar` so a single .woff2 carrying a
  full weight range is identified as variable (e.g. "Inter (100-900
  variable)").
- Uses `@types/fontkit` properly (no `unknown` cast), with a
  Font/FontCollection type guard. fontkit API drift surfaces as a
  compile error rather than silent undefined.
- Wired into `capture/index.ts` after `downloadAndRewriteFonts` so it
  runs after fonts are already on disk. Non-fatal try/catch — capture
  succeeds even if extraction fails.

Tested against 9 captures: 132/132 fonts identified by real family
name, including hashed Next.js builds.
2026-05-20 13:55:07 -07:00
ukimsanovandCursor f9d22df3c9 fix(shader-transitions): real opacity crossfade for CSS transitions in engine mode
Address Copilot round-3 review: the previous engine-mode timeline used
`tl.set(toId, opacity:1, T)` + `tl.set(fromId, opacity:0, T+dur)` for
every transition. That keeps BOTH scenes at opacity:1 throughout the
transition window. The Node-side layered compositor handles this fine —
it captures each scene separately, masks opacity per layer, and runs the
blend itself — but the page-side compositing path (one opaque RGB
screenshot per frame, opt-in via EngineConfig.enablePageSideCompositing)
relies on the page to produce a correct frame. With `shader === undefined`
the page-side compositor skips the entry, so the screenshot would show
both scenes stacked at 100% opacity (visible ghosting) instead of a blend.

Fix: schedule an actual opacity-crossfade tween in `initEngineMode`
when `t.shader === undefined`. Shader transitions keep the existing
opacity-flip pattern because the Node-side compositor needs both scenes
fully visible to capture them. The crossfade is harmless in the layered
Node path because `applyDomLayerMask` overrides per-scene opacity during
each capture anyway.

Also corrects docstrings in engineModePageComposite.ts and at the
installPageSideCompositor call site that previously claimed the GSAP
timeline "handles the blend" — it now actually does.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-19 18:47:47 -07:00
ukimsanovandCursor 351c7bfcc4 fix(shader-transitions): address Copilot round-2 review
Three follow-up fixes from the Copilot review on commit 8cad2173:

1. Use strict `t.shader === undefined` instead of `!t.shader` (Copilot c4)
   in both the WebGL program compile loop and the page-side compositor.
   An empty-string `shader: ""` from a vanilla-JS caller (the IIFE bundle
   is hand-loaded via <script> tags in user HTML) should reach the shader
   registry and surface a loud "unknown shader" error, not silently
   degrade to a crossfade.

2. Graceful degradation when shader compile fails (Copilot c5). The
   previous `continue` dropped the transition from `cachedTransitions`,
   which also dropped its scene-visibility timeline entries and broke
   scene progression. Now: log a warning and downgrade to the CSS
   crossfade fallback (prog=null, fallback=true) so the opacity timeline
   still runs and the composition keeps playing.

3. Preserve index-to-scene-pair correlation when calling the page-side
   compositor (Copilot c6). The earlier filter `transitions.filter(t =>
   !!t.shader)` shifted indices, so a shader transition at original index
   2 (sitting between CSS crossfades) would be paired with scenes[1] and
   scenes[2] inside `installPageSideCompositor` instead of the correct
   scenes[2] and scenes[3]. The compositor now accepts the full array,
   makes `PageCompositeTransitionConfig.shader` optional, and skips
   CSS-only entries internally while keeping `transitions[i]` aligned
   with `scenes[i]`/`scenes[i+1]`.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-19 18:31:58 -07:00
ukimsanovandCursor 8cad2173dc fix(shader-transitions,producer): harden CSS-only transition lifecycle and unblock CI
Three follow-on fixes after the optional-shader change rebased onto current
main (PR #832 introduced page-side compositing and the producer's hf#732
layered pipeline since this PR was opened).

shader-transitions/hyper-shader.ts
- Treat `cache.prog === null` as the canonical immutable marker for
  CSS-only transitions via a new `isCssOnlyTransition()` helper.
- `disposeCachedTransition()` now restores the always-ready CSS fallback
  state for prog=null caches instead of zeroing `fallback`/`ready` — the
  previous behaviour, combined with `markScenesDirty()` re-running the
  prewarm/capture pipeline, could put a CSS-only cache through the WebGL
  path and reach `renderShader(state.prog!)` with a null prog (Copilot
  review on lines 1168 + 1319).
- `markScenesDirty()` skips CSS-only caches; they have no shader to
  recompile and no texture pyramid to recapture.
- `ensureTransitionCachesReady()` filters CSS-only caches out of the
  prewarm work list.
- `tickShader()` now routes on `cache.fallback || cache.prog === null`
  and threads a narrowed non-null `prog` local into `renderShader()`,
  removing the unsound `state.prog!` non-null assertion.
- `initEngineMode()` filters CSS-only transitions before passing them to
  `installPageSideCompositor()`, which expects `shader: ShaderName`
  (required). Page-side compositing is shader-only; CSS crossfades stay
  on the GSAP opacity timeline.

producer/render/stages/captureHdrHybridLoop.ts
producer/render/stages/captureHdrSequentialLoop.ts
- Guard `activeTransition.shader` against undefined: when omitted, route
  the Node-side blend through `crossfade` (the engine's canonical
  opacity blend, equivalent to `applyFallbackTransition()` on the page).
- The hybrid path also bypasses the worker pool when `shaderName` is
  absent and runs `crossfade` inline.

This addresses the Copilot review comments and unblocks the 5 failing CI
jobs (Build, Typecheck, CLI smoke, Windows tests, Windows render) which
all rooted in 4 TS errors at these exact sites.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-19 18:17:16 -07:00
ukimsanov a78e49c181 feat(shader-transitions): make shader optional to support CSS crossfade mixing
Allow omitting the shader field in TransitionConfig to get a smooth CSS
opacity crossfade instead of a WebGL effect. HyperShader manages all scene
visibility regardless of transition type, so shader and CSS crossfade
transitions can now be mixed freely in the same composition.

When shader is omitted:
- No WebGL program is compiled or cached for that transition
- The existing applyFallbackTransition() path handles the crossfade
- No texture prewarming needed — transition is marked ready immediately

Tested: verified with a 3-scene composition (sdf-iris + CSS crossfade)
rendered to MP4. Both transition types render correctly.

engine/src/types.ts: HfTransitionMeta.shader is now optional to match
2026-05-19 16:11:24 -07:00
ukimsanovandClaude Opus 4.7 ce4bcc47c0 fix(cli): use multi-strategy runtime resolver for snapshot
Use loadRuntimeSource() from runtimeSource.ts instead of a single
hardcoded path. Tries: build from source (dev), inlined constant
(production), pre-built artifact (fallback). Fixes snapshot in dev
mode where __dirname is src/commands/ not dist/.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 14:36:34 -04:00
ukimsanov 8cc2084669 fix(cli): snapshot resolves runtime from own dist, not monorepo layout
The snapshot command resolved the HyperFrames runtime IIFE via a
relative path that walked up three directories to packages/core/dist/.
This only worked in the monorepo dev layout — npm/npx installs have
a flat dist/ folder with cli.js and the runtime side by side.

Without the runtime, window.__player was never created and the
snapshot fell back to seeking every __timelines entry to the same
absolute time. Sub-composition timelines expect relative time
(offset from their data-start), so all beats rendered beat-1 content.

Fix: resolve("hyperframe.runtime.iife.js") from __dirname (the dist/
folder itself), where the build already copies the runtime IIFE.
2026-04-26 00:53:06 -04:00
ukimsanovandClaude Opus 4.6 67ed767641 fix(docs): use claude.ai/design link, remove raw download option
- Link to claude.ai/design instead of claude.ai
- Remove raw.githubusercontent download links (just GitHub with ↓ button)
- Fix stale SKILL.md link text in prompting guide

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-25 17:02:58 -04:00
ukimsanovandClaude Opus 4.6 a8f0752bf6 fix(shader-transitions): address review — NaN guard, canvas sync, shared defaults
- Validate data-width/data-height: fall back to defaults if NaN or <= 0
- Sync existing #gl-canvas dimensions on reuse (if init called twice)
- Import DEFAULT_WIDTH/DEFAULT_HEIGHT in capture.ts instead of
  hardcoding 1920/1080 in parameter defaults

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-23 21:41:42 -04:00
ukimsanovandClaude Opus 4.6 c81ef50e2f feat(shader-transitions): support any aspect ratio + update skill
Add dynamic resolution to shader transitions and update Skeleton A
to use shaders on vertical compositions.

shader-transitions changes:
- webgl.ts: read dimensions from params instead of hardcoded constants
- capture.ts: accept width/height for html2canvas
- hyper-shader.ts: read data-width/data-height from composition root

skill + docs changes:
- Skeleton A now has 1 shader at hero reveal (s3→s4 midpoint)
- Removed "no shaders on vertical" limitation from docs
- Updated claude-design.mdx known limitations section

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-23 21:32:09 -04:00
ukimsanovandClaude Opus 4.6 6b84971f6b feat(shader-transitions): support any aspect ratio (vertical, square)
Read data-width/data-height from the composition root instead of
hardcoding 1920x1080. Enables shader transitions on vertical
(1080x1920) and square (1080x1080) compositions.

Changes across 3 files:
- webgl.ts: WIDTH/HEIGHT constants → DEFAULT_WIDTH/DEFAULT_HEIGHT,
  createContext and renderShader accept width/height params
- capture.ts: captureScene and captureIncomingScene accept
  width/height params for html2canvas
- hyper-shader.ts: reads data-width/data-height from root element,
  passes dimensions to all webgl and capture calls

GLSL shaders unchanged — they already use u_resolution uniform for
all coordinate math and work at any aspect ratio.

Backwards compatible: all params default to 1920x1080 when not
provided or when data-width/data-height are missing from the DOM.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-23 21:19:49 -04:00
ukimsanovandClaude Opus 4.6 c6da00d9c8 docs: fix SKILL.md download UX across all references
Raw GitHub URLs serve as text/plain — clicking opens a text tab
instead of downloading. Updated all 5 references across 4 files:

- claude-design.mdx (2 refs): "right-click → Save Link As"
- prompting.mdx (1 ref): "right-click → Save Link As"
- README.md (1 ref): "click download button on GitHub"
- quickstart.mdx (1 ref): "click download button on GitHub"

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-23 21:06:27 -04:00
ukimsanovandClaude Opus 4.6 95ca0200d4 docs: update all Claude Design references for template-first skill
Update docs, quickstart, prompting guide, and README to reflect:
- Template-first approach (attach file, not paste URL)
- Claude Design produces drafts, refine in any AI coding agent
- Known limitations (vertical shaders, seeking, no linting)
- Practical example prompts (feature announcement, founder pitch)
- Removed outdated references (invisible bridges, fetch-the-skills-tree)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-23 20:52:40 -04:00
ukimsanovandClaude Opus 4.6 18136d4eb5 fix(shader-transitions): add inWindow guard to .catch fallback handler
Address Vance's review on #456: the .catch handler was missing the
same tl.time() window check that .then has. Late-rejecting captures
(Safari + SVG-filter compositions) could fire gsap.to/fromTo on
scenes the playhead already left, causing flash-to-black mid-scene.

Wraps the CSS crossfade fallback in the same inWindow guard so
stale catch handlers are no-ops, matching the .then behavior.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-23 20:17:13 -04:00
ukimsanov ee7c6c3c24 refactor(claude-design-skill): template-first rewrite with bug fixes
Replace the rule-heavy Claude Design skill with a template-first
approach. Instead of teaching all rules from scratch (863 lines),
provide pre-valid skeletons where structural rules are embedded.
Claude Design fills in palette, content, and animations.

Key changes:
- Pre-valid HTML skeletons for social reel (vertical), launch teaser,
  product explainer, and cinematic title
- Mixed transitions: 2-3 shader transitions at key moments, rest
  hard cuts (matches professional video practice)
- autoAlpha toggles for non-anchor scenes (fixes invisible middle
  scenes caused by HyperShader's blanket opacity reset)
- Explicit first-anchor opacity fix (HyperShader browser mode never
  auto-shows the first anchor scene)
- No shaders on vertical (1080x1920) — WebGL canvas is hardcoded
  to 1920x1080 in webgl.ts
- Inline animation patterns (counter, stroke draw, stagger, float,
  bar chart, orbit, highlight sweep, safe CSS grain)
- Claude Design → Claude Code handoff workflow documented in
  README template and delivery step
- Troubleshooting table for black preview, invisible scenes, seeking
- Self-review checklist split: structural validity, brand accuracy,
  animation baseline
- Skill description updated per review feedback on PR #353 to
  disambiguate from the hyperframes skill on cross-surface routing

Bugs fixed by this rewrite (confirmed across demov4-1 through
demov4-6 and demov5-1 through demov5-5):
- First shader anchor invisible in every composition
- Non-anchor scenes killed by blanket querySelectorAll(".scene")
  opacity reset
- visibility toggles insufficient (opacity:0 persists after reset)
- Shaders on vertical compositions produce distorted transitions
2026-04-23 20:12:20 -04:00
ukimsanov 41b4017273 docs(shader-transitions): clarify allowTaint caveat in capture comment
Address Copilot review comment on #456: the old `allowTaint` doc comment
said the resulting canvas is "still usable as a WebGL texture via
gl.texImage2D (no pixel read-back required)", which is wrong. A tainted
canvas CANNOT be uploaded to WebGL — the spec requires SecurityError on
non-origin-clean sources with no opt-out. That's exactly what we observe
in Safari + SVG-filter compositions, and what hyper-shader.ts's catch
handler now handles via CSS crossfade.

Update the comment to correctly describe the flag's effect: it only
moves the failure point from html2canvas to texImage2D; the end-user
UX is the same (smooth CSS fade in either case). The flag remains
defensively correct for the non-taint branches where it genuinely
helps (cross-origin images with `Access-Control-Allow-Origin`).

No code change — comment only.

Made-with: Cursor
2026-04-23 16:20:32 -04:00
ukimsanov b3b458fda4 fix(shader-transitions): harden capture against visibility, scrub, Safari taint
Three interrelated fixes for the live-player path in
@hyperframes/shader-transitions (Studio preview, <hyperframes-player>
embeds, Claude Design in-pane iframe). Zero changes to engine mode —
initEngineMode is byte-identical; producer render pipeline and CLI
hyperframes render produce byte-identical output.

1. captureIncomingScene now forces visibility:visible during capture.
   The HF runtime sets visibility:hidden on [data-start] elements outside
   their playback window. With centered shader timing (transition.time =
   boundary - duration/2), html2canvas captures the incoming scene while
   it's still hidden → blank texture → visible blink mid-transition.
   Fix saves, overrides, captures, and restores visibility only for the
   capture window. Empirically validated via a direct html2canvas probe:
   captures of visibility:hidden elements return blank; with override,
   they return real content.

2. post-capture.dom guards on tl.time() window before mutating DOM.
   On scrub across multiple shader transitions, tl.call() fires several
   transitions' callbacks in rapid succession; each launches async
   html2canvas; each .then() unconditionally set all .scene opacities to
   0, enabled shader canvas, and pointed state at that transition. The
   last to resolve won — often for a transition the playhead had left.
   Result: scenes stuck opacity:0 mid-scene; blank screen until the next
   transition's end.call ran. Fix: check tl.time() is still inside
   [T, T+dur] before applying state; otherwise skip.

3. .catch fallback does CSS crossfade instead of hard cut.
   When capture fails (Safari canvas taint from SVG data URLs, CORS
   errors, extreme DOM complexity) the old catch snapped all scenes to
   opacity:0 then set incoming to opacity:1 — jarring instant jump. Fix
   uses gsap.to/fromTo on opacity over the intended transition duration;
   smooth 0.5s fade is strictly better UX. Hard cut preserved as
   last-resort if elements are missing.

Also adds defensive useCORS: true and allowTaint: true to the
html2canvas call. No behavior change in Chrome (capture normally
succeeds); adds resilience for cross-origin images with CORS headers
and SVG-tainted canvases respectively.

Known limitations (out of scope, follow-up tracked):

- Safari + cross-origin iframe: html2canvas is 10-12x slower than Chrome
  due to WebKit's DocumentCloner.cloneNode perf (html2canvas#3108),
  causing perceptible per-transition freezes (1.5-2s each) in Claude
  Design's in-pane preview. Needs pre-capture architecture (cache
  incoming-scene textures at init) to eliminate per-transition cost.
- SVG filter data URLs fundamentally taint html2canvas output in Safari;
  WebGL's texImage2D has no framework opt-out (WebGL spec). Addressed
  at the composition level via the Claude Design skill's anti-pattern 4
  in a parallel PR.

Made-with: Cursor
2026-04-23 15:15:58 -04:00
ukimsanov 0730f88dc7 fix(cli): address snapshot PR review feedback
Incorporates four review points on #348:

1. Fix typecheck error — cast the dynamic `@hyperframes/engine` import to a
   typed shape and drop the `as typeof x` cast on a `null`-unioned variable
   that TS rejected. CI `Typecheck` was failing on main because the cast
   conflicted with the declared union.

2. Clear stale injected <img> overlays — always call
   `syncVideoFrameVisibility(page, activeIds)` on every seek (including
   `active.length === 0`), so injected frames from a previous timestamp
   don't leak into later snapshots. The runtime's visibility toggles act
   on the native <video> but not on its injected sibling, which Copilot
   correctly flagged as a leakage source.

3. URL-decode the served video path before resolving to a filesystem path.
   Files whose names contain spaces or other URL-encoded characters were
   skipped because `new URL().pathname` preserves `%20` while the file
   server decodes inbound requests and the file on disk lives at the
   decoded name.

4. Mirror `packages/core/src/runtime/media.ts` media math so clips with a
   non-1 `defaultPlaybackRate` get the correct active window and relTime.
   Specifically: clamp `defaultPlaybackRate` to [0.1, 5], apply
   `(t - start) * rate + mediaStart`, and adjust the duration-fallback
   branch by `/ rate` when only source duration is known.

5. Kill FFmpeg on a 30s timeout so a pathological clip cannot wedge
   snapshot indefinitely. Matches the default in
   `@hyperframes/engine`'s `runFfmpeg`.

Re-verified against the same 4-timestamp A/B on launch-video-2:
MD5s match the ffmpeg-from-render ground truth
  12.5s → ef9684e36fea53a0db7adf7cfcaacad3  (Stripe)
  16.0s → 487494ca16344d55d7181408dc439a56  (Framer)
  20.5s → 8835c34ad2a45755a1c98a7e079427a1  (HeyGen 3D)
  32.5s → 34dc9450f2bd661c12039d7aa82a30b0  (GitHub finale)

No-video projects (basecamp-tour, linear-brand, commissioned/github)
still produce identical output to the pre-fix path. Latency unchanged.

Made-with: Cursor
2026-04-20 10:30:29 -04:00
ukimsanov a3e7c6176f fix(cli): inject real video frames in snapshot to match render
The snapshot command previously just called `tl.seek(t)` + `page.screenshot`
and trusted Chrome to advance `<video>`-element decoders. Chrome headless
silently ignores `video.currentTime = X` writes — the setter is accepted
but the decoder never moves. Result: every snapshot of a composition that
uses body-level `<video data-start>` elements renders the same frame
regardless of the requested timestamp (the z-topmost video's first-frame
paints through, because all clips share `position: absolute; inset: 0`
and visibility:hidden doesn't always prevent the GPU surface from
contributing to the composite).

The render pipeline has already solved this: for each body-level video it
extracts the needed frame via FFmpeg and overlays it as an <img> sibling
via `injectVideoFramesBatch` (packages/engine/src/services/screenshot
Service.ts). This commit ports that same primitive into `snapshot`:

1. Added `extractVideoFrameToBuffer(videoPath, t)` — one FFmpeg spawn per
   active video, `-ss` keyframe seek (~100-200 ms), writes a temp PNG.
2. After the existing seek + settle, enumerate `<video data-start>`
   elements that are active at the target time, resolve each one's
   `currentSrc` URL back to a filesystem path under `projectDir`, extract
   the frame, and call `injectVideoFramesBatch`.
3. Then screenshot — as before.

Non-breaking: when no body-level `<video data-start>` elements exist (every
other project in the repo — basecamp, linear, stripe, github component), the
new block short-circuits on `active.length === 0` and behaves identically
to the pre-fix path. Verified against three no-video projects: bit-identical
snapshot output, no latency regression.

Measured on macOS M2 (4 frames, cold):
  launch-video-2 (11 timed videos): 6.48s → 6.16s   (-5%)
  basecamp-tour  (no timed videos): 5.67s → 4.87s   (-14%)

Proof: Pre-fix MD5 at t=12.5, 16.0, 20.5, 32.5 — all 4 identical (wrong frame)
  Post-fix MD5 at same timestamps  — all 4 distinct, match ffmpeg-from-render
Made-with: Cursor
2026-04-20 09:36:33 -04:00
ukimsanovandClaude Opus 4.6 4e87f28cd9 fix: address PR #339 review — 7 issues
1. Catalog failure safety: warn when catalog is empty or throws, so
   capture doesn't silently produce zero images
2. Dead path: extract-audio-data.py → skills/gsap/scripts/ (was
   skills/hyperframes/scripts/)
3. --json fonts compat: emit both `fonts` (string[]) and
   `fontsDetailed` (FontToken[]) to avoid breaking external consumers
4. Restore .cursorrules writing alongside AGENTS.md + CLAUDE.md
5. .gitignore: remove over-broad `projects/` and `videos/` entries,
   keep scoped `cursor-tests/` and `launch-video*/`
6. agentPromptGenerator: mark unused params as reserved with comments,
   remove _animations from buildPrompt
7. Cookie filter: threshold 20 → 8 chars to preserve footer copy like
   "© 2026 Stripe" (16 chars) and "Privacy & Terms" (15 chars)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-19 15:34:15 -04:00
ukimsanov 517e327294 docs(skill): render step is on-demand, not automatic
Preview is the delivery — scrub through it, iterate on tweaks, only render
once the user explicitly asks ("render it", "make the final", "I'm happy").
Rendering takes minutes per pass and is wasted work if the user wants any
changes after seeing it.

- step-7-validate: rename Render section to "Render (on-demand only)" and
  make the trigger criteria explicit
- SKILL.md: step-7 summary updated to "Deliver the preview to the user
  first — only render to MP4 on explicit request"

Made-with: Cursor
2026-04-19 08:43:59 -04:00
ukimsanov 1a69cde4be docs(skill): shader/audio/render/snapshot guidance from regression evidence
Adds decision guidance and conventions the 8-site regression test exposed as
blind spots — agents had the capability but never reached for it.

step-1-capture:
- Clarify capture goes into <project-dir>/capture/ so capture artifacts stay
  isolated from later build files (SCRIPT/STORYBOARD/DESIGN/compositions/)
- 7/8 regression tests already did this; codify as the convention

step-4-storyboard:
- Add "When to pick which" decision table for shader vs CSS vs hard cut
  transitions. Shader transitions were available but used in 0/8 tests —
  every test defaulted to CSS. The table frames shaders as "reveals, reaction
  shots, brand moments" vs CSS as "connective tissue"
- Update technique count (10 → 11)

step-6-build:
- Mid-scene activity table gets a new row for audio-reactive logo/CTA
  animation (bass pulse, treble glow). Audio-reactive was used in 0/8 tests
  despite narration being present in all of them

step-7-validate:
- Snapshot section: explicit "use hyperframes snapshot, don't roll custom"
  with the default naming pattern spelled out. Stripe's run used custom
  ffmpeg naming (beat-6-cta-at-20.5s.png) instead of frame-XX-at-Ys.png
- New render section: require --output renders/<project>.mp4 so final MP4s
  have predictable names. Without this, 7/8 tests produced wildly different
  filenames (preview.mp4, cal_2026-04-19_20-29-21.mp4, basecamp.mp4, etc.)

techniques.md:
- New technique #11: Audio-Reactive Animation. Covers the sampling pattern
  (per-frame tl.call, not single tween), when to use (music/dramatic VO
  videos), intensity ranges (3-5% for text/logos, 10-30% for backgrounds),
  and anti-patterns (equalizer bars, waveforms, strobing). Cross-references
  skills/hyperframes/references/audio-reactive.md for the full API

Made-with: Cursor
2026-04-19 08:41:53 -04:00
ukimsanov d8f1af1ef1 feat(capture): write AGENTS.md alongside CLAUDE.md + skill refinements from regression tests
Capture pipeline:
- agentPromptGenerator now writes AGENTS.md + CLAUDE.md (drop legacy
  .cursorrules), matching the dual-file convention already used by the
  _shared templates in hyperframes init. AGENTS.md is picked up natively by
  Cursor, Codex, Gemini CLI, Windsurf, Aider, and Jules; CLAUDE.md covers
  Claude Code. Both files share the same content — a capture data inventory
  that points agents to the website-to-hyperframes skill.

website-to-hyperframes skill refinements (derived from 8-site regression test):
- Drop slash-command phrasing throughout SKILL.md and step-6-build.md so the
  skill works identically across Claude Code (slash), Cursor (auto-discover
  by description), and other agents.
- Remove stale HANDOFF.md references from SKILL.md step-7 summary and
  reference table — matches the intent of the prior step-7 cleanup.
- step-5-vo: specify narration.txt filename convention (pronunciation-
  substituted spoken text; distinct from SCRIPT.md the creative doc).
- step-6 self-review adds three rules derived from actual lint warnings
  observed across the 8 regression runs:
    - Every <template> root needs data-start + data-duration (catches
      root_composition_missing_data_start/duration, seen in 4/8 runs).
    - Caption exits need a hard tl.set kill after tl.to(opacity:0), or
      per-word karaoke tweens can leave captions stuck on screen
      (caption_exit_missing_hard_kill).
    - No duplicate media nodes with identical src + start + duration, or
      the compiler discovers them twice (duplicate_media_discovery_risk).

Housekeeping:
- .gitignore: add cursor-tests/, basecamp-video/, projects/, videos/ —
  local regression-test scratch dirs that should never be committed.
- Remove two broken symlinks from .claude/skills/ that pointed to paths
  which never existed in the repo (.claude/skills/ is already gitignored).

Made-with: Cursor
2026-04-18 23:17:29 -04:00
ukimsanov 92a5ef419b feat(capture): improve capture quality + clean up CLAUDE.md
Capture improvements:
- Font weights via document.fonts API + DOM sampling (variable font detection)
- Section background-image extraction (no more false #FFFFFF on hero photos)
- Detected libraries surfaced in CLAUDE.md brand summary
- Structured visible-text.txt with [tag] prefixes, cookie/nav noise filtered
- tokens.json cleaned: removed images/paragraphs/icons (duplicated elsewhere),
  filtered sections to heading-only, trimmed cssVariables to design-relevant
- Removed redundant scroll pass in htmlExtractor (2-5s faster per capture)
- Font cap at 20 families, Placeholder/Fallback fonts filtered

CLAUDE.md rewrite:
- Removed prescriptive tone ("use exact strings" → "rephrase freely")
- Removed fluff sections (How to Create, DESIGN.md warning, Example Prompts,
  Source Patterns)
- asset-descriptions.md promoted to first data row
- Removed assets-catalog.json from inventory

Skill fixes:
- Dead shader refs → point to packages/shader-transitions/README.md
- Google Fonts import in techniques.md → local @font-face placeholder
- Added Stripe DESIGN.md as light-brand example
- Removed HANDOFF.md generation from step-7
- Updated step-1 for new font weight + visible-text formats
2026-04-18 18:48:37 -04:00
ukimsanov 5cb3726f27 Merge remote-tracking branch 'origin/main' into feat/capture-improvements-v2 2026-04-18 17:16:42 -04:00
ukimsanov 65011f3b57 Merge remote-tracking branch 'origin/main' into feat/capture-improvements-v2 2026-04-17 13:39:44 -04:00
ukimsanovandClaude Opus 4.6 274db7a5ef fix: address PR #299 review — lint correctness, docs, Gemini benchmark
- lintMultipleRootCompositions: scan filesystem for HTML files with
  data-composition-id (was filtering results array — always 1 entry)
- lintDuplicateAudioTracks: order-independent attribute extraction,
  dedup by (src,start,duration,trackIndex), Infinity fallback for
  missing data-duration (matches runtime behavior)
- 10 new tests for both lint rules
- docs: explicit skill invocation, remove gsap-skills, fix indentation
- Gemini: env override (HYPERFRAMES_GEMINI_MODEL), benchmark data in
  code comment (49 imgs: 3.1-lite ~507ms/img, 2.5-lite ~230ms/img)
- cli.mdx: version-agnostic "Gemini vision" reference

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 10:15:26 -04:00
ularkim de5b53c08b feat(capture): switch Gemini 2.5 Flash → 3.1 Flash Lite
Gemini 3.1 Flash Lite Preview: 2.5x faster TTFT, 45% faster output,
slightly cheaper ($0.25/M vs $0.30/M input), near-2.5-Flash quality.
Descriptions are actually more detailed in testing.
2026-04-16 22:58:50 -04:00
ularkim a77a6cbbf7 fix: double-audio bug + lint rules + docs guide + capture improvements
Double-audio bug fix:
- scaffolding.ts: stop writing index.html in captures/ (root cause —
  runtime discovered scaffold + real index.html as two compositions)
- New lint rule: multiple_root_compositions — errors if >1 root HTML
- New lint rule: duplicate_audio_track — warns on overlapping audio

Capture improvements (from testing 30+ websites):
- Catalog runs BEFORE extractHtml (which mutates DOM — converts img src
  to data URLs). HeyKuba: 2 images → 78.
- networkidle2 instead of networkidle0 (unblocks SPAs with WebSockets)
- Lazy-load image wait, CSS background-image cataloging
- SVG naming from class/id/parent (not just aria-label)
- Gemini batch 5→20, pause 12s→2s, maxOutputTokens 300→500
- Asset descriptions sorted: captioned first

Docs:
- New guide: guides/website-to-video.mdx (full tutorial)
- CLI docs: added capture and snapshot commands
- docs.json: website-to-video in Guides nav

C
2026-04-16 22:58:50 -04:00
Ular KimsanovandClaude Opus 4.6 87f4c77e2f feat: website capture pipeline + 7-step video production skill (#284)
* feat(cli): add website capture with AI-powered DESIGN.md generation

Adds `hyperframes capture <url>` command that extracts a complete design
system from any website, producing AI-agent-ready output:

- Full-page screenshot (lazy-load aware, nav at top)
- AI-generated DESIGN.md via Claude API (colors, typography, elevation,
  components, do's/don'ts) with programmatic asset catalog (136+ assets
  with HTML context annotations like img[src], css url(), link[rel=preload])
- CSS-purged compositions (87% size reduction via PurgeCSS)
- HTML-prettified compositions (one-tag-per-line for AI readability)
- CLAUDE.md + .cursorrules auto-generated for AI agent instructions
- Asset deduplication (srcset variants) and tracking pixel filtering

* feat(cli): add gemini 3.1 pro, playwright screenshots, replica refinement

- switch to gemini 3.1 pro (gemini-3.1-pro-preview) with claude fallback
- playwright for full-page screenshots (fixes puppeteer gradient/fixed bugs)
- replica refinement loop: generate, screenshot, compare, fix
- extract inline svgs (50 max, 10kb each) to assets/svgs/
- extract visible text in dom order for content accuracy
- detect js libraries (gsap, three.js, scrolltrigger) via globals
- improved asset catalog grouping and naming
- reverse-engineered aura system prompt documentation
- comprehensive session handoff doc

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

* docs: update session handoff with slack research findings

- key finding: team already wants DESIGN.md integration (James, Bin, Vance)
- skills quality matters enormously - must invoke /hyperframes-compose
- eval infrastructure exists (Abhay's dashboards, Teodora's 78-criteria guide)
- templates at templates/ need study before finalizing skill
- session handoff updated with critical next steps

* refactor(cli): simplify capture pipeline, remove replica generator

* feat(capture): add Lottie detection and WebGL shader extraction

Captures Lottie animations via network interception and WebGL shader
source via gl.shaderSource hooking during site crawl. Updates
website-to-hyperframes skill with asset planning guidance, Lottie/shader
reading instructions, and stronger creative direction for scene planning.

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

* refactor(capture): clean pipeline + shader-first creative workflow

Capture pipeline:
- Remove dead deps (puppeteer-extra, stealth plugin, duplicate devDeps)
- Remove duplicate generateAgentPrompt() call (first lied about DESIGN.md)
- Remove dead canvas-to-image code in htmlExtractor (post canvas removal)
- Parallelize image downloads (batches of 5 via Promise.allSettled)
- Fix pre-existing TS error (match[1] guard in font downloader)
- Default capture output to captures/<hostname>

Skill creative overhaul:
- Add shader transition selection to creative director step (Step 4)
- Add shader wiring instructions to engineer step (Step 5)
- Replace 4-line energy modifiers with visual vocabulary table
- Strip rigid scene-by-scene templates from video-recipes.md
- Strip example fill data from scene plan tables
- Add "read transition refs before planning" instruction
- Add creative ambition language ("how the hell did they make this")

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

* docs: add skill architecture redesign spec

Comprehensive redesign of website-to-hyperframes skill and capture
pipeline based on code review findings and Claude Code architecture
research. Key changes: remove AI auto-generation, restructure skill
into phases, embed shader boilerplate in scaffold, fix color format.

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

* docs: add implementation plan for skill architecture redesign

13-task plan covering: capture pipeline cleanup (remove AI generation,
fix colors to HEX, add asset descriptions, shader-ready scaffold),
skill restructuring (4 phases with artifact gates), and compose skill
Visual Identity Gate upgrade.

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

* refactor(capture): remove AI auto-generation and SDK dependencies

* fix(capture): convert extracted colors to HEX format

* refactor(capture): remove AI key path, add asset descriptions generator

* refactor(capture): update agent prompt, remove hasDesignMd, add asset descriptions

* feat(capture): pre-wire shader transitions in index.html scaffold

* chore: remove duplicate visual-styles.md (canonical is in hyperframes/)

* refactor(skill): rewrite website-to-hyperframes as phase-based orchestrator

* feat(skill): add Phase 1 understand reference

* feat(skill): add Phase 2 design reference with full DESIGN.md schema

* feat(skill): add Phase 3 creative direction reference

* feat(skill): add Phase 4 build reference with inline shader example

* feat(skill): upgrade Visual Identity Gate to produce full DESIGN.md

* docs: update CLAUDE.md skill references for phase-based workflow

* fix: address code review findings

- Remove orphaned `false` argument in generateAgentPrompt call (critical:
  was shifting hasLottie, hasShaders, catalogedAssets parameters)
- Add HSL color handling in rgbToHex via temp element resolution
- Remove build artifact commit section from phase-4-build.md
- Fix __GSAP_TIMELINE reference to __timelines

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

* fix(capture): regex double-escape + simplify scaffold + fix asset descriptions

- Double-escape regex in tokenExtractor template literal (\s→\\s, \d→\\d, \(→\\()
  so browser receives valid regex patterns via page.evaluate()
- Simplify index.html scaffold: scene slots + audio + timeline + comment pointing
  to shader-setup.md reference (no broken inline shader boilerplate)
- Fix asset descriptions: use CatalogedAsset.contexts/notes instead of
  nonexistent htmlContext field

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

* fix: code review — 16 bugs, 7-step skill rewrite, cleanup

Code fixes:
- snapshot.ts: path traversal guard, browser leak (try/finally), div-by-zero
  for --frames 1, port bind error handling, rAF-based render settle
- index.ts: remove invalid thinkingConfig for gemini-2.5-flash, fix Gemini
  batch/rate-limit comments, fix video preview viewport y-coordinate
- tokenExtractor.ts: remove dead seen[si] dedup code
- gsap.ts: index ALL classes for inline-style transform conflict detection

Skill architecture rewrite (4-phase → 7-step):
- Replace phase-1 through phase-4 with step-1 through step-7
- Add techniques.md (10 visual techniques with code patterns)
- Fix /hyperframes-compose → /hyperframes (skill doesn't exist)
- Fix captures/arc-browser reference → shader-setup.md (file doesn't exist)
- Fix step-7 hardcoded captures/stripe path
- Document Gemini API free/paid rate limits in step-1

Cleanup:
- CLAUDE.md: restore from Stripe-capture overwrite, update 4-phase → 7-step
- .gitignore: add PR #267 skills (hyperframes-animation-map, hyperframes-contrast)
- Delete old phase-*.md, animation-recreation.md, tts-integration.md

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

* chore: remove dev artifacts, research docs, wrong lockfiles

Remove files that shouldn't ship in this PR:
- docs/research/ (aura analysis, prompt catalogs)
- docs/session-*.md, docs/SESSION-HANDOFF.md (dev notes)
- docs/superpowers/ planning and spec docs
- pnpm-lock.yaml at root and cli (repo uses bun, not pnpm)

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

* fix(CLAUDE.md): align with main — slim format, add website-to-hyperframes mention

Main PR #283 removed the full skills table from CLAUDE.md and moved it
to AGENTS.md. Align with that decision: use main's slim dev-focused
format, fix pnpm→bun references, add one-line /website-to-hyperframes
pointer.

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

* fix(cli): add capture command to help groups

The capture command was registered in cli.ts but missing from
the help groups, so it wouldn't appear in `hyperframes --help`.

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

* style: format skill reference files (oxfmt)

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

* fix: regenerate bun.lock after rebase

The lockfile was stale after rebasing onto main — bun install
--frozen-lockfile failed in CI because new dependencies (google/genai,
patchright, purgecss) weren't reflected in the lockfile.

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

* fix: address PR review comments + improve capture quality

Review fixes (16 comments from jrusso1020 + vanceingalls):
- screenshotCapture: remove Playwright dep, use Puppeteer for all screenshots
- screenshotCapture: dynamic screenshot count based on page height (30% overlap)
- snapshot.ts: fix duration() function-vs-property bug, cross-platform path guard
- htmlExtractor: fix code injection via parameterized evaluate
- index.ts: video preview re-measures position after scroll, .env file loading
- capture.ts: BLOCKED.md on timeout failures
- gsap.ts: 5 inline-style lint tests added (all pass)
- Remove Playwright, patchright deps; @google/genai to optionalDependencies
- Gitignore: generic patterns instead of 20 hardcoded directories
- Remove asset-sourcing.md, video-recipes.md (unused, duplicated guidance)

Capture quality improvements (tested on 10+ websites):
- Color extraction: canvas-based oklch/lab resolver, pixel sampling via
  elementFromPoint, broad sweep for accent colors, gradient/shadow extraction
- Section detection: broadened selectors for div-based layouts, height cap
  to skip page-level wrappers, parent bg walkup for dark sites
- Font downloads: cap 6 per family / 30 total (Cal.com: 306→30)
- CTA detection: text pattern matching + nav context filtering
- Heading text: innerText with whitespace normalization
- Gemini captioning: maxOutputTokens 100→300, .env auto-loading
- .env.example updated with GEMINI_API_KEY docs
- TTS ranking: Kokoro first with Python 3.10+ note

* fix: address PR review comments + improve capture quality

Review round 2 fixes (jrusso1020 + vanceingalls):
- verify/index.ts: add path traversal guard (relative + isAbsolute)
- verify/index.ts: fix sections[i] undefined typecheck error (CI green)
- index.ts: escape Lottie JSON with \u003c to prevent </script> breakout
- step-4-storyboard: fix technique count contradiction (2-3 per beat, not
  across whole video)
- step-6-build: perspective tilt uses gsap.set() instead of CSS transform
  (avoids GSAP overwrite conflict)
- step-1-capture: reorder — command first, Gemini note after (zero-config
  is the default path, API key is optional enhancement)
- step-7-validate: add tsx fallback for snapshot command
- step-3-script: vary hook patterns, don't default to number every time
- assetDownloader: exempt SVGs from 10KB minimum filter (company logos
  like Hubspot/Intel/DHL are 2-6KB; HeyGen capture: 13→75 assets)

Note: adm-zip was NOT removed (reviewer #3) — it's still in
packages/cli/package.json:30. The root package.json had patchright
and purgecss removed, not adm-zip.

Note: ANTHROPIC_API_KEY not restored in .env.example — grep confirms
zero references in the entire codebase. The @anthropic-ai/sdk dependency
was removed earlier in this branch.

* refactor(capture): split index.ts (1175 to 566 lines) into modules

Mechanical extraction, zero logic changes.

New files:
- mediaCapture.ts (345 lines): Lottie preview, video manifest/screenshots
- contentExtractor.ts (314 lines): library detection, text, Gemini, asset descriptions
- scaffolding.ts (135 lines): .env loading, project scaffold generation

Also fixes false-positive BLOCKED.md with structural Cloudflare detection.
Tested on 20 websites, pre/post output identical.

* chore(capture): remove --split flow (splitter, verify, cssPurger, purgecss)

The --split feature auto-generates compositions from captured HTML — a
different approach from the /website-to-hyperframes skill workflow where
agents build compositions from scratch using the storyboard.

No skill file, no step reference, and no test session ever used --split.
Removes 923 lines of unused code + purgecss dependency.

Backed up to ~/Desktop/capture-split-backup/ for reference.

* fix(security): add ssrf protection, lottie injection fix, oom guard

- assetDownloader: add isPrivateUrl() guard blocking private IP ranges
  (127.x, 10.x, 172.16-31.x, 192.168.x, 169.254.x), cloud metadata
  endpoints, localhost, and non-HTTP schemes
- mediaCapture: fix Lottie JSON injection by loading shell HTML first
  then passing animation data via parameterized page.evaluate()
- index.ts: check Content-Length header before response.buffer() in
  Lottie network interception to avoid OOM on multi-GB responses

* fix(capture): security fixes, timeout, sub-agent dispatch instructions

Security (from miguel-heygen review):
- assetDownloader: export isPrivateUrl() SSRF guard
- htmlExtractor: add isPrivateUrl check before CSS fetch
- mediaCapture: add isPrivateUrl check before Lottie fetch
- mediaCapture: fix previewPage leak (try/finally)
- mediaCapture: skip Lottie files > 2MB for preview (CDP limit)
- contentExtractor: skip images > 4MB for Gemini captioning
- index.ts: check Content-Length before response.buffer() (OOM guard)
- snapshot.ts: register error handler before server.listen()

Capture improvements:
- Default timeout 30s to 120s (Shopify needs ~90s for Cloudflare)
- step-6-build: sub-agent dispatch template with explicit rules:
  pass file PATHS not contents, use local fonts not Google Fonts,
  verify ../assets/ references after each beat

* fix(capture): catalog before DOM mutation, networkidle2, faster Gemini

Critical: asset cataloger now runs BEFORE extractHtml which converts img
src to data URLs. Framer sites like heykuba.com went from 2 to 78 images.

- networkidle2 instead of networkidle0 (unblocks SPAs with WebSockets)
- Lazy-load wait: scroll to bottom, wait for img.complete
- CSS background-image cataloging for Framer/Webflow
- SVG naming: checks class, id, parent, inner text (not just aria-label)
- Gemini batch 5->20, pause 12s->2s (paid tier: 2000 RPM, ~0.001/img)
- maxOutputTokens 300->500, descriptions sorted captioned-first
- Remove tsx fallback from step-1 (reviewer nit, published CLI has it)

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 12:03:16 -07:00