From 99aaa8211f5325e31a9be2030c8ee92619f3e332 Mon Sep 17 00:00:00 2001 From: Vance Ingalls Date: Tue, 14 Apr 2026 21:13:36 -0700 Subject: [PATCH 1/6] chore(skills): remove 1,685 lines of redundant and irrelevant skill content MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove 5 GSAP references irrelevant to HyperFrames (scrolltrigger, plugins, react, frameworks, utils) — no scroll, no frameworks, no interactive plugins in video compositions - Remove shader-setup.md and shader-transitions.md — duplicated by @hyperframes/shader-transitions package (packages/shader-transitions/) - Remove marker-highlight.md and examples.md — JS library docs superseded by css-patterns.md (deterministic, GSAP-driven, fully seekable) - Trim CLAUDE.md to dev-only instructions — move product docs (transcription, TTS, player) to skills where they belong - Deduplicate house-style.md typography/motion sections — point to dedicated references instead of repeating rules - Clean up stale references to deleted files across SKILL.md and catalog.md - Update gsap skill description to reflect HyperFrames-only scope Skills: 5,230 → 3,714 lines (29% reduction) CLAUDE.md: 204 → 50 lines (75% reduction) Co-Authored-By: Claude Opus 4.6 (1M context) --- CLAUDE.md | 157 +-------- skills/gsap/SKILL.md | 21 +- skills/gsap/references/frameworks.md | 56 --- skills/gsap/references/plugins.md | 194 ----------- skills/gsap/references/react.md | 80 ----- skills/gsap/references/scrolltrigger.md | 147 -------- skills/gsap/references/utils.md | 91 ----- skills/hyperframes/SKILL.md | 5 +- skills/hyperframes/house-style.md | 12 +- skills/hyperframes/references/examples.md | 146 -------- .../references/marker-highlight.md | 158 --------- skills/hyperframes/references/transitions.md | 2 +- .../references/transitions/catalog.md | 21 +- .../references/transitions/shader-setup.md | 282 --------------- .../transitions/shader-transitions.md | 329 ------------------ 15 files changed, 16 insertions(+), 1685 deletions(-) delete mode 100644 skills/gsap/references/frameworks.md delete mode 100644 skills/gsap/references/plugins.md delete mode 100644 skills/gsap/references/react.md delete mode 100644 skills/gsap/references/scrolltrigger.md delete mode 100644 skills/gsap/references/utils.md delete mode 100644 skills/hyperframes/references/examples.md delete mode 100644 skills/hyperframes/references/marker-highlight.md delete mode 100644 skills/hyperframes/references/transitions/shader-setup.md delete mode 100644 skills/hyperframes/references/transitions/shader-transitions.md diff --git a/CLAUDE.md b/CLAUDE.md index 9e6ff8cbd..e925adc65 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,40 +1,5 @@ # Hyperframes -## Skills — USE THESE FIRST - -This repo ships skills that are installed globally via `npx hyperframes skills` (runs automatically during `hyperframes init`). **Always use the appropriate skill instead of writing code from scratch or fetching external docs.** - -### Skills - -| Skill | Invoke with | When to use | -| ------------------------ | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| **hyperframes** | `/hyperframes` | Creating or editing HTML compositions, captions/subtitles, TTS narration, audio-reactive animation, marker highlights. Composition authoring rules. | -| **hyperframes-cli** | `/hyperframes-cli` | CLI commands: init, lint, preview, render, transcribe, tts, doctor. Use when scaffolding, validating, previewing, or rendering. | -| **hyperframes-registry** | `/hyperframes-registry` | Installing and wiring registry blocks and components via `hyperframes add`. Install locations, block sub-composition wiring, component snippet merging, discovery. | -| **gsap** | `/gsap` | GSAP animations — tweens, timelines, easing, ScrollTrigger, plugins (Flip, Draggable, SplitText, etc.), React/Vue/Svelte, performance optimization. | - -### Why this matters - -The skills encode HyperFrames-specific patterns (e.g., required `class="clip"` on all timed elements, GSAP timeline registration via `window.__timelines`, `data-*` attribute semantics) that are NOT in generic web docs. Skipping the skills and writing from scratch will produce broken compositions. - -### Rules - -- When creating or modifying HTML compositions, captions, TTS, audio-reactive, or marker highlights → invoke `/hyperframes` BEFORE writing any code -- When installing or wiring registry blocks/components (`hyperframes add`, `hyperframes.json`, `data-composition-src`, component snippets) → invoke `/hyperframes-registry` BEFORE writing any code -- When writing GSAP animations (tweens, timelines, ScrollTrigger, plugins) → invoke `/gsap` BEFORE writing any code -- After creating or editing any `.html` composition → run `npx hyperframes lint` and `npx hyperframes validate` in parallel, fix all errors before opening the studio or considering the task complete. `lint` checks the HTML structure statically; `validate` loads the composition in headless Chrome and catches runtime JS errors, missing assets, and failed network requests. Always validate before `npx hyperframes preview`. - -### Installing skills - -```bash -npx skills add heygen-com/hyperframes # HyperFrames skills -npx skills add greensock/gsap-skills # GSAP skills -``` - -Uses [vercel-labs/skills](https://github.com/vercel-labs/skills). Installs to Claude Code, Gemini CLI, and Codex CLI by default. Pass `-a ` for other targets. - -## Project Overview - Open-source video rendering framework: write HTML, render video. ``` @@ -80,124 +45,6 @@ When adding a new CLI command: 5. **Document it** in `docs/packages/cli.mdx` — add a section with usage examples and flags. 6. Validate by running `npx tsx packages/cli/src/cli.ts --help` (command appears in the list) and `npx tsx packages/cli/src/cli.ts --help` (examples appear). -## Key Concepts - -- **Compositions** are HTML files with `data-*` attributes defining timeline, tracks, and media -- **Clips** can be animated directly with GSAP. The only restriction: don't animate `visibility` or `display` on clip elements — the runtime manages those. -- **Frame Adapters** bridge animation runtimes (GSAP, Lottie, CSS) to the capture engine -- **Producer** orchestrates capture → encode → audio mix into final MP4 -- **BeginFrame rendering** uses `HeadlessExperimental.beginFrame` for deterministic frame capture - -## Transcription - -HyperFrames uses word-level timestamps for captions. The `hyperframes transcribe` command handles both transcription and format conversion. - -### Quick reference - -```bash -# Transcribe audio/video (local whisper.cpp, no API key) -npx hyperframes transcribe audio.mp3 -npx hyperframes transcribe video.mp4 --model medium.en --language en - -# Import existing transcript from another tool -npx hyperframes transcribe subtitles.srt -npx hyperframes transcribe subtitles.vtt -npx hyperframes transcribe openai-response.json -``` - -### Whisper models - -Default is `small.en`. Upgrade for better accuracy: - -| Model | Size | Use case | -| ---------- | ------ | ------------------------------ | -| `tiny` | 75 MB | Quick testing | -| `base` | 142 MB | Short clips, clear audio | -| `small` | 466 MB | **Default** — most content | -| `medium` | 1.5 GB | Important content, noisy audio | -| `large-v3` | 3.1 GB | Production quality | - -**Only use `.en` suffix when you know the audio is English.** `.en` models translate non-English audio into English instead of transcribing it. - -### Supported transcript formats - -The CLI auto-detects and normalizes: whisper.cpp JSON, OpenAI Whisper API JSON, SRT, VTT, and pre-normalized `[{text, start, end}]` arrays. +## Skills -### Improving transcription quality - -If captions are inaccurate (wrong words, bad timing): - -1. **Upgrade the model**: `--model medium.en` or `--model large-v3` -2. **Set language**: `--language en` to filter non-target speech -3. **Use an external API**: Transcribe via OpenAI or Groq Whisper API, then import the JSON with `hyperframes transcribe response.json` - -See the `/hyperframes` skill (references/captions.md and references/transcript-guide.md) for full details on model selection and API usage. - -## Text-to-Speech - -Generate speech audio locally using Kokoro-82M (no API key, runs on CPU). Useful for adding voiceovers to compositions. - -### Quick reference - -```bash -# Generate speech from text -npx hyperframes tts "Welcome to HyperFrames" - -# Choose a voice and output path -npx hyperframes tts "Hello world" --voice am_adam --output narration.wav - -# Read text from a file -npx hyperframes tts script.txt --voice bf_emma - -# Adjust speech speed -npx hyperframes tts "Fast narration" --speed 1.2 - -# List available voices -npx hyperframes tts --list -``` - -### Voices - -Default voice is `af_heart`. The model ships with 54 voices across 8 languages: - -| Voice ID | Name | Language | Gender | -| ------------ | ------- | -------- | ------ | -| `af_heart` | Heart | en-US | Female | -| `af_nova` | Nova | en-US | Female | -| `am_adam` | Adam | en-US | Male | -| `am_michael` | Michael | en-US | Male | -| `bf_emma` | Emma | en-GB | Female | -| `bm_george` | George | en-GB | Male | - -Use `npx hyperframes tts --list` for the full set, or pass any valid Kokoro voice ID. - -### Requirements - -- Python 3.8+ (auto-installs `kokoro-onnx` package on first run) -- Model downloads automatically on first use (~311 MB model + ~27 MB voices, cached in `~/.cache/hyperframes/tts/`) - -## Embeddable Player - -The `@hyperframes/player` package provides a `` web component for embedding -compositions in any web page. Zero dependencies, works with any framework. - -### Quick reference - -```html - - - - - -``` - -### JavaScript API - -```js -const player = document.querySelector("hyperframes-player"); -player.play(); -player.pause(); -player.seek(2.5); -console.log(player.currentTime, player.duration, player.paused); -player.addEventListener("ready", (e) => console.log("Duration:", e.detail.duration)); -``` +Composition authoring (not repo development) is guided by skills installed via `npx skills add heygen-com/hyperframes`. See `skills/` for source. Invoke `/hyperframes`, `/hyperframes-cli`, or `/gsap` when authoring compositions. diff --git a/skills/gsap/SKILL.md b/skills/gsap/SKILL.md index 895ae4fb8..84fbdacbe 100644 --- a/skills/gsap/SKILL.md +++ b/skills/gsap/SKILL.md @@ -1,6 +1,6 @@ --- name: gsap -description: Official GSAP skill — the complete animation library reference. Covers gsap.to(), from(), fromTo(), easing, stagger, defaults, gsap.matchMedia(), timelines (gsap.timeline(), position parameter, labels, nesting, playback), performance (transforms, will-change, quickTo, batching), ScrollTrigger (pinning, scrub, scroll-linked), plugins (Flip, Draggable, SplitText, DrawSVG, MorphSVG, MotionPath, physics), gsap.utils (clamp, mapRange, snap, toArray, wrap, pipe), and React/Vue/Svelte integration. Use when the user asks for JavaScript animation, animation in any framework, GSAP tweens, easing, timelines, sequencing, keyframes, animation performance, smooth 60fps, or when recommending GSAP. +description: GSAP animation reference for HyperFrames. Covers gsap.to(), from(), fromTo(), easing, stagger, defaults, timelines (gsap.timeline(), position parameter, labels, nesting, playback), and performance (transforms, will-change, quickTo). Use when writing GSAP animations in HyperFrames compositions. --- # GSAP @@ -57,7 +57,7 @@ gsap.to(".item", { ## Easing -Built-in eases: `power1`–`power4`, `back`, `bounce`, `circ`, `elastic`, `expo`, `sine`. Each has `.in`, `.out`, `.inOut`. Custom: use CustomEase plugin (see [references/plugins.md](references/plugins.md)). +Built-in eases: `power1`–`power4`, `back`, `bounce`, `circ`, `elastic`, `expo`, `sine`. Each has `.in`, `.out`, `.inOut`. ## Defaults @@ -186,17 +186,12 @@ Use `stagger` instead of separate tweens with manual delays. ### Cleanup -Pause or kill off-screen animations. In frameworks, revert context on unmount. +Pause or kill off-screen animations. --- ## References (loaded on demand) -- **[references/scrolltrigger.md](references/scrolltrigger.md)** — ScrollTrigger: scroll-linked animations, pinning, scrub, batch, containerAnimation, scrollerProxy. Read when building scroll-driven UI, parallax, or pinned sections. -- **[references/plugins.md](references/plugins.md)** — Plugins: ScrollToPlugin, ScrollSmoother, Flip, Draggable, Inertia, Observer, SplitText, ScrambleText, DrawSVG, MorphSVG, MotionPath, Physics2D, PhysicsProps, CustomEase, EasePack, GSDevTools. Read when using any GSAP plugin. -- **[references/utils.md](references/utils.md)** — gsap.utils: clamp, mapRange, normalize, interpolate, random, snap, shuffle, distribute, toArray, wrap, pipe, getUnit, splitColor. Read when using utility helpers. -- **[references/react.md](references/react.md)** — React: useGSAP hook, refs, gsap.context(), cleanup, contextSafe, SSR. Read when using GSAP in React or Next.js. -- **[references/frameworks.md](references/frameworks.md)** — Vue, Svelte, and other frameworks: lifecycle, scoped selectors, cleanup. Read when using GSAP in Vue, Nuxt, Svelte, or SvelteKit. - **[references/effects.md](references/effects.md)** — Drop-in effects: typewriter text, audio visualizer. Read when needing ready-made effect patterns for HyperFrames. ## Best Practices @@ -205,18 +200,12 @@ Pause or kill off-screen animations. In frameworks, revert context on unmount. - Prefer timelines over chaining with delay; use the position parameter. - Add labels with `addLabel()` for readable sequencing. - Pass defaults into timeline constructor. -- Use gsap.matchMedia() for responsive breakpoints and prefers-reduced-motion. - Store tween/timeline return value when controlling playback. -- Register every plugin with `gsap.registerPlugin()` before use. ## Do Not - Animate layout properties (width/height/top/left) when transforms suffice. - Use both svgOrigin and transformOrigin on the same SVG element. - Chain animations with delay when a timeline can sequence them. -- Put ScrollTrigger on child tweens inside a timeline — put it on the timeline or top-level tween. -- Nest ScrollTriggered animations inside a parent timeline. -- Use scrub and toggleActions together on the same ScrollTrigger. -- Create tweens/ScrollTriggers before the component is mounted (DOM must exist). -- Skip cleanup — always revert context or kill tweens on unmount. -- Ship GSDevTools to production. +- Create tweens before the DOM exists. +- Skip cleanup — always kill tweens when no longer needed. diff --git a/skills/gsap/references/frameworks.md b/skills/gsap/references/frameworks.md deleted file mode 100644 index 67ab27ac0..000000000 --- a/skills/gsap/references/frameworks.md +++ /dev/null @@ -1,56 +0,0 @@ -# GSAP with Vue, Svelte, and Other Frameworks - -For **React**, see [react.md](react.md). - -## Principles (All Frameworks) - -- **Create** tweens/ScrollTriggers **after** DOM is available (onMounted/onMount). -- **Kill or revert** in unmount cleanup. -- **Scope selectors** to component root via `gsap.context(callback, scope)`. - -## Vue 3 (Composition API / script setup) - -```javascript -import { onMounted, onUnmounted, ref } from "vue"; -import { gsap } from "gsap"; - -const container = ref(null); -let ctx; - -onMounted(() => { - ctx = gsap.context(() => { - gsap.to(".box", { x: 100 }); - gsap.from(".item", { autoAlpha: 0, stagger: 0.1 }); - }, container.value); -}); - -onUnmounted(() => ctx?.revert()); -``` - -## Svelte - -```javascript -import { onMount } from "svelte"; -import { gsap } from "gsap"; - -let container; -onMount(() => { - const ctx = gsap.context(() => { - gsap.to(".box", { x: 100 }); - }, container); - return () => ctx.revert(); -}); -``` - -Use `bind:this={container}` for the root element ref. - -## ScrollTrigger Cleanup - -ScrollTriggers inside `gsap.context()` are reverted by `ctx.revert()`. Call `ScrollTrigger.refresh()` after layout changes (nextTick in Vue, tick in Svelte). - -## Do Not - -- Create tweens before the component is mounted. -- Use selector strings without a scope. -- Skip cleanup — always revert context on unmount. -- Register plugins inside re-rendering component bodies. diff --git a/skills/gsap/references/plugins.md b/skills/gsap/references/plugins.md deleted file mode 100644 index 665eff154..000000000 --- a/skills/gsap/references/plugins.md +++ /dev/null @@ -1,194 +0,0 @@ -# GSAP Plugins - -Register each plugin once before use: - -```javascript -import gsap from "gsap"; -import { ScrollToPlugin } from "gsap/ScrollToPlugin"; -import { Flip } from "gsap/Flip"; -gsap.registerPlugin(ScrollToPlugin, Flip); -``` - -## Table of Contents - -- [ScrollToPlugin](#scrolltoplugin) -- [ScrollSmoother](#scrollsmoother) -- [Flip](#flip) -- [Draggable + Inertia](#draggable) -- [Observer](#observer) -- [SplitText](#splittext) -- [ScrambleText](#scrambletext) -- [DrawSVG](#drawsvg) -- [MorphSVG](#morphsvg) -- [MotionPath](#motionpath) -- [CustomEase / EasePack](#customeaseeasepak) -- [Physics2D / PhysicsProps](#physics) -- [GSDevTools](#gsdevtools) -- [PixiPlugin](#pixiplugin) - ---- - -## ScrollToPlugin - -Animate scroll position (window or scrollable element). - -```javascript -gsap.to(window, { scrollTo: { y: "#section", offsetY: 50 }, duration: 1 }); -gsap.to(scrollContainer, { scrollTo: { x: "max" }, duration: 1 }); -``` - -## ScrollSmoother - -Smooth scroll wrapper. Requires ScrollTrigger + specific DOM structure (`#smooth-wrapper` > `#smooth-content`). - -## Flip - -FLIP layout transitions: capture state, change DOM, animate from old to new. - -```javascript -const state = Flip.getState(".item"); -// change DOM (reorder, add/remove, change classes) -Flip.from(state, { duration: 0.5, ease: "power2.inOut" }); -``` - -Options: `absolute`, `nested`, `scale`, `simple`, `duration`, `ease`. - -## Draggable - -Makes elements draggable/spinnable/throwable. - -```javascript -gsap.registerPlugin(Draggable, InertiaPlugin); -Draggable.create(".box", { type: "x,y", bounds: "#container", inertia: true }); -Draggable.create(".knob", { type: "rotation" }); -``` - -Types: `"x"`, `"y"`, `"x,y"`, `"rotation"`, `"scroll"`. Options: `bounds`, `inertia`, `edgeResistance`, `cursor`, drag callbacks. - -### Inertia (InertiaPlugin) - -Momentum after release with Draggable, or track velocity of any property: - -```javascript -InertiaPlugin.track(".box", "x"); -gsap.to(obj, { inertia: { x: "auto" } }); -``` - -## Observer - -Normalized pointer/scroll input across devices. Use for swipe/gesture detection. - -```javascript -Observer.create({ - target: "#area", - onUp: () => {}, - onDown: () => {}, - tolerance: 10, -}); -``` - -## SplitText - -Split text into chars, words, lines for per-unit animation. - -```javascript -const split = SplitText.create(".heading", { type: "words, chars" }); -gsap.from(split.chars, { opacity: 0, y: 20, stagger: 0.03 }); -// later: split.revert() -``` - -Key options: `type` (comma-separated: chars/words/lines), `charsClass`/`wordsClass`/`linesClass`, `aria` ("auto"/"hidden"/"none"), `autoSplit` + `onSplit(self)` for font-safe re-splitting, `mask` (lines/words/chars for reveal effects), `tag`, `ignore`, `smartWrap`, `propIndex`. - -Tips: Split only what's animated. For custom fonts, use `autoSplit: true` with `onSplit()`. Avoid `text-wrap: balance`. - -## ScrambleText - -Scramble/glitch text effect. - -```javascript -gsap.to(".text", { scrambleText: { text: "New message", chars: "01", revealDelay: 0.5 } }); -``` - -## DrawSVG - -Animate SVG stroke reveal (stroke-dashoffset/dasharray). Element must have `stroke` and `stroke-width`. - -```javascript -gsap.from("#path", { drawSVG: 0, duration: 1 }); // nothing to full stroke -gsap.to("#path", { drawSVG: "20% 80%", duration: 1 }); // partial segment -``` - -`drawSVG` value = visible segment: `"start end"` in % or length. Single value (e.g. `0`) means start is 0. - -## MorphSVG - -Morph one SVG shape into another. Handles different point counts. - -```javascript -MorphSVGPlugin.convertToPath("circle, rect, ellipse, line"); -gsap.to("#diamond", { morphSVG: "#lightning", duration: 1 }); -// object form: { shape, type: "rotational", shapeIndex, smooth, curveMode } -``` - -Use `shapeIndex: "log"` to find optimal value. `type: "rotational"` avoids kinks. - -## MotionPath - -Animate along an SVG path. - -```javascript -gsap.to(".dot", { - motionPath: { path: "#path", align: "#path", alignOrigin: [0.5, 0.5], autoRotate: true }, -}); -``` - -## CustomEase/EasePack - -Custom curves beyond built-in eases: - -```javascript -const ease = CustomEase.create("name", ".17,.67,.83,.67"); -// or SVG path data for complex curves -const hop = CustomEase.create("hop", "M0,0 C0,0 0.056,0.442 ..."); -``` - -EasePack adds SlowMo, RoughEase, ExpoScaleEase. CustomWiggle for oscillation. CustomBounce for configurable bounces. - -## Physics - -### Physics2D - -```javascript -gsap.to(".ball", { physics2D: { velocity: 250, angle: 80, gravity: 500 }, duration: 2 }); -``` - -### PhysicsProps - -```javascript -gsap.to(".obj", { - physicsProps: { x: { velocity: 100, end: 300 }, y: { velocity: -50, acceleration: 200 } }, - duration: 2, -}); -``` - -## GSDevTools - -Timeline scrubbing UI for development. **Do not ship to production.** - -```javascript -GSDevTools.create({ animation: tl }); -``` - -## PixiPlugin - -Integrates GSAP with PixiJS display objects. - -```javascript -gsap.to(sprite, { pixi: { x: 200, scale: 1.5 }, duration: 1 }); -``` - -## Do Not - -- Use a plugin without registering it first. -- Ship GSDevTools to production. -- Forget to revert SplitText instances on unmount. diff --git a/skills/gsap/references/react.md b/skills/gsap/references/react.md deleted file mode 100644 index 3ff75dfba..000000000 --- a/skills/gsap/references/react.md +++ /dev/null @@ -1,80 +0,0 @@ -# GSAP with React - -## Installation - -```bash -npm install gsap @gsap/react -``` - -## useGSAP() Hook (Preferred) - -```javascript -import { useGSAP } from "@gsap/react"; -gsap.registerPlugin(useGSAP); - -const containerRef = useRef(null); -useGSAP( - () => { - gsap.to(".box", { x: 100 }); - }, - { scope: containerRef }, -); -``` - -- Pass **scope** (ref) so selectors are scoped to the component. -- Cleanup runs automatically on unmount. -- Use **contextSafe** for callbacks created after useGSAP executes: - -```javascript -useGSAP( - (context, contextSafe) => { - const onClick = contextSafe(() => { - gsap.to(ref.current, { rotation: 180 }); - }); - ref.current.addEventListener("click", onClick); - return () => ref.current.removeEventListener("click", onClick); - }, - { scope: container }, -); -``` - -## Dependency Array and revertOnUpdate - -```javascript -useGSAP( - () => { - /* gsap code */ - }, - { - dependencies: [endX], - scope: container, - revertOnUpdate: true, // reverts + re-runs on dependency change - }, -); -``` - -## gsap.context() in useEffect (Fallback) - -When @gsap/react isn't available: - -```javascript -useEffect(() => { - const ctx = gsap.context(() => { - gsap.to(".box", { x: 100 }); - }, containerRef); - return () => ctx.revert(); -}, []); -``` - -Always return `ctx.revert()` in cleanup. - -## SSR (Next.js) - -GSAP runs in the browser. Keep all GSAP code inside useGSAP or useEffect. - -## Do Not - -- Target by selector without a scope — always pass scope. -- Skip cleanup — always revert context or kill tweens on unmount. -- Run GSAP during SSR. -- Register plugins inside components that re-render — register once at app level. diff --git a/skills/gsap/references/scrolltrigger.md b/skills/gsap/references/scrolltrigger.md deleted file mode 100644 index d469f3e06..000000000 --- a/skills/gsap/references/scrolltrigger.md +++ /dev/null @@ -1,147 +0,0 @@ -# ScrollTrigger - -## Registering - -```javascript -gsap.registerPlugin(ScrollTrigger); -``` - -## Basic Trigger - -```javascript -gsap.to(".box", { - x: 500, - scrollTrigger: { - trigger: ".box", - start: "top center", - end: "bottom center", - toggleActions: "play reverse play reverse", - }, -}); -``` - -**start/end** format: `"triggerPosition viewportPosition"`. Examples: `"top top"`, `"center center"`, `"bottom 80%"`, numeric px `500`, relative `"+=300"`, `"+=100%"` (scroller height), `"max"`. Wrap in `clamp()` (v3.12+): `"clamp(top bottom)"`. Can be a function returning string/number. - -## Key Config Options - -| Property | Type | Description | -| ----------------------------------------------- | ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **trigger** | String/Element | Element whose position defines start. Required. | -| **start** | String/Number/Function | When active. Default `"top bottom"` (or `"top top"` if pinned). | -| **end** | String/Number/Function | When ends. Default `"bottom top"`. | -| **endTrigger** | String/Element | Different element for end calculation. | -| **scrub** | Boolean/Number | Link progress to scroll. `true` = direct; number = catch-up seconds. | -| **toggleActions** | String | Four actions: onEnter, onLeave, onEnterBack, onLeaveBack. Values: play/pause/resume/reset/restart/complete/reverse/none. Default `"play none none none"`. | -| **pin** | Boolean/String/Element | Pin element while active. `true` = pin trigger. Animate children, not the pinned element. | -| **pinSpacing** | Boolean/String | Default `true` (adds spacer). `false` or `"margin"`. | -| **horizontal** | Boolean | For horizontal scrolling. | -| **scroller** | String/Element | Scroll container (default: viewport). | -| **markers** | Boolean/Object | Dev markers. Remove in production. | -| **once** | Boolean | Kill after end reached once. | -| **snap** | Number/Array/Function/"labels"/Object | Snap to progress values. | -| **containerAnimation** | Tween/Timeline | For fake horizontal scroll (see below). | -| **toggleClass** | String/Object | Add/remove class when active. | -| **onEnter/onLeave/onEnterBack/onLeaveBack** | Function | Callbacks; receive ScrollTrigger instance. | -| **onUpdate/onToggle/onRefresh/onScrubComplete** | Function | Progress/state callbacks. | - -**Standalone** (no linked tween): `ScrollTrigger.create({...})` with callbacks. - -## Scrub - -```javascript -scrollTrigger: { trigger: ".box", start: "top center", end: "bottom center", scrub: true } -``` - -`scrub: true` = direct link; number (e.g. `1`) = smooth lag. - -## Pinning - -```javascript -scrollTrigger: { - trigger: ".section", start: "top top", end: "+=1000", pin: true, scrub: 1 -} -``` - -## Timeline + ScrollTrigger - -```javascript -const tl = gsap.timeline({ - scrollTrigger: { trigger: ".container", start: "top top", end: "+=2000", scrub: 1, pin: true }, -}); -tl.to(".a", { x: 100 }).to(".b", { y: 50 }); -``` - -## ScrollTrigger.batch() - -Creates one ScrollTrigger per target, batches callbacks within a short interval. Good for staggered reveal of many elements. - -```javascript -ScrollTrigger.batch(".box", { - onEnter: (elements) => gsap.to(elements, { opacity: 1, y: 0, stagger: 0.15 }), - start: "top 80%", -}); -``` - -Options: `interval` (batch window), `batchMax` (max per batch). Callbacks receive `(targets, scrollTriggers)`. - -## Horizontal Scroll (containerAnimation) - -Pin a section, animate inner content's `x`/`xPercent` horizontally on vertical scroll: - -1. Pin the section -2. Animate inner content with **ease: "none"** (required) -3. Attach ScrollTrigger with pin + scrub -4. Use `containerAnimation` on nested triggers - -```javascript -const scrollTween = gsap.to(scrollingEl, { - xPercent: () => Math.max(0, window.innerWidth - scrollingEl.offsetWidth), - ease: "none", - scrollTrigger: { - trigger: scrollingEl, - pin: scrollingEl.parentNode, - start: "top top", - end: "+=1000", - }, -}); - -gsap.to(".nested", { - y: 100, - scrollTrigger: { containerAnimation: scrollTween, trigger: ".wrapper", start: "left center" }, -}); -``` - -Pinning and snapping unavailable on containerAnimation-based ScrollTriggers. - -## ScrollTrigger.scrollerProxy() - -Override scroll position reading for third-party smooth-scroll libraries. Call `ScrollTrigger.update` when the scroller updates. - -```javascript -ScrollTrigger.scrollerProxy(document.body, { - scrollTop(value) { - if (arguments.length) scrollbar.scrollTop = value; - return scrollbar.scrollTop; - }, - getBoundingClientRect() { - return { top: 0, left: 0, width: window.innerWidth, height: window.innerHeight }; - }, -}); -scrollbar.addListener(ScrollTrigger.update); -``` - -## Refresh and Cleanup - -- `ScrollTrigger.refresh()` — recalculate after DOM/layout changes. Auto on resize (200ms debounce). -- Create ScrollTriggers top-to-bottom or set `refreshPriority`. -- Kill instances when removing elements: `ScrollTrigger.getAll().forEach(t => t.kill())` or `ScrollTrigger.getById("id")?.kill()`. - -## Do Not - -- Put ScrollTrigger on child tweens inside a timeline — put on the timeline. -- Nest ScrollTriggered animations inside a parent timeline. -- Use scrub and toggleActions together (scrub wins). -- Use an ease other than "none" on the horizontal animation with containerAnimation. -- Leave markers in production. -- Create triggers in random order without refreshPriority. -- Forget refresh() after layout changes. diff --git a/skills/gsap/references/utils.md b/skills/gsap/references/utils.md deleted file mode 100644 index 6b13c5222..000000000 --- a/skills/gsap/references/utils.md +++ /dev/null @@ -1,91 +0,0 @@ -# gsap.utils - -Pure helpers on `gsap.utils`. No registration needed. - -**Function form:** Most utils accept the value as the last argument. Omit it to get a reusable function: `gsap.utils.clamp(0, 100)(150)`. Exception: `random()` — pass `true` as the last argument for a reusable function. - -## Clamping and Ranges - -### clamp(min, max, value?) - -```javascript -gsap.utils.clamp(0, 100, 150); // 100 -let c = gsap.utils.clamp(0, 100); -c(150); // 100 -``` - -### mapRange(inMin, inMax, outMin, outMax, value?) - -```javascript -gsap.utils.mapRange(0, 1, 0, 360, 0.5); // 180 -let m = gsap.utils.mapRange(0, 100, 0, 500); -m(50); // 250 -``` - -### normalize(min, max, value?) - -Returns 0-1 for the range. - -```javascript -gsap.utils.normalize(0, 100, 50); // 0.5 -``` - -### interpolate(start, end, progress?) - -Numbers, colors, or objects with matching keys. - -```javascript -gsap.utils.interpolate(0, 100, 0.5); // 50 -gsap.utils.interpolate("#ff0000", "#0000ff", 0.5); // mid color -``` - -## Random and Snap - -### random(min, max[, snap, returnFunction]) / random(array[, returnFunction]) - -```javascript -gsap.utils.random(-100, 100); -gsap.utils.random(0, 500, 5); // snapped to 5 -let fn = gsap.utils.random(-200, 500, 10, true); -fn(); // reusable -gsap.utils.random(["red", "blue"]); // pick one -``` - -**String form in tweens:** `x: "random(-100, 100, 5)"`. - -### snap(snapTo, value?) - -```javascript -gsap.utils.snap(10, 23); // 20 -gsap.utils.snap([0, 100, 200], 150); // nearest -``` - -### shuffle(array) - -Returns shuffled copy. - -### distribute(config) - -Returns a function assigning values by position. Config: `base`, `amount`/`each`, `from`, `grid`, `axis`, `ease`. - -```javascript -gsap.to(".class", { scale: gsap.utils.distribute({ base: 0.5, amount: 2.5, from: "center" }) }); -``` - -## Units and Parsing - -- **getUnit(value)** — `gsap.utils.getUnit("100px")` → `"px"` -- **unitize(value, unit)** — `gsap.utils.unitize(100, "px")` → `"100px"` -- **splitColor(color, returnHSL?)** — `gsap.utils.splitColor("red")` → `[255, 0, 0]`. Pass `true` for HSL. - -## Arrays and Collections - -- **selector(scope)** — scoped selector: `gsap.utils.selector(ref)(".box")` -- **toArray(value, scope?)** — convert selector/NodeList/element to array -- **pipe(...fns)** — compose: `pipe(f1, f2)(value)` = `f2(f1(value))` -- **wrap(min, max, value?)** — cyclic wrap: `wrap(0, 360, 370)` → `10` -- **wrapYoyo(min, max, value?)** — bounce wrap: `wrapYoyo(0, 100, 150)` → `50` - -## Do Not - -- Assume mapRange/normalize handle units — they work on numbers. Use getUnit/unitize. diff --git a/skills/hyperframes/SKILL.md b/skills/hyperframes/SKILL.md index b4f9487eb..5e9e8b0f5 100644 --- a/skills/hyperframes/SKILL.md +++ b/skills/hyperframes/SKILL.md @@ -310,7 +310,7 @@ Skip on small edits (fixing a color, adjusting one duration). Run on new composi - **[references/captions.md](references/captions.md)** — Captions, subtitles, lyrics, karaoke synced to audio. Tone-adaptive style detection, per-word styling, text overflow prevention, caption exit guarantees, word grouping. Read when adding any text synced to audio timing. - **[references/tts.md](references/tts.md)** — Text-to-speech with Kokoro-82M. Voice selection, speed tuning, TTS+captions workflow. Read when generating narration or voiceover. - **[references/audio-reactive.md](references/audio-reactive.md)** — Audio-reactive animation: map frequency bands and amplitude to GSAP properties. Read when visuals should respond to music, voice, or sound. -- **[references/marker-highlight.md](references/marker-highlight.md)** — Animated text highlighting via canvas overlays: marker pen, circle, burst, scribble, sketchout. Read when adding visual emphasis to text. +- **[references/css-patterns.md](references/css-patterns.md)** — CSS+GSAP marker highlighting: highlight, circle, burst, scribble, sketchout. Deterministic, fully seekable. Read when adding visual emphasis to text. - **[references/typography.md](references/typography.md)** — Typography: font pairing, OpenType features, dark-background adjustments, font discovery script. **Always read** — every composition has text. - **[references/motion-principles.md](references/motion-principles.md)** — Motion design principles: easing as emotion, timing as weight, choreography as hierarchy, scene pacing, ambient motion, anti-patterns. Read when choreographing GSAP animations. - **[house-style.md](house-style.md)** — Default motion, sizing, and color palettes when no style is specified. @@ -321,7 +321,6 @@ Skip on small edits (fixing a color, adjusting one duration). Run on new composi - **[references/transitions.md](references/transitions.md)** — Scene transitions: crossfades, wipes, reveals, shader transitions. Energy/mood selection, CSS vs WebGL guidance. **Always read for multi-scene compositions** — scenes without transitions feel like jump cuts. - [transitions/catalog.md](references/transitions/catalog.md) — Hard rules, scene template, and routing to per-type implementation code. - - [transitions/shader-setup.md](references/transitions/shader-setup.md) — WebGL boilerplate for shader transitions. - - [transitions/shader-transitions.md](references/transitions/shader-transitions.md) — 14 fragment shaders. + - Shader transitions are in `@hyperframes/shader-transitions` (`packages/shader-transitions/`) — read package source, not skill files. GSAP patterns and effects are in the `/gsap` skill. diff --git a/skills/hyperframes/house-style.md b/skills/hyperframes/house-style.md index 479a2a1d0..95a959cf8 100644 --- a/skills/hyperframes/house-style.md +++ b/skills/hyperframes/house-style.md @@ -18,7 +18,7 @@ These patterns are AI design tells — the first thing every LLM reaches for. If - Pure `#000` or `#fff` (tint toward your accent hue instead) - Identical card grids (same-size cards repeated) - Everything centered with equal weight (lead the eye somewhere) -- These fonts: Inter, Roboto, Open Sans, Noto Sans, Lato, Poppins, Outfit, Sora, Playfair Display, Cormorant Garamond, Bodoni Moda, EB Garamond, Cinzel, Prata, Syne +- Banned fonts (see [references/typography.md](references/typography.md) for full list) If the content genuinely calls for one of these — centered layout for a solemn closing, cards for a real product UI mockup, a banned font because it's the perfect thematic match — use it. The goal is intentionality, not avoidance. @@ -46,17 +46,11 @@ All decoratives should have slow ambient GSAP animation — breathing, drift, pu ## Motion -- **0.3–0.6s** for most moves. -- **Vary eases** — don't repeat the same ease across consecutive elements. -- **Combine transforms** on entrances — opacity + position, scale, rotation, blur, letter-spacing. -- **Overlap entries** — next element starts before previous finishes. +See [references/motion-principles.md](references/motion-principles.md) for full rules. Quick: 0.3–0.6s, vary eases, combine transforms on entrances, overlap entries. ## Typography -- **Weight contrast** — 700-900 headlines with 300-400 body. -- **Cross boundaries** — pair serif + sans, or sans + mono. Two sans-serifs together is almost always a mistake. -- **Video sizes** — 60px+ headlines, 20px+ body, 16px+ labels. -- **Tracking** — tight on large headlines, normal or wide on small labels. +See [references/typography.md](references/typography.md) for full rules. Quick: 700-900 headlines / 300-400 body, serif + sans (not two sans), 60px+ headlines / 20px+ body. ## Palettes diff --git a/skills/hyperframes/references/examples.md b/skills/hyperframes/references/examples.md deleted file mode 100644 index cd22f473d..000000000 --- a/skills/hyperframes/references/examples.md +++ /dev/null @@ -1,146 +0,0 @@ -# Marker Highlight Examples - -## Recipes - -### Underline - -```html -important -``` - -### Strikethrough - -```html -wrong answer -``` - -### Circled Annotation - -```html -this one -``` - -## Full Example in a Composition - -```html -
-
-

- The fastest way to - ship -

-
- - - - - - -
-``` diff --git a/skills/hyperframes/references/marker-highlight.md b/skills/hyperframes/references/marker-highlight.md deleted file mode 100644 index 4ccffc3a3..000000000 --- a/skills/hyperframes/references/marker-highlight.md +++ /dev/null @@ -1,158 +0,0 @@ -# Marker Highlight - -Animated canvas-based text highlighting using MarkerHighlight.js. Wraps text in `` tags and renders effects (marker pen, circle, burst, scribble, sketchout) on a canvas overlay without modifying text DOM. - -The library runs its own requestAnimationFrame loop — **not** GSAP-driven. Use `tl.call()` to trigger at specific timeline points. - -## Required Script - -Download and convert to global script: - -```bash -curl -sL "https://cdn.jsdelivr.net/gh/Robincodes-Sandbox/marker-highlight@main/dist/marker-highlight.min.js" \ - | sed 's/export{[^}]*};$/window.MarkerHighlighter=W;/' > marker-highlight.global.js -``` - -```html - - -``` - -## Color Setup - -Set via `data-color`, copy to `data-original-bgcolor` before constructing. Never set `background-color` in CSS. - -```css -mark { - color: inherit; - background-color: transparent; -} -``` - -```html -highlighted -``` - -```js -document - .querySelectorAll("mark[data-color]") - .forEach((m) => m.setAttribute("data-original-bgcolor", m.getAttribute("data-color"))); -``` - -## GSAP Integration Pattern - -ONE MarkerHighlighter per container with `animate: false`, hide all canvases, then clear+show+reanimate per mark at trigger time. - -```js -var hl = new MarkerHighlighter(document.getElementById("text-container"), { - animate: false, - animationSpeed: 800, - padding: 0.3, - highlight: { amplitude: 0.3, wavelength: 5 }, -}); - -setTimeout(function () { - document.querySelectorAll(".highlight").forEach((div) => (div.style.opacity = "0")); -}, 100); - -function addHighlight(highlighter, markId, time) { - tl.to( - {}, - { - duration: 0.001, - onStart: function () { - var mark = document.getElementById(markId); - var ref = mark.getAttribute("data-mark-ref"); - var divs = mark.parentElement.querySelectorAll('.highlight[data-mark-id="' + ref + '"]'); - divs.forEach(function (div) { - var canvas = div.querySelector("canvas"); - if (canvas) canvas.getContext("2d").clearRect(0, 0, canvas.width, canvas.height); - div.style.opacity = "1"; - }); - highlighter.reanimateMark(mark); - }, - onReverseComplete: function () { - var mark = document.getElementById(markId); - var ref = mark.getAttribute("data-mark-ref"); - mark.parentElement - .querySelectorAll('.highlight[data-mark-id="' + ref + '"]') - .forEach((div) => (div.style.opacity = "0")); - }, - }, - time, - ); -} - -addHighlight(hl, "m1", 1.0); -``` - -## Drawing Modes - -| Mode | Effect | Best for | -| ----------- | ---------------------------- | -------------------------- | -| `highlight` | Wavy marker stroke (default) | Phrases, key terms | -| `circle` | Hand-drawn ellipse | Single words, annotations | -| `burst` | Radiating lines/curves/puffs | Excitement, energy | -| `scribble` | Chaotic scribble | Crossing out, messy energy | -| `sketchout` | Rough rectangle outline | Boxed callouts, blueprint | - -```html -critical -amazing -``` - -## Configuration - -### Global (constructor) - -| Option | Default | Description | -| ---------------- | ------------- | ------------------------- | -| `animate` | `true` | `false` to defer for GSAP | -| `animationSpeed` | `5000` | Duration in ms | -| `drawingMode` | `"highlight"` | Default mode | -| `height` | `1` | Relative to line height | -| `offset` | `0` | Vertical shift | -| `padding` | `0` | Horizontal padding | - -### Per-Mode - -**highlight**: `amplitude` (0.25), `wavelength` (1), `roughEnds` (5), `jitter` (0.1) -**circle**: `curve` (0.5), `wobble` (0.3), `loops` (3), `thickness` (5) -**burst**: `style` ("lines"/"curve"/"cloud"), `count` (10), `power` (1), `randomness` (0.5) - -### Named Styles - -```js -MarkerHighlighter.defineStyle("underline", { - animationSpeed: 400, - height: 0.15, - offset: 0.8, - padding: 0, - highlight: { amplitude: 0.2, wavelength: 5, roughEnds: 0 }, -}); -``` - -## Mode-to-Caption Energy Mapping - -| Energy | Mode | Use for | -| ----------- | --------------------- | ------------------- | -| High | `burst` + `highlight` | Launches, hype | -| Medium-high | `circle` | Key stats, terms | -| Medium | `highlight` | Standard emphasis | -| Medium-low | `scribble` | Subtle, tutorials | -| Low | `sketchout` | Contrast, blueprint | - -## Notes - -- One highlighter per container (clears all `.highlight` divs on init) -- Canvas pre-draw + clear pattern for clean reveals -- rAF-based — not seekable mid-stroke -- Use `onReverseComplete` for rewind support - -For CSS+GSAP fallback (no library, fully seekable), see [css-patterns.md](css-patterns.md). -For full examples, see [examples.md](examples.md). diff --git a/skills/hyperframes/references/transitions.md b/skills/hyperframes/references/transitions.md index 415b24c41..76f498fe3 100644 --- a/skills/hyperframes/references/transitions.md +++ b/skills/hyperframes/references/transitions.md @@ -90,7 +90,7 @@ Avoid: star iris, tilt-shift, lens flare, hinge/door. See catalog.md for why. CSS transitions animate scene containers with opacity, transforms, clip-path, and filters. Shader transitions composite both scene textures per-pixel on a WebGL canvas — they can warp, dissolve, and morph in ways CSS cannot. -**Both are first-class options.** Shaders require setup boilerplate (~200 lines, copied from [transitions/shader-setup.md](transitions/shader-setup.md)) but produce richer, more cinematic effects. CSS transitions are simpler to set up. Choose based on the effect you want, not based on which is easier. +**Both are first-class options.** Shaders are provided by the `@hyperframes/shader-transitions` package — import from the package instead of writing raw GLSL. CSS transitions are simpler to set up. Choose based on the effect you want, not based on which is easier. When a composition uses shader transitions, ALL transitions in that composition should be shader-based (the WebGL canvas replaces DOM-based scene switching). Don't mix CSS and shader transitions in the same composition. diff --git a/skills/hyperframes/references/transitions/catalog.md b/skills/hyperframes/references/transitions/catalog.md index 1ea3ded62..4918b77bc 100644 --- a/skills/hyperframes/references/transitions/catalog.md +++ b/skills/hyperframes/references/transitions/catalog.md @@ -28,19 +28,9 @@ These cause real bugs if violated. **Don't use:** Star iris (polygon interpolation broken), tilt-shift (no selective CSS blur), lens flare (visible shape, not optical), hinge/door (distorts too fast). -## Hard Rules (Shader) - -Read [shader-setup.md](./shader-setup.md) for the full setup code these rules apply to. - -**WebGL setup:** `gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false)` — NOT true. Vertex shader flips Y: `v_uv.y = 1.0 - v_uv.y`. `preserveDrawingBuffer: true` required for HyperFrames capture. No `fwidth()` without extension — use constant `0.003`. - -**Rendering model:** DOM scenes play normally with GSAP animations during holds — canvas is hidden (`display:none`). When a transition starts: capture outgoing scene with full content, capture incoming scene with `.scene-content` hidden (background + decoratives only), show canvas, run shader. When transition ends: hide canvas, show next DOM scene. GSAP entrance animations play on the live DOM. The incoming scene's content is never visible in the shader — it only shows the background layer, preventing un-animated elements from flashing. - -**Scene capture:** Canvas `fillText` doesn't match CSS fonts exactly (known, not a bug). No CSS gradients or SVGs. Images and videos ARE supported via `ctx.drawImage()`. Video scenes re-capture every frame during transitions via `recaptureVideoScene()`. - -**Timeline:** Use `tl.call()` for begin/end — NOT `onStart`/`onComplete`. Each tween proxy `{p:0}` must be unique. Never boomerang (`u_progress*(1.-u_progress)*4.`). Morph both scenes. +## Shader Transitions -**Shader code:** One noise library per shader (NQ or ND, not both). Always `clamp(uv, 0., 1.)`. +Shader setup, WebGL init, capture, and fragment shaders are handled by `@hyperframes/shader-transitions` (`packages/shader-transitions/`). Read the package source for API details. Compositions using shaders must follow the CSS rules in [transitions.md](../transitions.md) § "Shader-Compatible CSS Rules". ## Scene Template @@ -124,9 +114,4 @@ All code examples use `old` for the outgoing scene-inner selector and `new` for ## Shader Transitions -WebGL fragment shaders that composite between scene textures per-pixel. Require setup boilerplate. - -| What | Reference | -| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------ | -| Setup (canvas, capture, WebGL init, render loop, GSAP integration) | [shader-setup.md](./shader-setup.md) | -| Fragment shaders (13 transitions: domain warp, ridged burn, whip pan, SDF iris, ripple waves, gravitational lens, cinematic zoom, chromatic split, glitch, swirl vortex, thermal distortion, cross-warp morph, light leak) | [shader-transitions.md](./shader-transitions.md) | +WebGL shader transitions are provided by `@hyperframes/shader-transitions` (`packages/shader-transitions/`). The package handles setup, capture, WebGL init, render loop, and GSAP integration. Read the package source for available shaders and API — do not copy raw GLSL manually. diff --git a/skills/hyperframes/references/transitions/shader-setup.md b/skills/hyperframes/references/transitions/shader-setup.md deleted file mode 100644 index e0dd8ccb3..000000000 --- a/skills/hyperframes/references/transitions/shader-setup.md +++ /dev/null @@ -1,282 +0,0 @@ -# Shader Transition Setup - -Complete boilerplate for WebGL shader transitions in HyperFrames. Copy the setup code, then plug in the fragment shader from the catalog. - -**Rendering model:** DOM scenes play normally with GSAP animations. The WebGL canvas is hidden (`display:none`) between transitions. When a transition starts, `beginTrans` uses html2canvas to capture the outgoing scene with full content, and the incoming scene with `.scene-content` hidden (background + decorative elements only). This prevents un-animated content from flashing during the transition. When the transition ends, `endTrans` hides the canvas and reveals the incoming DOM scene — GSAP entrance animations then play on live elements. - -**Shader-compatible CSS:** Compositions using shader transitions must follow the rules in transitions.md § "Shader-Compatible CSS Rules" — no `transparent` in gradients, no gradient backgrounds on sub-4px elements, no `var()` on captured elements, `data-no-capture` on uncapturable decoratives. - -## HTML - -```html - -``` - -## WebGL Init - -```js -var sceneTextures = {}; -var glCanvas = document.getElementById("gl-canvas"); -var gl = glCanvas.getContext("webgl", { preserveDrawingBuffer: true }); -gl.viewport(0, 0, 1920, 1080); -gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); -``` - -## Shader Compilation + Shared Constants - -```js -var vertSrc = - "attribute vec2 a_pos; varying vec2 v_uv; void main(){" + - "v_uv=a_pos*0.5+0.5; v_uv.y=1.0-v_uv.y; gl_Position=vec4(a_pos,0,1);}"; - -var quadBuf = gl.createBuffer(); -gl.bindBuffer(gl.ARRAY_BUFFER, quadBuf); -gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]), gl.STATIC_DRAW); - -function compileShader(src, type) { - var s = gl.createShader(type); - gl.shaderSource(s, src); - gl.compileShader(s); - if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) - console.error("Shader:", gl.getShaderInfoLog(s)); - return s; -} - -function mkProg(fragSrc) { - var p = gl.createProgram(); - gl.attachShader(p, compileShader(vertSrc, gl.VERTEX_SHADER)); - gl.attachShader(p, compileShader(fragSrc, gl.FRAGMENT_SHADER)); - gl.linkProgram(p); - if (!gl.getProgramParameter(p, gl.LINK_STATUS)) console.error("Link:", gl.getProgramInfoLog(p)); - return p; -} - -// Shared uniform header — every fragment shader starts with this -var H = - "precision mediump float;" + - "varying vec2 v_uv;" + - "uniform sampler2D u_from, u_to;" + - "uniform float u_progress;" + - "uniform vec2 u_resolution;\n"; -``` - -## Noise Libraries - -Include only what each shader needs. Do NOT include multiple libraries that redefine `hash()` in the same shader. - -```js -// Quintic C2 noise + inter-octave rotation FBM -var NQ = - "float hash(vec2 p){return fract(sin(dot(p,vec2(127.1,311.7)))*43758.5453);}" + - "float vnoise(vec2 p){vec2 i=floor(p),f=fract(p);" + - "f=f*f*f*(f*(f*6.-15.)+10.);" + // quintic interpolation — C2 continuous - "return mix(mix(hash(i),hash(i+vec2(1,0)),f.x)," + - "mix(hash(i+vec2(0,1)),hash(i+vec2(1,1)),f.x),f.y);}" + - "float fbm(vec2 p){float v=0.,a=.5;" + - "mat2 R=mat2(.8,.6,-.6,.8);" + // inter-octave rotation (~37deg) - "for(int i=0;i<5;i++){v+=a*vnoise(p);p=R*p*2.02;a*=.5;}return v;}"; - -// Noise with analytical derivatives (quintic) + erosion FBM -// Use for transitions that need gradient-based edge lighting -var ND = - "float hash(vec2 p){return fract(sin(dot(p,vec2(127.1,311.7)))*43758.5453);}" + - "vec3 noised(vec2 p){vec2 i=floor(p),f=fract(p);" + - "vec2 u=f*f*f*(f*(f*6.-15.)+10.),du=30.*f*f*(f*(f-2.)+1.);" + - "float a=hash(i),b=hash(i+vec2(1,0)),c=hash(i+vec2(0,1)),d=hash(i+vec2(1,1));" + - "return vec3(a+(b-a)*u.x+(c-a)*u.y+(a-b-c+d)*u.x*u.y," + - "du*vec2(b-a+(a-b-c+d)*u.y,c-a+(a-b-c+d)*u.x));}" + - "float erosionFBM(vec2 p){float v=0.,a=.5;vec2 d=vec2(0);mat2 R=mat2(.8,.6,-.6,.8);" + - "for(int i=0;i<6;i++){vec3 n=noised(p);d+=n.yz;v+=a*n.x/(1.+dot(d,d));p=R*p*2.02;a*=.5;}return v;}"; - -// Cosine palette: a + b*cos(2pi(c*t + d)) -var CP = "vec3 palette(float t,vec3 a,vec3 b,vec3 c,vec3 d){" + "return a+b*cos(6.2832*(c*t+d));}"; -``` - -## Render + State Machine - -DOM scenes play normally with GSAP animations during holds. The canvas is only visible during shader transitions — hidden the rest of the time. Capture uses html2canvas (loaded from CDN alongside GSAP). - -Add this script tag alongside GSAP: - -```html - -``` - -```js -// Patch createPattern for html2canvas bug with 0-dimension elements -var _origCP = CanvasRenderingContext2D.prototype.createPattern; -CanvasRenderingContext2D.prototype.createPattern = function (img, rep) { - if (img && (img.width === 0 || img.height === 0)) return null; - return _origCP.call(this, img, rep); -}; - -function uploadTexture(sceneId, canvas) { - if (!sceneTextures[sceneId]) { - var tex = gl.createTexture(); - gl.bindTexture(gl.TEXTURE_2D, tex); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); - sceneTextures[sceneId] = tex; - } - gl.bindTexture(gl.TEXTURE_2D, sceneTextures[sceneId]); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, canvas); -} - -// BG_COLOR must match your composition's background color (e.g. "#0a0a1a"). -// html2canvas backgroundColor: null means transparent, which renders as black -// in WebGL textures. Always pass the explicit color. -var BG_COLOR = "#000"; // ← set to your composition's background - -function captureScene(sceneEl) { - return html2canvas(sceneEl, { - width: 1920, - height: 1080, - scale: 1, - backgroundColor: BG_COLOR, - logging: false, - ignoreElements: function (el) { - return el.tagName === "CANVAS" || el.hasAttribute("data-no-capture"); - }, - }); -} - -function renderShader(prog, texFrom, texTo, progress) { - gl.useProgram(prog); - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, texFrom); - gl.uniform1i(gl.getUniformLocation(prog, "u_from"), 0); - gl.activeTexture(gl.TEXTURE1); - gl.bindTexture(gl.TEXTURE_2D, texTo); - gl.uniform1i(gl.getUniformLocation(prog, "u_to"), 1); - gl.uniform1f(gl.getUniformLocation(prog, "u_progress"), progress); - gl.uniform2f(gl.getUniformLocation(prog, "u_resolution"), 1920, 1080); - var pos = gl.getAttribLocation(prog, "a_pos"); - gl.bindBuffer(gl.ARRAY_BUFFER, quadBuf); - gl.enableVertexAttribArray(pos); - gl.vertexAttribPointer(pos, 2, gl.FLOAT, false, 0, 0); - gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); -} - -var trans = { - active: false, - prog: null, - fromId: null, - toId: null, - progress: 0, -}; - -function beginTrans(prog, fromId, toId) { - if (!gl) return; - var fromScene = document.getElementById(fromId); - var toScene = document.getElementById(toId); - - // Capture outgoing scene (DOM stays visible during async capture) - captureScene(fromScene) - .then(function (fromCanvas) { - uploadTexture(fromId, fromCanvas); - - // Show incoming scene BEHIND outgoing (z-index -1) for capture - toScene.style.zIndex = "-1"; - toScene.style.opacity = "1"; - var contentEl = toScene.querySelector(".scene-content"); - if (contentEl) contentEl.style.visibility = "hidden"; - - // Wait 2 rAFs for browser to render with correct fonts - return new Promise(function (resolve) { - requestAnimationFrame(function () { - requestAnimationFrame(function () { - captureScene(toScene).then(function (toCanvas) { - if (contentEl) contentEl.style.visibility = ""; - toScene.style.opacity = "0"; - toScene.style.zIndex = ""; - uploadTexture(toId, toCanvas); - resolve(); - }); - }); - }); - }); - }) - .then(function () { - // Both textures ready — swap DOM for canvas - document.querySelectorAll(".scene").forEach(function (s) { - s.style.opacity = "0"; - }); - glCanvas.style.display = "block"; - trans.prog = prog; - trans.fromId = fromId; - trans.toId = toId; - trans.progress = 0; - trans.active = true; - }); -} - -function updateTrans() { - if (!trans.active || !gl) return; - renderShader(trans.prog, sceneTextures[trans.fromId], sceneTextures[trans.toId], trans.progress); -} - -function endTrans(showId) { - trans.active = false; - glCanvas.style.display = "none"; - document.getElementById(showId).style.opacity = "1"; -} -``` - -## GSAP Timeline Integration - -Scene 1 starts visible on the DOM. GSAP animates elements normally. The canvas is hidden until a transition begins. After each transition, the canvas hides and the next scene's DOM takes over. - -```js -// Canvas starts hidden — DOM scene 1 is visible -glCanvas.style.display = "none"; - -var tl = gsap.timeline({ - paused: true, - onUpdate: function () { - updateTrans(); - }, -}); - -// Scene 1 entrance animations go here (normal GSAP on DOM)... - -// Transition 1→2: -tl.call( - function () { - beginTrans(myShaderProg, "scene1", "scene2"); - }, - null, - T, -); -var tw1 = { p: 0 }; -tl.to( - tw1, - { - p: 1, - duration: DUR, - ease: "power2.inOut", - onUpdate: function () { - trans.progress = tw1.p; - }, - }, - T, -); -tl.call( - function () { - endTrans("scene2"); - }, - null, - T + DUR, -); - -// Scene 2 entrance animations go here (normal GSAP on DOM)... - -window.__timelines["main"] = tl; -``` diff --git a/skills/hyperframes/references/transitions/shader-transitions.md b/skills/hyperframes/references/transitions/shader-transitions.md deleted file mode 100644 index 33c86510c..000000000 --- a/skills/hyperframes/references/transitions/shader-transitions.md +++ /dev/null @@ -1,329 +0,0 @@ -# Shader Transition Fragment Shaders - -Each shader below is just the GLSL fragment body. Plug it into `mkProg(H + NQ + "...")` using the setup from [shader-setup.md](./shader-setup.md). - -### Domain Warp Dissolve - -Cascaded `fbm(p + fbm(p))` — both scenes displace along the warp field in opposite directions. Iridescent cosine palette edge glow. - -```glsl -// Requires: NQ + CP -void main() { - vec2 q = vec2(fbm(v_uv * 3.), fbm(v_uv * 3. + vec2(5.2, 1.3))); - vec2 r = vec2(fbm(v_uv * 3. + q * 4. + vec2(1.7, 9.2)), - fbm(v_uv * 3. + q * 4. + vec2(8.3, 2.8))); - float n = fbm(v_uv * 3. + r * 2.); - vec2 warpDir = (q - .5) * .4; - vec4 A = texture2D(u_from, clamp(v_uv + warpDir * u_progress, 0., 1.)); - vec4 B = texture2D(u_to, clamp(v_uv - warpDir * (1. - u_progress), 0., 1.)); - float e = smoothstep(u_progress - .08, u_progress + .08, n); - float ed = abs(n - u_progress); - float em = smoothstep(.1, 0., ed) * (1. - step(1., u_progress)); - vec3 ec = palette(ed * 8., vec3(.5), vec3(.5), vec3(1.), vec3(0., .33, .67)); - gl_FragColor = vec4(mix(B, A, e).rgb + ec * em * 2., 1.); -} -``` - -### Ridged Burn - -`abs(noise)` creates sharp lightning-crack edges. Blackbody color gradient (dark red → orange → yellow → white) + high-frequency ember sparks near the edge. - -```glsl -// Requires: NQ -float ridged(vec2 p) { - float v = 0., a = .5; - mat2 R = mat2(.8, .6, -.6, .8); - for (int i = 0; i < 5; i++) { - v += a * abs(vnoise(p) * 2. - 1.); - p = R * p * 2.02; a *= .5; - } - return v; -} -void main() { - vec4 A = texture2D(u_from, v_uv), B = texture2D(u_to, v_uv); - float n = ridged(v_uv * 4.); - float e = smoothstep(u_progress - .04, u_progress + .04, n); - float heat = smoothstep(.12, 0., abs(n - u_progress)) * (1. - step(1., u_progress)); - vec3 burn = mix(vec3(.4, 0, 0), vec3(1, .4, 0), smoothstep(0., .25, heat)); - burn = mix(burn, vec3(1, .85, .3), smoothstep(.25, .5, heat)); - burn = mix(burn, vec3(1), smoothstep(.5, 1., heat)); - float sparks = step(.92, vnoise(v_uv * 80.)) * heat * 3.; - gl_FragColor = vec4(mix(B, A, e).rgb + burn * heat * 3.5 - + vec3(1., .7, .3) * sparks, 1.); -} -``` - -### Whip Pan - -Both scenes slide horizontally in opposite directions with 10-sample directional motion blur. No noise needed. - -```glsl -void main() { - float fromOff = u_progress * 1.5; - vec3 fromC = vec3(0.); - for (int i = 0; i < 10; i++) { - float f = float(i) / 10.; - vec2 fuv = vec2(v_uv.x + fromOff + u_progress * .08 * f, v_uv.y); - fromC += texture2D(u_from, clamp(fuv, 0., 1.)).rgb; - } - fromC /= 10.; - float toOff = (1. - u_progress) * 1.5; - vec3 toC = vec3(0.); - for (int i = 0; i < 10; i++) { - float f = float(i) / 10.; - vec2 tuv = vec2(v_uv.x - toOff - (1. - u_progress) * .08 * f, v_uv.y); - toC += texture2D(u_to, clamp(tuv, 0., 1.)).rgb; - } - toC /= 10.; - gl_FragColor = vec4(mix(fromC, toC, u_progress), 1.); -} -``` - -### SDF Iris - -Aspect-corrected circle SDF opening from center. Triple onion ring glow. - -```glsl -void main() { - vec4 A = texture2D(u_from, v_uv), B = texture2D(u_to, v_uv); - vec2 uv = (v_uv - .5) * vec2(u_resolution.x / u_resolution.y, 1.); - float d = length(uv); - float radius = u_progress * 1.2; - float fw = .003; - float edge = smoothstep(radius + fw, radius - fw, d); - float ring1 = exp(-abs(d - radius) * 25.); - float ring2 = exp(-abs(d - radius + .04) * 20.) * .5; - float ring3 = exp(-abs(d - radius + .08) * 15.) * .25; - float glow = (ring1 + ring2 + ring3) * u_progress * (1. - u_progress) * 4.; - gl_FragColor = vec4(mix(A, B, edge).rgb + vec3(1., .85, .6) * glow * .6, 1.); -} -``` - -### Ripple Waves - -Exponential sine waves (`exp(sin(x)-1)`) — sharp crests, broad troughs. Both scenes ripple in opposite phases. - -```glsl -void main() { - vec2 uv = v_uv - .5; - float dist = length(uv); - vec2 dir = normalize(uv + .001); - float fromAmp = u_progress * .04; - float fw1 = exp(sin(dist * 25. - u_progress * 12.) - 1.); - float fw2 = exp(sin(dist * 50. - u_progress * 18.) - 1.) * .5; - vec2 fromUv = clamp(v_uv + dir * (fw1 + fw2) * fromAmp, 0., 1.); - float toAmp = (1. - u_progress) * .04; - float tw1 = exp(sin(dist * 25. + u_progress * 12.) - 1.); - float tw2 = exp(sin(dist * 50. + u_progress * 18.) - 1.) * .5; - vec2 toUv = clamp(v_uv - dir * (tw1 + tw2) * toAmp, 0., 1.); - vec4 A = texture2D(u_from, fromUv); - vec4 B = texture2D(u_to, toUv); - float peak = fw1 * u_progress; - vec3 tint = vec3(.9, .95, 1.) * peak * .1; - gl_FragColor = vec4(mix(A.rgb + tint, B.rgb, u_progress), 1.); -} -``` - -### Gravitational Lens - -Content warps toward gravity well with chromatic aberration + event horizon darkening. Pull is monotonic — never reverses. - -```glsl -void main() { - vec4 B = texture2D(u_to, v_uv); - vec2 uv = v_uv - .5; - float dist = length(uv); - float pull = u_progress * 2.; - float warpStr = pull * .3 / (dist + .1); - vec2 warped = clamp(v_uv - uv * warpStr, 0., 1.); - vec4 A = texture2D(u_from, warped); - float horizon = smoothstep(0., .3, dist / (1. - u_progress * .85 + .001)); - float shift = pull * .02 / (dist + .2); - float r = texture2D(u_from, clamp(v_uv - uv * (warpStr + shift), 0., 1.)).r; - float b = texture2D(u_from, clamp(v_uv - uv * (warpStr - shift), 0., 1.)).b; - vec3 lensed = vec3(r, A.g, b) * horizon; - gl_FragColor = vec4(mix(lensed, B.rgb, smoothstep(.3, .9, u_progress)), 1.); -} -``` - -### Cinematic Zoom - -Both scenes zoom-blur in opposite directions with per-channel radial offset (chromatic aberration). From zooms outward, to zooms inward from tight. - -```glsl -void main() { - vec2 d = v_uv - vec2(.5); - float fromS = u_progress * .08; - float toS = (1. - u_progress) * .06; - float fr = 0., fg = 0., fb = 0.; - for (int i = 0; i < 12; i++) { - float f = float(i) / 12.; - fr += texture2D(u_from, v_uv - d * (fromS * 1.06) * f).r; - fg += texture2D(u_from, v_uv - d * fromS * f).g; - fb += texture2D(u_from, v_uv - d * (fromS * .94) * f).b; - } - vec3 fromBl = vec3(fr, fg, fb) / 12.; - float tr = 0., tg = 0., tb = 0.; - for (int i = 0; i < 12; i++) { - float f = float(i) / 12.; - tr += texture2D(u_to, v_uv + d * (toS * 1.06) * f).r; - tg += texture2D(u_to, v_uv + d * toS * f).g; - tb += texture2D(u_to, v_uv + d * (toS * .94) * f).b; - } - vec3 toBl = vec3(tr, tg, tb) / 12.; - gl_FragColor = vec4(mix(fromBl, toBl, u_progress), 1.); -} -``` - -### Chromatic Radial Split - -Both scenes' RGB channels separate/converge radially. From-scene splits outward, to-scene converges inward. - -```glsl -void main() { - vec2 c = v_uv - .5; - float fromShift = u_progress * .06; - float fr = texture2D(u_from, clamp(v_uv + c * fromShift, 0., 1.)).r; - float fg = texture2D(u_from, v_uv).g; - float fb = texture2D(u_from, clamp(v_uv - c * fromShift, 0., 1.)).b; - vec3 fromSplit = vec3(fr, fg, fb); - float toShift = (1. - u_progress) * .06; - float tr = texture2D(u_to, clamp(v_uv - c * toShift, 0., 1.)).r; - float tg = texture2D(u_to, v_uv).g; - float tb = texture2D(u_to, clamp(v_uv + c * toShift, 0., 1.)).b; - vec3 toSplit = vec3(tr, tg, tb); - gl_FragColor = vec4(mix(fromSplit, toSplit, u_progress), 1.); -} -``` - -### Glitch (shader) - -Scan lines + block scramble + chromatic aberration + brightness flicker + color posterization. More aggressive than the CSS glitch. - -```glsl -float rand(vec2 co) { - return fract(sin(dot(co, vec2(12.9898, 78.233))) * 43758.5453); -} -void main() { - float inten = u_progress * (1. - u_progress) * 4.; - float lineY = floor(v_uv.y * 60.) / 60.; - float lineDisp = (rand(vec2(lineY, floor(u_progress * 17.))) - .5) * .18 * inten; - vec2 block = floor(v_uv * vec2(12., 8.)); - float br = rand(block + vec2(floor(u_progress * 11.))); - float ba = step(.83, br) * inten; - vec2 bd = (vec2(rand(block * 2.1), rand(block * 3.7)) - .5) * .35 * ba; - vec2 uv = clamp(v_uv + vec2(lineDisp, 0.) + bd, 0., 1.); - float shift = inten * .035; - float r = texture2D(u_from, uv + vec2(shift, 0.)).r; - float g = texture2D(u_from, uv).g; - float b = texture2D(u_from, uv - vec2(shift, 0.)).b; - vec3 col = vec3(r, g, b); - col -= step(.5, fract(v_uv.y * u_resolution.y * .5)) * .05 * inten; - col *= 1. + (rand(vec2(floor(u_progress * 23.))) - .5) * .3 * inten; - float levels = mix(256., 8., inten * .5); - col = floor(col * levels) / levels; - gl_FragColor = mix(vec4(col, 1.), texture2D(u_to, v_uv), u_progress); -} -``` - -### Swirl Vortex - -Both scenes swirl in opposite directions with FBM-warped spiral path. - -```glsl -// Requires: NQ -void main() { - vec2 uv = v_uv - .5; - float dist = length(uv); - float warp = fbm(v_uv * 4.) * .5; - float fromAng = u_progress * (1. - dist) * 10. + warp * u_progress * 3.; - float fs = sin(fromAng), fc = cos(fromAng); - vec2 fromUv = clamp(vec2(uv.x*fc - uv.y*fs, uv.x*fs + uv.y*fc) + .5, 0., 1.); - float toAng = -(1. - u_progress) * (1. - dist) * 10. - - warp * (1. - u_progress) * 3.; - float ts = sin(toAng), tc = cos(toAng); - vec2 toUv = clamp(vec2(uv.x*tc - uv.y*ts, uv.x*ts + uv.y*tc) + .5, 0., 1.); - vec4 A = texture2D(u_from, fromUv); - vec4 B = texture2D(u_to, toUv); - gl_FragColor = mix(A, B, u_progress); -} -``` - -### Thermal Distortion - -FBM-driven heat shimmer rising from bottom. Warps both scenes with sine displacement modulated by noise. Slight warm overexposure haze. - -```glsl -// Requires: NQ -void main() { - float heat = u_progress * 1.5; - float yFade = smoothstep(1., 0., v_uv.y); - float shimmer = sin(v_uv.y * 40. + fbm(v_uv * 6.) * 8.) - * fbm(v_uv * 3. + vec2(0., u_progress * 2.)); - float dispX = shimmer * heat * .03 * yFade; - vec2 fromUv = clamp(v_uv + vec2(dispX, 0.), 0., 1.); - vec4 A = texture2D(u_from, fromUv); - float invShimmer = sin(v_uv.y * 40. + fbm(v_uv * 6. + 3.) * 8.) - * fbm(v_uv * 3. + vec2(3., u_progress * 2.)); - float dispX2 = invShimmer * (1. - u_progress) * .03 * yFade; - vec2 toUv = clamp(v_uv + vec2(dispX2, 0.), 0., 1.); - vec4 B = texture2D(u_to, toUv); - float haze = heat * yFade * .15 * (1. - u_progress); - gl_FragColor = vec4(mix(A.rgb, B.rgb, u_progress) - + vec3(1., .9, .7) * haze, 1.); -} -``` - -### Flash Through White - -Both scenes brighten to white midpoint. Works on dark backgrounds where color-dip-to-black is invisible. - -```glsl -void main() { - vec4 A = texture2D(u_from, v_uv), B = texture2D(u_to, v_uv); - float toWhite = smoothstep(0., .45, u_progress); - vec3 fromC = mix(A.rgb, vec3(1.), toWhite); - float fromWhite = 1. - smoothstep(.5, 1., u_progress); - vec3 toC = mix(B.rgb, vec3(1.), fromWhite); - gl_FragColor = vec4(mix(fromC, toC, smoothstep(.35, .65, u_progress)), 1.); -} -``` - -### Cross-Warp Morph - -Both scenes displace along a shared FBM noise field in opposite directions. Noise-driven blend boundary. Neither scene just sits there. - -```glsl -// Requires: NQ -void main() { - vec2 disp = vec2(fbm(v_uv * 3.), fbm(v_uv * 3. + vec2(7.3, 3.7))) - .5; - vec2 fromUv = clamp(v_uv + disp * u_progress * .5, 0., 1.); - vec2 toUv = clamp(v_uv - disp * (1. - u_progress) * .5, 0., 1.); - vec4 A = texture2D(u_from, fromUv); - vec4 B = texture2D(u_to, toUv); - float n = fbm(v_uv * 4. + vec2(3.1, 1.7)); - float blend = smoothstep(.4, .6, n + u_progress * 1.2 - .6); - gl_FragColor = mix(A, B, blend); -} -``` - -### Light Leak (shader) - -Beer-Lambert exponential falloff + ACES tone mapping + directional flare streak. More physically accurate than the CSS overlay version. - -```glsl -vec3 aces(vec3 x) { - return clamp((x * (2.51 * x + .03)) / (x * (2.43 * x + .59) + .14), 0., 1.); -} -void main() { - vec4 A = texture2D(u_from, v_uv), B = texture2D(u_to, v_uv); - vec2 lp = vec2(1.3, -.2); - float dist = length(v_uv - lp); - float leak = clamp(exp(-dist * 1.8) * u_progress * 4., 0., 1.); - vec3 warmColor = mix(vec3(1., .5, .15), vec3(1., .9, .75), dist * .7); - float flare = exp(-abs(v_uv.y - (-.2 + v_uv.x * .3)) * 15.) * leak * .3; - vec3 overexposed = A.rgb + warmColor * leak * 3. + vec3(1., .8, .5) * flare; - overexposed = aces(overexposed); - gl_FragColor = vec4(mix(overexposed, B.rgb, smoothstep(.15, .85, u_progress)), 1.); -} -``` From 6ca920990b1d5ac05a3ab15525d12b1567d0b72a Mon Sep 17 00:00:00 2001 From: Vance Ingalls Date: Tue, 14 Apr 2026 21:17:15 -0700 Subject: [PATCH 2/6] fix(skills): update broken marker-highlight.md references in captions.md Point to css-patterns.md instead of deleted marker-highlight.md. Co-Authored-By: Claude Opus 4.6 (1M context) --- skills/hyperframes/references/captions.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/skills/hyperframes/references/captions.md b/skills/hyperframes/references/captions.md index fc8885923..791d411f9 100644 --- a/skills/hyperframes/references/captions.md +++ b/skills/hyperframes/references/captions.md @@ -44,7 +44,7 @@ Scan for words deserving distinct treatment: - **Numbers/statistics** — bold weight, accent color - **Emotional keywords** — exaggerated animation (overshoot, bounce) - **Call-to-action** — highlight, underline, color pop -- **Marker highlight** — for beyond-color emphasis, see [marker-highlight.md](marker-highlight.md) +- **Marker highlight** — for beyond-color emphasis, see [css-patterns.md](css-patterns.md) ## Script-to-Style Mapping @@ -121,7 +121,7 @@ tl.seek(0); - [dynamic-techniques.md](dynamic-techniques.md) — karaoke, clip-path reveals, slam words, scatter exits, elastic, 3D rotation - [transcript-guide.md](transcript-guide.md) — transcription commands, whisper models, external APIs -- [marker-highlight.md](marker-highlight.md) — animated text emphasis paired with per-word styling +- [css-patterns.md](css-patterns.md) — CSS+GSAP marker highlighting (deterministic, fully seekable) ## Constraints From 3ad22313cfb6c5dbb520f90c5bd1c46fb274eb91 Mon Sep 17 00:00:00 2001 From: Vance Ingalls Date: Tue, 14 Apr 2026 21:22:36 -0700 Subject: [PATCH 3/6] fix(skills): update stale shader CSS rule to reference package API BG_COLOR was from the old manual setup. Now it's bgColor in the @hyperframes/shader-transitions init() config. Co-Authored-By: Claude Opus 4.6 (1M context) --- skills/hyperframes/references/transitions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skills/hyperframes/references/transitions.md b/skills/hyperframes/references/transitions.md index 76f498fe3..3b5404bba 100644 --- a/skills/hyperframes/references/transitions.md +++ b/skills/hyperframes/references/transitions.md @@ -103,7 +103,7 @@ Shader transitions capture DOM scenes to WebGL textures via html2canvas. The can 3. **No CSS variables (`var()`) on elements visible during capture.** html2canvas doesn't reliably resolve custom properties. Use literal color values in inline styles. 4. **Mark uncapturable decorative elements with `data-no-capture`.** The capture function skips these. They're present on the live DOM but absent from the shader texture. Use for elements that can't follow the rules above. 5. **No gradient opacity below 0.15.** Gradient elements below 10% opacity render differently in canvas vs CSS. Increase to 0.15+ or use a solid color at equivalent brightness. -6. **Every `.scene` div must have explicit `background-color`, AND set `BG_COLOR` in the shader setup to the same value.** html2canvas captures the scene element, not the body. Both the CSS `background-color` on `.scene` and the `backgroundColor` option in `html2canvas()` must be set to the composition's background color. Without either, the texture renders as black. +6. **Every `.scene` div must have explicit `background-color`, AND pass the same color as `bgColor` in the `init()` config.** The package captures scene elements via html2canvas. Both the CSS `background-color` on `.scene` and the `bgColor` config must match. Without either, the texture renders as black. These rules only apply to shader transition compositions. CSS-only compositions have no restrictions. From bb1f30d91bab85f26a90a6742a44a721852704bd Mon Sep 17 00:00:00 2001 From: Vance Ingalls Date: Tue, 14 Apr 2026 21:34:45 -0700 Subject: [PATCH 4/6] fix(skills): address 6 doc gaps surfaced by eval agents P0: Document HyperShader as IIFE global name in shader-transitions README P1: Replace async fetch() with sync XHR in effects.md audio data loading (fetch violates synchronous timeline construction rule in SKILL.md) P1: Change
to in css-patterns.md marker highlight patterns (
inside

is invalid HTML, breaks layout in inline contexts) P2: Clarify bgColor as fallback color in shader-transitions README P2: Add data-start to Composition Clips table in SKILL.md (root composition element needs data-start="0", linter enforces it) Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/shader-transitions/README.md | 26 +++---- skills/gsap/references/effects.md | 21 ++---- skills/hyperframes/SKILL.md | 1 + skills/hyperframes/references/css-patterns.md | 70 ++++++++++--------- 4 files changed, 57 insertions(+), 61 deletions(-) diff --git a/packages/shader-transitions/README.md b/packages/shader-transitions/README.md index 23420025f..050ca63f0 100644 --- a/packages/shader-transitions/README.md +++ b/packages/shader-transitions/README.md @@ -74,14 +74,14 @@ init({ ### `init(config): GsapTimeline` -| Option | Type | Required | Description | -| --------------- | -------------------- | -------- | ------------------------------------------------------------ | -| `bgColor` | `string` | yes | Background color (hex) for scene capture | -| `accentColor` | `string` | no | Accent color (hex) for shader glow effects | -| `scenes` | `string[]` | yes | Element IDs of each scene, in order | -| `transitions` | `TransitionConfig[]` | yes | Transition definitions (see below) | -| `timeline` | `GsapTimeline` | no | Existing timeline to attach transitions to | -| `compositionId` | `string` | no | Override the `data-composition-id` for timeline registration | +| Option | Type | Required | Description | +| --------------- | -------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `bgColor` | `string` | yes | Fallback background color (hex) for scene capture. Use the composition's body/canvas background — individual scenes set their own `background-color` via CSS. | +| `accentColor` | `string` | no | Accent color (hex) for shader glow effects | +| `scenes` | `string[]` | yes | Element IDs of each scene, in order | +| `transitions` | `TransitionConfig[]` | yes | Transition definitions (see below) | +| `timeline` | `GsapTimeline` | no | Existing timeline to attach transitions to | +| `compositionId` | `string` | no | Override the `data-composition-id` for timeline registration | ### `TransitionConfig` @@ -103,11 +103,11 @@ import { SHADER_NAMES } from "@hyperframes/shader-transitions"; ## Distribution -| Format | File | Use case | -| ------ | ---------------------- | ------------------------------ | -| ESM | `dist/index.js` | Bundlers (Vite, webpack, etc.) | -| CJS | `dist/index.cjs` | Node.js / require() | -| IIFE | `dist/index.global.js` | `