mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-11 14:50:02 +00:00
chore(skills): remove 1,685 lines of redundant skill content (#283)
* chore(skills): remove 1,685 lines of redundant and irrelevant skill content - 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) <noreply@anthropic.com> * 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) <noreply@anthropic.com> * 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) <noreply@anthropic.com> * 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 <div> to <span> in css-patterns.md marker highlight patterns (<div> inside <p> 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) <noreply@anthropic.com> * fix(templates): update init templates to match trimmed skill scope - Remove ScrollTrigger/plugins/React/Vue/Svelte from gsap skill description - Replace class="clip" with accurate pattern examples in skill intro text (class="clip" is still in Key Rules where it belongs) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(skills): remove contradictory 5:1 contrast threshold from house-style house-style.md said 5:1 minimum, but hyperframes validate enforces WCAG AA (4.5:1 normal text, 3:1 large text). Now defers to validate instead of stating a conflicting number. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
acf8223171
commit
a262ad59f3
@@ -1,40 +1,5 @@
|
|||||||
# Hyperframes
|
# 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 <agent>` for other targets.
|
|
||||||
|
|
||||||
## Project Overview
|
|
||||||
|
|
||||||
Open-source video rendering framework: write HTML, render video.
|
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.
|
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 <name> --help` (examples appear).
|
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 <name> --help` (examples appear).
|
||||||
|
|
||||||
## Key Concepts
|
## Skills
|
||||||
|
|
||||||
- **Compositions** are HTML files with `data-*` attributes defining timeline, tracks, and media
|
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.
|
||||||
- **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.
|
|
||||||
|
|
||||||
### 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 `<hyperframes-player>` web component for embedding
|
|
||||||
compositions in any web page. Zero dependencies, works with any framework.
|
|
||||||
|
|
||||||
### Quick reference
|
|
||||||
|
|
||||||
```html
|
|
||||||
<!-- Load the player (CDN or npm) -->
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/@hyperframes/player"></script>
|
|
||||||
|
|
||||||
<!-- Embed a composition -->
|
|
||||||
<hyperframes-player src="./my-composition/index.html" controls></hyperframes-player>
|
|
||||||
```
|
|
||||||
|
|
||||||
### 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));
|
|
||||||
```
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ npx skills add heygen-com/hyperframes # HyperFrames composition skills
|
|||||||
npx skills add greensock/gsap-skills # GSAP animation skills
|
npx skills add greensock/gsap-skills # GSAP animation skills
|
||||||
```
|
```
|
||||||
|
|
||||||
Skills encode patterns like `class="clip"` on timed elements, `window.__timelines` registration, and `data-*` attribute semantics that are not in generic web docs. Using them produces correct compositions from the start.
|
Skills encode patterns like `window.__timelines` registration, `data-*` attribute semantics, and shader-compatible CSS rules that are not in generic web docs. Using them produces correct compositions from the start.
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
|
|||||||
@@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
## Skills — USE THESE FIRST
|
## Skills — USE THESE FIRST
|
||||||
|
|
||||||
**Always invoke the relevant skill before writing or modifying compositions.** Skills encode framework-specific patterns (e.g., `class="clip"`, `window.__timelines`, `data-*` attributes) that are NOT in generic web docs. Skipping them produces broken compositions.
|
**Always invoke the relevant skill before writing or modifying compositions.** Skills encode framework-specific patterns (e.g., `window.__timelines` registration, `data-*` attribute semantics, shader-compatible CSS rules) that are NOT in generic web docs. Skipping them produces broken compositions.
|
||||||
|
|
||||||
| Skill | Command | When to use |
|
| Skill | Command | When to use |
|
||||||
| ------------------- | ------------------ | --------------------------------------------------------------------------------------------------------------- |
|
| ------------------- | ------------------ | ------------------------------------------------------------------------------------------------- |
|
||||||
| **hyperframes** | `/hyperframes` | Creating or editing HTML compositions, captions, TTS, audio-reactive animation, marker highlights |
|
| **hyperframes** | `/hyperframes` | Creating or editing HTML compositions, captions, TTS, audio-reactive animation, marker highlights |
|
||||||
| **hyperframes-cli** | `/hyperframes-cli` | CLI commands: init, lint, preview, render, transcribe, tts |
|
| **hyperframes-cli** | `/hyperframes-cli` | CLI commands: init, lint, preview, render, transcribe, tts |
|
||||||
| **gsap** | `/gsap` | GSAP animations — tweens, timelines, easing, ScrollTrigger, plugins, React/Vue/Svelte, performance optimization |
|
| **gsap** | `/gsap` | GSAP animations for HyperFrames — tweens, timelines, easing, performance |
|
||||||
|
|
||||||
> **Skills not available?** Ask the user to run `npx hyperframes skills` and restart their
|
> **Skills not available?** Ask the user to run `npx hyperframes skills` and restart their
|
||||||
> agent session, or install manually: `npx skills add heygen-com/hyperframes` and
|
> agent session, or install manually: `npx skills add heygen-com/hyperframes` and
|
||||||
|
|||||||
@@ -74,14 +74,14 @@ init({
|
|||||||
|
|
||||||
### `init(config): GsapTimeline`
|
### `init(config): GsapTimeline`
|
||||||
|
|
||||||
| Option | Type | Required | Description |
|
| Option | Type | Required | Description |
|
||||||
| --------------- | -------------------- | -------- | ------------------------------------------------------------ |
|
| --------------- | -------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `bgColor` | `string` | yes | Background color (hex) for scene capture |
|
| `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 |
|
| `accentColor` | `string` | no | Accent color (hex) for shader glow effects |
|
||||||
| `scenes` | `string[]` | yes | Element IDs of each scene, in order |
|
| `scenes` | `string[]` | yes | Element IDs of each scene, in order |
|
||||||
| `transitions` | `TransitionConfig[]` | yes | Transition definitions (see below) |
|
| `transitions` | `TransitionConfig[]` | yes | Transition definitions (see below) |
|
||||||
| `timeline` | `GsapTimeline` | no | Existing timeline to attach transitions to |
|
| `timeline` | `GsapTimeline` | no | Existing timeline to attach transitions to |
|
||||||
| `compositionId` | `string` | no | Override the `data-composition-id` for timeline registration |
|
| `compositionId` | `string` | no | Override the `data-composition-id` for timeline registration |
|
||||||
|
|
||||||
### `TransitionConfig`
|
### `TransitionConfig`
|
||||||
|
|
||||||
@@ -103,11 +103,11 @@ import { SHADER_NAMES } from "@hyperframes/shader-transitions";
|
|||||||
|
|
||||||
## Distribution
|
## Distribution
|
||||||
|
|
||||||
| Format | File | Use case |
|
| Format | File | Use case |
|
||||||
| ------ | ---------------------- | ------------------------------ |
|
| ------ | ---------------------- | ------------------------------------------- |
|
||||||
| ESM | `dist/index.js` | Bundlers (Vite, webpack, etc.) |
|
| ESM | `dist/index.js` | Bundlers (Vite, webpack, etc.) |
|
||||||
| CJS | `dist/index.cjs` | Node.js / require() |
|
| CJS | `dist/index.cjs` | Node.js / require() |
|
||||||
| IIFE | `dist/index.global.js` | `<script>` tag, CDN |
|
| IIFE | `dist/index.global.js` | `<script>` tag, CDN (global: `HyperShader`) |
|
||||||
|
|
||||||
All formats include source maps. TypeScript definitions included.
|
All formats include source maps. TypeScript definitions included.
|
||||||
|
|
||||||
|
|||||||
+5
-16
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: gsap
|
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
|
# GSAP
|
||||||
@@ -57,7 +57,7 @@ gsap.to(".item", {
|
|||||||
|
|
||||||
## Easing
|
## 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
|
## Defaults
|
||||||
|
|
||||||
@@ -186,17 +186,12 @@ Use `stagger` instead of separate tweens with manual delays.
|
|||||||
|
|
||||||
### Cleanup
|
### Cleanup
|
||||||
|
|
||||||
Pause or kill off-screen animations. In frameworks, revert context on unmount.
|
Pause or kill off-screen animations.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## References (loaded on demand)
|
## 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.
|
- **[references/effects.md](references/effects.md)** — Drop-in effects: typewriter text, audio visualizer. Read when needing ready-made effect patterns for HyperFrames.
|
||||||
|
|
||||||
## Best Practices
|
## 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.
|
- Prefer timelines over chaining with delay; use the position parameter.
|
||||||
- Add labels with `addLabel()` for readable sequencing.
|
- Add labels with `addLabel()` for readable sequencing.
|
||||||
- Pass defaults into timeline constructor.
|
- Pass defaults into timeline constructor.
|
||||||
- Use gsap.matchMedia() for responsive breakpoints and prefers-reduced-motion.
|
|
||||||
- Store tween/timeline return value when controlling playback.
|
- Store tween/timeline return value when controlling playback.
|
||||||
- Register every plugin with `gsap.registerPlugin()` before use.
|
|
||||||
|
|
||||||
## Do Not
|
## Do Not
|
||||||
|
|
||||||
- Animate layout properties (width/height/top/left) when transforms suffice.
|
- Animate layout properties (width/height/top/left) when transforms suffice.
|
||||||
- Use both svgOrigin and transformOrigin on the same SVG element.
|
- Use both svgOrigin and transformOrigin on the same SVG element.
|
||||||
- Chain animations with delay when a timeline can sequence them.
|
- 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.
|
- Create tweens before the DOM exists.
|
||||||
- Nest ScrollTriggered animations inside a parent timeline.
|
- Skip cleanup — always kill tweens when no longer needed.
|
||||||
- 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.
|
|
||||||
|
|||||||
@@ -210,25 +210,18 @@ Requires ffmpeg and numpy.
|
|||||||
|
|
||||||
```js
|
```js
|
||||||
// Option A: inline (small files, under ~500KB)
|
// Option A: inline (small files, under ~500KB)
|
||||||
const AUDIO_DATA = {
|
var AUDIO_DATA = {
|
||||||
/* paste audio-data.json contents */
|
/* paste audio-data.json contents */
|
||||||
};
|
};
|
||||||
setupTimeline(AUDIO_DATA);
|
|
||||||
|
|
||||||
// Option B: fetch (large files)
|
// Option B: sync XHR (large files — must be synchronous for deterministic timeline construction)
|
||||||
fetch("audio-data.json")
|
var xhr = new XMLHttpRequest();
|
||||||
.then((r) => r.json())
|
xhr.open("GET", "audio-data.json", false);
|
||||||
.then((data) => setupTimeline(data));
|
xhr.send();
|
||||||
|
var AUDIO_DATA = JSON.parse(xhr.responseText);
|
||||||
function setupTimeline(AUDIO_DATA) {
|
|
||||||
// IMPORTANT: all tl.call() setup must be inside this callback
|
|
||||||
for (let f = 0; f < AUDIO_DATA.totalFrames; f++) {
|
|
||||||
tl.call(() => draw(AUDIO_DATA.frames[f]), [], f / AUDIO_DATA.fps);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
With fetch, wrap all timeline setup inside the callback so `AUDIO_DATA` is available.
|
**Do NOT use async `fetch()` to load audio data.** HyperFrames requires synchronous timeline construction — the capture engine reads `window.__timelines` synchronously after page load. Building timelines inside `.then()` callbacks means the timeline isn't ready when capture starts.
|
||||||
|
|
||||||
### Rendering Approaches
|
### Rendering Approaches
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
|
||||||
@@ -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.
|
|
||||||
@@ -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.
|
|
||||||
@@ -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.
|
|
||||||
@@ -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.
|
|
||||||
@@ -111,6 +111,7 @@ Layered effects (glow behind text, shadow elements, background patterns) and z-s
|
|||||||
| Attribute | Required | Values |
|
| Attribute | Required | Values |
|
||||||
| ---------------------------- | -------- | -------------------------------------------- |
|
| ---------------------------- | -------- | -------------------------------------------- |
|
||||||
| `data-composition-id` | Yes | Unique composition ID |
|
| `data-composition-id` | Yes | Unique composition ID |
|
||||||
|
| `data-start` | Yes | Start time (root composition: use `"0"`) |
|
||||||
| `data-duration` | Yes | Takes precedence over GSAP timeline duration |
|
| `data-duration` | Yes | Takes precedence over GSAP timeline duration |
|
||||||
| `data-width` / `data-height` | Yes | Pixel dimensions (1920x1080 or 1080x1920) |
|
| `data-width` / `data-height` | Yes | Pixel dimensions (1920x1080 or 1080x1920) |
|
||||||
| `data-composition-src` | No | Path to external HTML file |
|
| `data-composition-src` | No | Path to external HTML file |
|
||||||
@@ -310,7 +311,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/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/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/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/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.
|
- **[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.
|
- **[house-style.md](house-style.md)** — Default motion, sizing, and color palettes when no style is specified.
|
||||||
@@ -321,7 +322,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.
|
- **[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/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.
|
- Shader transitions are in `@hyperframes/shader-transitions` (`packages/shader-transitions/`) — read package source, not skill files.
|
||||||
- [transitions/shader-transitions.md](references/transitions/shader-transitions.md) — 14 fragment shaders.
|
|
||||||
|
|
||||||
GSAP patterns and effects are in the `/gsap` skill.
|
GSAP patterns and effects are in the `/gsap` skill.
|
||||||
|
|||||||
@@ -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)
|
- Pure `#000` or `#fff` (tint toward your accent hue instead)
|
||||||
- Identical card grids (same-size cards repeated)
|
- Identical card grids (same-size cards repeated)
|
||||||
- Everything centered with equal weight (lead the eye somewhere)
|
- 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.
|
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.
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ If the content genuinely calls for one of these — centered layout for a solemn
|
|||||||
- Match light/dark to content: food, wellness, kids → light. Tech, cinema, finance → dark.
|
- Match light/dark to content: food, wellness, kids → light. Tech, cinema, finance → dark.
|
||||||
- One accent hue. Same background across all scenes.
|
- One accent hue. Same background across all scenes.
|
||||||
- Tint neutrals toward your accent (even subtle warmth/coolness beats dead gray).
|
- Tint neutrals toward your accent (even subtle warmth/coolness beats dead gray).
|
||||||
- **Contrast:** 5:1 minimum between text and scene background. Text must be readable with decoratives removed.
|
- **Contrast:** enforced by `hyperframes validate` (WCAG AA). Text must be readable with decoratives removed.
|
||||||
- Declare palette up front. Don't invent colors per-element.
|
- Declare palette up front. Don't invent colors per-element.
|
||||||
|
|
||||||
## Background Layer
|
## Background Layer
|
||||||
@@ -46,17 +46,11 @@ All decoratives should have slow ambient GSAP animation — breathing, drift, pu
|
|||||||
|
|
||||||
## Motion
|
## Motion
|
||||||
|
|
||||||
- **0.3–0.6s** for most moves.
|
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.
|
||||||
- **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.
|
|
||||||
|
|
||||||
## Typography
|
## Typography
|
||||||
|
|
||||||
- **Weight contrast** — 700-900 headlines with 300-400 body.
|
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.
|
||||||
- **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.
|
|
||||||
|
|
||||||
## Palettes
|
## Palettes
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ Scan for words deserving distinct treatment:
|
|||||||
- **Numbers/statistics** — bold weight, accent color
|
- **Numbers/statistics** — bold weight, accent color
|
||||||
- **Emotional keywords** — exaggerated animation (overshoot, bounce)
|
- **Emotional keywords** — exaggerated animation (overshoot, bounce)
|
||||||
- **Call-to-action** — highlight, underline, color pop
|
- **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
|
## 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
|
- [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
|
- [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
|
## Constraints
|
||||||
|
|
||||||
|
|||||||
@@ -15,16 +15,16 @@ Pure CSS + GSAP implementations of all five MarkerHighlight.js drawing modes. Us
|
|||||||
Yellow marker sweep behind text. The most common mode.
|
Yellow marker sweep behind text. The most common mode.
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<div class="mh-highlight-wrap">
|
<span class="mh-highlight-wrap">
|
||||||
<div class="mh-highlight-bar" id="hl-1"></div>
|
<span class="mh-highlight-bar" id="hl-1"></span>
|
||||||
<span class="mh-highlight-text">highlighted text</span>
|
<span class="mh-highlight-text">highlighted text</span>
|
||||||
</div>
|
</span>
|
||||||
```
|
```
|
||||||
|
|
||||||
```css
|
```css
|
||||||
.mh-highlight-wrap {
|
.mh-highlight-wrap {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: inline-block;
|
display: inline;
|
||||||
}
|
}
|
||||||
.mh-highlight-bar {
|
.mh-highlight-bar {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -75,16 +75,16 @@ tl.to(
|
|||||||
Hand-drawn circle around text. Use `border-radius: 50%` with a slight rotation for organic feel.
|
Hand-drawn circle around text. Use `border-radius: 50%` with a slight rotation for organic feel.
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<div class="mh-circle-wrap">
|
<span class="mh-circle-wrap">
|
||||||
<span class="mh-circle-text" id="circle-word">IMPORTANT</span>
|
<span class="mh-circle-text" id="circle-word">IMPORTANT</span>
|
||||||
<div class="mh-circle-ring" id="circle-1"></div>
|
<span class="mh-circle-ring" id="circle-1"></span>
|
||||||
</div>
|
</span>
|
||||||
```
|
```
|
||||||
|
|
||||||
```css
|
```css
|
||||||
.mh-circle-wrap {
|
.mh-circle-wrap {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: inline-block;
|
display: inline;
|
||||||
}
|
}
|
||||||
.mh-circle-text {
|
.mh-circle-text {
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -148,29 +148,29 @@ tl.to(
|
|||||||
Radiating lines from text center. Each line is a positioned div rotated to its angle.
|
Radiating lines from text center. Each line is a positioned div rotated to its angle.
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<div class="mh-burst-wrap">
|
<span class="mh-burst-wrap">
|
||||||
<span class="mh-burst-text">WOW</span>
|
<span class="mh-burst-text">WOW</span>
|
||||||
<div class="mh-burst-container" id="burst-1">
|
<span class="mh-burst-container" id="burst-1">
|
||||||
<div class="mh-burst-line" style="--angle: 0deg; --len: 70px;"></div>
|
<span class="mh-burst-line" style="--angle: 0deg; --len: 70px;"></span>
|
||||||
<div class="mh-burst-line" style="--angle: 30deg; --len: 55px;"></div>
|
<span class="mh-burst-line" style="--angle: 30deg; --len: 55px;"></span>
|
||||||
<div class="mh-burst-line" style="--angle: 60deg; --len: 80px;"></div>
|
<span class="mh-burst-line" style="--angle: 60deg; --len: 80px;"></span>
|
||||||
<div class="mh-burst-line" style="--angle: 90deg; --len: 45px;"></div>
|
<span class="mh-burst-line" style="--angle: 90deg; --len: 45px;"></span>
|
||||||
<div class="mh-burst-line" style="--angle: 120deg; --len: 65px;"></div>
|
<span class="mh-burst-line" style="--angle: 120deg; --len: 65px;"></span>
|
||||||
<div class="mh-burst-line" style="--angle: 150deg; --len: 75px;"></div>
|
<span class="mh-burst-line" style="--angle: 150deg; --len: 75px;"></span>
|
||||||
<div class="mh-burst-line" style="--angle: 180deg; --len: 50px;"></div>
|
<span class="mh-burst-line" style="--angle: 180deg; --len: 50px;"></span>
|
||||||
<div class="mh-burst-line" style="--angle: 210deg; --len: 60px;"></div>
|
<span class="mh-burst-line" style="--angle: 210deg; --len: 60px;"></span>
|
||||||
<div class="mh-burst-line" style="--angle: 240deg; --len: 80px;"></div>
|
<span class="mh-burst-line" style="--angle: 240deg; --len: 80px;"></span>
|
||||||
<div class="mh-burst-line" style="--angle: 270deg; --len: 40px;"></div>
|
<span class="mh-burst-line" style="--angle: 270deg; --len: 40px;"></span>
|
||||||
<div class="mh-burst-line" style="--angle: 300deg; --len: 70px;"></div>
|
<span class="mh-burst-line" style="--angle: 300deg; --len: 70px;"></span>
|
||||||
<div class="mh-burst-line" style="--angle: 330deg; --len: 55px;"></div>
|
<span class="mh-burst-line" style="--angle: 330deg; --len: 55px;"></span>
|
||||||
</div>
|
</span>
|
||||||
</div>
|
</span>
|
||||||
```
|
```
|
||||||
|
|
||||||
```css
|
```css
|
||||||
.mh-burst-wrap {
|
.mh-burst-wrap {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: inline-block;
|
display: inline;
|
||||||
}
|
}
|
||||||
.mh-burst-text {
|
.mh-burst-text {
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -186,6 +186,7 @@ Radiating lines from text center. Each line is a positioned div rotated to its a
|
|||||||
}
|
}
|
||||||
.mh-burst-line {
|
.mh-burst-line {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
display: block;
|
||||||
width: 3px;
|
width: 3px;
|
||||||
height: var(--len);
|
height: var(--len);
|
||||||
background: #1e88e5;
|
background: #1e88e5;
|
||||||
@@ -214,7 +215,7 @@ tl.fromTo(
|
|||||||
Wavy SVG underlines and strikethroughs that draw themselves via `stroke-dashoffset`.
|
Wavy SVG underlines and strikethroughs that draw themselves via `stroke-dashoffset`.
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<div class="mh-scribble-wrap">
|
<span class="mh-scribble-wrap">
|
||||||
<span class="mh-scribble-text">underlined text</span>
|
<span class="mh-scribble-text">underlined text</span>
|
||||||
<svg class="mh-scribble-svg" viewBox="0 0 500 24" preserveAspectRatio="none">
|
<svg class="mh-scribble-svg" viewBox="0 0 500 24" preserveAspectRatio="none">
|
||||||
<path
|
<path
|
||||||
@@ -232,7 +233,7 @@ Wavy SVG underlines and strikethroughs that draw themselves via `stroke-dashoffs
|
|||||||
```css
|
```css
|
||||||
.mh-scribble-wrap {
|
.mh-scribble-wrap {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: inline-block;
|
display: inline;
|
||||||
}
|
}
|
||||||
.mh-scribble-text {
|
.mh-scribble-text {
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -283,19 +284,19 @@ Scale the path's viewBox width to match text width. The wave pattern `Q x1,y1 x2
|
|||||||
Cross-hatch lines over de-emphasized text. Multiple angled lines create a "crossed out" effect.
|
Cross-hatch lines over de-emphasized text. Multiple angled lines create a "crossed out" effect.
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<div class="mh-sketchout-wrap">
|
<span class="mh-sketchout-wrap">
|
||||||
<span class="mh-sketchout-text">old price</span>
|
<span class="mh-sketchout-text">old price</span>
|
||||||
<div class="mh-sketchout-lines" id="sketchout-1">
|
<span class="mh-sketchout-lines" id="sketchout-1">
|
||||||
<div class="mh-sketchout-line mh-sketchout-fwd"></div>
|
<span class="mh-sketchout-line mh-sketchout-fwd"></span>
|
||||||
<div class="mh-sketchout-line mh-sketchout-bwd"></div>
|
<span class="mh-sketchout-line mh-sketchout-bwd"></span>
|
||||||
</div>
|
</span>
|
||||||
</div>
|
</span>
|
||||||
```
|
```
|
||||||
|
|
||||||
```css
|
```css
|
||||||
.mh-sketchout-wrap {
|
.mh-sketchout-wrap {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: inline-block;
|
display: inline;
|
||||||
}
|
}
|
||||||
.mh-sketchout-text {
|
.mh-sketchout-text {
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -312,6 +313,7 @@ Cross-hatch lines over de-emphasized text. Multiple angled lines create a "cross
|
|||||||
}
|
}
|
||||||
.mh-sketchout-line {
|
.mh-sketchout-line {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
display: block;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -1,146 +0,0 @@
|
|||||||
# Marker Highlight Examples
|
|
||||||
|
|
||||||
## Recipes
|
|
||||||
|
|
||||||
### Underline
|
|
||||||
|
|
||||||
```html
|
|
||||||
<mark
|
|
||||||
data-height="0.15"
|
|
||||||
data-offset="0.8"
|
|
||||||
data-padding="0"
|
|
||||||
data-highlight='{"amplitude":0.2,"wavelength":5,"roughEnds":0}'
|
|
||||||
data-color="rgba(30, 136, 229, 0.6)"
|
|
||||||
>important</mark
|
|
||||||
>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Strikethrough
|
|
||||||
|
|
||||||
```html
|
|
||||||
<mark
|
|
||||||
data-drawing-mode="highlight"
|
|
||||||
data-height="0.1"
|
|
||||||
data-offset="0"
|
|
||||||
data-highlight='{"amplitude":0.1,"wavelength":3}'
|
|
||||||
data-color="rgba(229, 57, 53, 0.8)"
|
|
||||||
>wrong answer</mark
|
|
||||||
>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Circled Annotation
|
|
||||||
|
|
||||||
```html
|
|
||||||
<mark
|
|
||||||
data-drawing-mode="circle"
|
|
||||||
data-circle='{"curve":0.8,"wobble":0.4,"loops":2,"thickness":3}'
|
|
||||||
data-animation-speed="1200"
|
|
||||||
data-color="rgba(229, 57, 53, 0.6)"
|
|
||||||
>this one</mark
|
|
||||||
>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Full Example in a Composition
|
|
||||||
|
|
||||||
```html
|
|
||||||
<div data-composition-id="highlight-demo" data-width="1920" data-height="1080">
|
|
||||||
<div
|
|
||||||
id="content"
|
|
||||||
style="
|
|
||||||
position: absolute; inset: 0;
|
|
||||||
display: flex; align-items: center; justify-content: center;
|
|
||||||
font-family: 'Bricolage Grotesque', sans-serif; font-size: 72px; color: #fff;
|
|
||||||
background: #111;
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<p id="hero">
|
|
||||||
The <mark id="m1" data-color="rgba(255, 220, 50, 0.5)">fastest</mark> way to
|
|
||||||
<mark
|
|
||||||
id="m2"
|
|
||||||
data-drawing-mode="circle"
|
|
||||||
data-circle='{"curve":0.8,"wobble":0.3,"loops":2,"thickness":3}'
|
|
||||||
data-color="rgba(229, 57, 53, 0.6)"
|
|
||||||
>ship</mark
|
|
||||||
>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
[data-composition-id="highlight-demo"] mark {
|
|
||||||
background-color: transparent;
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
|
||||||
<script src="marker-highlight.global.js"></script>
|
|
||||||
<script>
|
|
||||||
window.__timelines = window.__timelines || {};
|
|
||||||
var tl = gsap.timeline({ paused: true });
|
|
||||||
|
|
||||||
// Set colors via data attribute (no visible flash)
|
|
||||||
document.querySelectorAll("mark[data-color]").forEach(function (m) {
|
|
||||||
m.setAttribute("data-original-bgcolor", m.getAttribute("data-color"));
|
|
||||||
});
|
|
||||||
|
|
||||||
// Init once after fonts, then hide all canvases
|
|
||||||
var hl;
|
|
||||||
document.fonts.ready.then(function () {
|
|
||||||
setTimeout(function () {
|
|
||||||
hl = new MarkerHighlighter(document.getElementById("hero"), {
|
|
||||||
animate: false,
|
|
||||||
animationSpeed: 800,
|
|
||||||
padding: 0.3,
|
|
||||||
highlight: { amplitude: 0.3, wavelength: 5 },
|
|
||||||
});
|
|
||||||
setTimeout(function () {
|
|
||||||
document.querySelectorAll(".highlight").forEach(function (d) {
|
|
||||||
d.style.opacity = "0";
|
|
||||||
});
|
|
||||||
}, 100);
|
|
||||||
}, 50);
|
|
||||||
});
|
|
||||||
|
|
||||||
function addHighlight(markId, time) {
|
|
||||||
tl.to(
|
|
||||||
{},
|
|
||||||
{
|
|
||||||
duration: 0.001,
|
|
||||||
onStart: function () {
|
|
||||||
var mark = document.getElementById(markId);
|
|
||||||
var ref = mark.getAttribute("data-mark-ref");
|
|
||||||
if (!ref || !hl) return;
|
|
||||||
mark.parentElement
|
|
||||||
.querySelectorAll('.highlight[data-mark-id="' + ref + '"]')
|
|
||||||
.forEach(function (div) {
|
|
||||||
var c = div.querySelector("canvas");
|
|
||||||
if (c) c.getContext("2d").clearRect(0, 0, c.width, c.height);
|
|
||||||
div.style.opacity = "1";
|
|
||||||
});
|
|
||||||
hl.reanimateMark(mark);
|
|
||||||
},
|
|
||||||
onReverseComplete: function () {
|
|
||||||
var mark = document.getElementById(markId);
|
|
||||||
var ref = mark.getAttribute("data-mark-ref");
|
|
||||||
if (!ref) return;
|
|
||||||
mark.parentElement
|
|
||||||
.querySelectorAll('.highlight[data-mark-id="' + ref + '"]')
|
|
||||||
.forEach(function (div) {
|
|
||||||
div.style.opacity = "0";
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
time,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
gsap.set("#hero", { opacity: 0 });
|
|
||||||
tl.to("#hero", { opacity: 1, duration: 0.6 }, 0);
|
|
||||||
|
|
||||||
addHighlight("m1", 0.8);
|
|
||||||
addHighlight("m2", 1.6);
|
|
||||||
|
|
||||||
window.__timelines["highlight-demo"] = tl;
|
|
||||||
</script>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
@@ -1,158 +0,0 @@
|
|||||||
# Marker Highlight
|
|
||||||
|
|
||||||
Animated canvas-based text highlighting using MarkerHighlight.js. Wraps text in `<mark>` 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
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
|
||||||
<script src="marker-highlight.global.js"></script>
|
|
||||||
```
|
|
||||||
|
|
||||||
## 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
|
|
||||||
<mark id="m1" data-color="rgba(255, 220, 50, 0.5)">highlighted</mark>
|
|
||||||
```
|
|
||||||
|
|
||||||
```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
|
|
||||||
<mark data-drawing-mode="circle" data-color="rgba(229, 57, 53, 0.6)">critical</mark>
|
|
||||||
<mark
|
|
||||||
data-drawing-mode="burst"
|
|
||||||
data-burst='{"style":"cloud","count":20}'
|
|
||||||
data-color="rgba(255, 220, 50, 0.5)"
|
|
||||||
>amazing</mark
|
|
||||||
>
|
|
||||||
```
|
|
||||||
|
|
||||||
## 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).
|
|
||||||
@@ -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.
|
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.
|
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.
|
||||||
|
|
||||||
@@ -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.
|
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.
|
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.
|
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.
|
These rules only apply to shader transition compositions. CSS-only compositions have no restrictions.
|
||||||
|
|
||||||
|
|||||||
@@ -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).
|
**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)
|
## Shader Transitions
|
||||||
|
|
||||||
Read [shader-setup.md](./shader-setup.md) for the full setup code these rules apply to.
|
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".
|
||||||
|
|
||||||
**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 code:** One noise library per shader (NQ or ND, not both). Always `clamp(uv, 0., 1.)`.
|
|
||||||
|
|
||||||
## Scene Template
|
## Scene Template
|
||||||
|
|
||||||
@@ -124,9 +114,4 @@ All code examples use `old` for the outgoing scene-inner selector and `new` for
|
|||||||
|
|
||||||
## Shader Transitions
|
## Shader Transitions
|
||||||
|
|
||||||
WebGL fragment shaders that composite between scene textures per-pixel. Require setup boilerplate.
|
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.
|
||||||
|
|
||||||
| 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) |
|
|
||||||
|
|||||||
@@ -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
|
|
||||||
<canvas
|
|
||||||
id="gl-canvas"
|
|
||||||
width="1920"
|
|
||||||
height="1080"
|
|
||||||
style="position:absolute;top:0;left:0;width:1920px;height:1080px;z-index:100;pointer-events:none;display:none;"
|
|
||||||
>
|
|
||||||
</canvas>
|
|
||||||
```
|
|
||||||
|
|
||||||
## 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
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js"></script>
|
|
||||||
```
|
|
||||||
|
|
||||||
```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;
|
|
||||||
```
|
|
||||||
@@ -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.);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
Reference in New Issue
Block a user