diff --git a/packages/core/src/runtime/init.ts b/packages/core/src/runtime/init.ts index 689126b2f..4e64f5c82 100644 --- a/packages/core/src/runtime/init.ts +++ b/packages/core/src/runtime/init.ts @@ -1297,6 +1297,8 @@ export function initSandboxRuntimeModular(): void { return sourceDuration ?? hostRemaining; }, }); + const forceSync = state.mediaForceSyncNextTick; + if (forceSync) state.mediaForceSyncNextTick = false; syncRuntimeMedia({ clips: cache.mediaClips, timeSeconds: state.currentTime, @@ -1305,6 +1307,7 @@ export function initSandboxRuntimeModular(): void { outputMuted: state.mediaOutputMuted, userMuted: state.bridgeMuted, userVolume: state.bridgeVolume, + forceSync, onAutoplayBlocked: () => { if (state.mediaAutoplayBlockedPosted) return; state.mediaAutoplayBlockedPosted = true; @@ -1481,6 +1484,7 @@ export function initSandboxRuntimeModular(): void { } else { state.playbackRate = Math.max(0.1, Math.min(5, parsed)); } + state.mediaForceSyncNextTick = true; if (state.capturedTimeline && typeof state.capturedTimeline.timeScale === "function") { state.capturedTimeline.timeScale(state.playbackRate); } @@ -1505,6 +1509,7 @@ export function initSandboxRuntimeModular(): void { (window.__timelines ?? {}) as Record, getIsPlaying: () => state.isPlaying, setIsPlaying: (playing) => { + if (state.isPlaying !== playing) state.mediaForceSyncNextTick = true; state.isPlaying = playing; }, getPlaybackRate: () => state.playbackRate, @@ -1512,6 +1517,7 @@ export function initSandboxRuntimeModular(): void { getCanonicalFps: () => state.canonicalFps, onSyncMedia: (timeSeconds, playing) => { state.currentTime = Math.max(0, Number(timeSeconds) || 0); + if (state.isPlaying !== playing) state.mediaForceSyncNextTick = true; state.isPlaying = playing; syncMediaForCurrentState(); }, diff --git a/packages/core/src/runtime/media.test.ts b/packages/core/src/runtime/media.test.ts index 7496c0097..00f05b30b 100644 --- a/packages/core/src/runtime/media.test.ts +++ b/packages/core/src/runtime/media.test.ts @@ -350,15 +350,21 @@ describe("syncRuntimeMedia", () => { expect(clip.el.currentTime).toBe(5); }); - it("does not seek on sub-0.5s drift in steady-state — avoids pause/play hiccups", () => { + it("corrects stable sub-0.5s drift after consecutive over-threshold ticks", () => { const clip = createMockClip({ start: 0, end: 10, mediaStart: 0 }); Object.defineProperty(clip.el, "currentTime", { value: 5.4, writable: true }); - // Establish a baseline offset of 0 with a steady-state tick first. + // Establish a baseline offset of 0 with a steady-state tick. syncRuntimeMedia({ clips: [clip], timeSeconds: 5.4, playing: true, playbackRate: 1 }); - // Now a small transient drift: timeline backs up 0.4s (typical of - // pause/play ordering). Below the 0.5s threshold — don't seek. + // Drift appears: timeline at 5, media at 5.4, offset jumps from 0 to -0.4. + // This tick establishes the new offset — strict sync counter hasn't started. syncRuntimeMedia({ clips: [clip], timeSeconds: 5, playing: true, playbackRate: 1 }); expect(clip.el.currentTime).toBe(5.4); + // Offset stabilizes at -0.4: strict sync sample 1. + syncRuntimeMedia({ clips: [clip], timeSeconds: 5, playing: true, playbackRate: 1 }); + expect(clip.el.currentTime).toBe(5.4); + // Strict sync sample 2 — threshold met, correction fires. + syncRuntimeMedia({ clips: [clip], timeSeconds: 5, playing: true, playbackRate: 1 }); + expect(clip.el.currentTime).toBe(5); }); it("does not force audio forward while it's still buffering (gradual drift growth)", () => { @@ -378,6 +384,23 @@ describe("syncRuntimeMedia", () => { expect(clip.el.currentTime).toBe(0); }); + it("forceSync corrects any drift above 20ms immediately", () => { + const clip = createMockClip({ start: 0, end: 10, mediaStart: 0 }); + Object.defineProperty(clip.el, "currentTime", { value: 5.1, writable: true }); + // Steady-state baseline. + syncRuntimeMedia({ clips: [clip], timeSeconds: 5.1, playing: true, playbackRate: 1 }); + // 100ms drift — normally below the 0.5s hard-sync threshold and would + // need 2 consecutive strict-sync samples. With forceSync, corrects immediately. + syncRuntimeMedia({ + clips: [clip], + timeSeconds: 5, + playing: true, + playbackRate: 1, + forceSync: true, + }); + expect(clip.el.currentTime).toBe(5); + }); + it("re-syncs on a scrub — offset jumps in one tick", () => { const clip = createMockClip({ start: 0, end: 20, mediaStart: 0 }); Object.defineProperty(clip.el, "currentTime", { value: 2, writable: true }); diff --git a/packages/core/src/runtime/media.ts b/packages/core/src/runtime/media.ts index d4c0039e7..c8cf437e1 100644 --- a/packages/core/src/runtime/media.ts +++ b/packages/core/src/runtime/media.ts @@ -79,6 +79,11 @@ export function refreshRuntimeMediaCache(params?: { // inactive so the next activation gets a hard resync on its first tick. const lastOffset = new WeakMap(); +// Per-element consecutive over-threshold drift sample counter for strict mode. +// Mirrors the consecutive-sample gating from the parent-proxy path to avoid +// reacting to single-frame jitter spikes. +const strictDriftSamples = new WeakMap(); + // Elements that had a seek past their buffered range (common with streaming // MP3 where preload="metadata" only fetches the first few seconds). After // setting preload="auto" and calling load(), we mark the element so subsequent @@ -130,6 +135,13 @@ export function syncRuntimeMedia(params: { * outbound message; further invocations are suppressed by the caller. */ onAutoplayBlocked?: () => void; + /** + * Force a hard media sync on this tick, bypassing the normal drift + * thresholds. Set this on play, pause, seek, and playback-rate changes + * so that accumulated sub-threshold drift from pause/play toggling is + * immediately corrected instead of persisting until catastrophic (3s). + */ + forceSync?: boolean; }): void { // Either flag silences output. Combined up front so the per-clip loop is // a single branch instead of two. @@ -165,26 +177,23 @@ export function syncRuntimeMedia(params: { // ignore unsupported playbackRate swallow("runtime.media.site1", err); } - // Drift correction. Forcing `el.currentTime = relTime` every frame - // causes an audible seek+rebuffer hiccup (readyState drops briefly). + // Drift correction. Two tiers: // - // We only want to correct drift that came from an *event* — an explicit - // user seek, a sub-composition activation, or a timeline jump — not - // drift that grew naturally from initial-buffer latency. Telling them - // apart by timing: scrubs move the timeline-to-media offset by seconds - // in a single tick; buffer catch-up grows the offset by ~one frame - // (<20ms) per tick. + // 1. Hard sync (0.5s threshold): fires on first tick, timeline jumps, + // catastrophic drift (>3s), or explicit forceSync. Unconditional + // seek — accepts the brief rebuffer cost. // - // The first tick a clip is active we don't have a previous offset to - // compare against — treat that as a hard resync so sub-compositions - // with non-zero `mediaStart` land on the right frame. + // 2. Strict sync (80ms threshold, consecutive-sample gated): catches + // accumulated drift from pause/play toggling that the hard sync + // misses. Two consecutive over-threshold samples required to avoid + // reacting to single-frame jitter. Mirrors the parent-proxy path's + // MIRROR_DRIFT_THRESHOLD_SECONDS strategy. // - // Tradeoff: the 3 s catastrophic-drift valve means an unnoticed - // steady-state drift can accumulate up to ~3 s before we correct. - // For music / motion graphics this is inaudible; for lip-synced - // dialogue it is not. If that becomes a target use case, switch to - // a short-window tight threshold (e.g. tighten to 0.15 s when the - // last play/pause transition was >500 ms ago). + // Together, these ensure narration stays within ~80ms of the visual + // timeline without introducing audible seek hiccups on every frame. + const STRICT_DRIFT_THRESHOLD = 0.08; + const STRICT_REQUIRED_SAMPLES = 2; + const currentElTime = el.currentTime || 0; const drift = Math.abs(currentElTime - relTime); const offset = relTime - currentElTime; @@ -193,7 +202,33 @@ export function syncRuntimeMedia(params: { const firstTickOfClip = prevOffset === undefined; const offsetJumped = !firstTickOfClip && Math.abs(offset - prevOffset!) > 0.5; const catastrophicDrift = drift > 3; - if (drift > 0.5 && (firstTickOfClip || offsetJumped || catastrophicDrift)) { + const hardSync = drift > 0.5 && (firstTickOfClip || offsetJumped || catastrophicDrift); + // Strict sync: tighter threshold with consecutive-sample gating. + // Catches accumulated drift from repeated pause/play that never + // crosses the 0.5s hard-sync threshold. + // + // Guard: only apply strict sync when offset has stabilized (not + // growing). During initial buffering, the offset grows ~16ms per + // tick as the timeline advances while media stays at 0. That's + // expected catch-up, not accumulated drift. Accumulated drift from + // pause/play toggling shows up as a stable, non-zero offset. + // Offset change < 0.04s/tick = "stabilized". At 60fps, each tick + // advances ~16ms, so during buffering (media stuck at 0, timeline + // advancing) the offset grows by ~16ms/tick. For accumulated drift + // (both advancing, offset persists), the delta is near 0. + const offsetStabilized = prevOffset !== undefined && Math.abs(offset - prevOffset) < 0.004; + let strictSync = false; + if (!hardSync && !firstTickOfClip && offsetStabilized && drift > STRICT_DRIFT_THRESHOLD) { + const samples = (strictDriftSamples.get(el) ?? 0) + 1; + strictDriftSamples.set(el, samples); + if (samples >= STRICT_REQUIRED_SAMPLES) { + strictSync = true; + strictDriftSamples.set(el, 0); + } + } else if (drift <= STRICT_DRIFT_THRESHOLD) { + strictDriftSamples.set(el, 0); + } + if (hardSync || strictSync || (params.forceSync && drift > 0.02)) { try { el.currentTime = relTime; } catch (err) { @@ -258,6 +293,7 @@ export function syncRuntimeMedia(params: { // Clip left its active window — drop the offset baseline so the next // activation (e.g. re-entering a sub-composition) gets a hard resync. lastOffset.delete(el); + strictDriftSamples.delete(el); seekLoadRetried.delete(el); if (!el.paused) el.pause(); } diff --git a/packages/core/src/runtime/state.ts b/packages/core/src/runtime/state.ts index c6ddaf57d..23dced7fe 100644 --- a/packages/core/src/runtime/state.ts +++ b/packages/core/src/runtime/state.ts @@ -26,6 +26,13 @@ export type RuntimeState = { * takes over playback and further rejections are the same problem. */ mediaAutoplayBlockedPosted: boolean; + /** + * One-shot flag: force a hard media sync on the next tick. Set on + * play/pause/seek/rate transitions to immediately correct any + * accumulated sub-threshold drift from pause/play toggling. + * Consumed (reset to false) by `syncMediaForCurrentState`. + */ + mediaForceSyncNextTick: boolean; playbackRate: number; bridgeLastPostedFrame: number; bridgeLastPostedAt: number; @@ -82,6 +89,7 @@ export function createRuntimeState(): RuntimeState { bridgeVolume: 1, mediaOutputMuted: false, mediaAutoplayBlockedPosted: false, + mediaForceSyncNextTick: false, playbackRate: 1, bridgeLastPostedFrame: -1, bridgeLastPostedAt: 0, diff --git a/packages/studio/data/projects/threejs-shaders-catalog/compositions/chromatic-radial-split.html b/packages/studio/data/projects/threejs-shaders-catalog/compositions/chromatic-radial-split.html new file mode 100644 index 000000000..cdbd1c5d6 --- /dev/null +++ b/packages/studio/data/projects/threejs-shaders-catalog/compositions/chromatic-radial-split.html @@ -0,0 +1,366 @@ + + + + + + + + +
+ + +
+
+
08 / 14
+ + +
+
SCENE A
+
+
+
SCENE B
+
+ + + + + + +
+
Chromatic Radial Split
+
Prompt
+
"use chromatic radial split shader transition"
+
+ RGB channels separate and converge radially. From-scene splits outward, to-scene converges + inward. +
+
+ +
+
+ + + + diff --git a/packages/studio/data/projects/threejs-shaders-catalog/compositions/cinematic-zoom.html b/packages/studio/data/projects/threejs-shaders-catalog/compositions/cinematic-zoom.html new file mode 100644 index 000000000..635437348 --- /dev/null +++ b/packages/studio/data/projects/threejs-shaders-catalog/compositions/cinematic-zoom.html @@ -0,0 +1,366 @@ + + + + + + + + +
+ + +
+
+
07 / 14
+ + +
+
SCENE A
+
+
+
SCENE B
+
+ + + + + + +
+
Cinematic Zoom
+
Prompt
+
"use cinematic zoom shader transition"
+
+ Both scenes zoom-blur with per-channel radial offset. From zooms outward, to zooms inward + from tight. +
+
+ +
+
+ + + + diff --git a/packages/studio/data/projects/threejs-shaders-catalog/compositions/cross-warp-morph.html b/packages/studio/data/projects/threejs-shaders-catalog/compositions/cross-warp-morph.html new file mode 100644 index 000000000..6945d3dd9 --- /dev/null +++ b/packages/studio/data/projects/threejs-shaders-catalog/compositions/cross-warp-morph.html @@ -0,0 +1,377 @@ + + + + + + + + +
+ + +
+
+
13 / 14
+ + +
+
SCENE A
+
+
+
SCENE B
+
+ + + + + + +
+
Cross-Warp Morph
+
Prompt
+
"use cross-warp morph shader transition"
+
+ Both scenes displace along a shared FBM noise field in opposite directions. Noise-driven + blend boundary. +
+
+ +
+
+ + + + diff --git a/packages/studio/data/projects/threejs-shaders-catalog/compositions/domain-warp-dissolve.html b/packages/studio/data/projects/threejs-shaders-catalog/compositions/domain-warp-dissolve.html new file mode 100644 index 000000000..8a24dc98d --- /dev/null +++ b/packages/studio/data/projects/threejs-shaders-catalog/compositions/domain-warp-dissolve.html @@ -0,0 +1,380 @@ + + + + + + + + +
+ + +
+
+
01 / 14
+ + +
+
SCENE A
+
+
+
SCENE B
+
+ + + + + + +
+
Domain Warp Dissolve
+
Prompt
+
"use domain warp dissolve shader transition"
+
+ Cascaded fbm(p + fbm(p)) displaces both scenes along a warp field. Iridescent + cosine-palette edge glow. +
+
+ +
+
+ + + + diff --git a/packages/studio/data/projects/threejs-shaders-catalog/compositions/flash-through-white.html b/packages/studio/data/projects/threejs-shaders-catalog/compositions/flash-through-white.html new file mode 100644 index 000000000..930b6e100 --- /dev/null +++ b/packages/studio/data/projects/threejs-shaders-catalog/compositions/flash-through-white.html @@ -0,0 +1,366 @@ + + + + + + + + +
+ + +
+
+
12 / 14
+ + +
+
SCENE A
+
+
+
SCENE B
+
+ + + + + + +
+
Flash Through White
+
Prompt
+
"use flash through white shader transition"
+
+ Both scenes brighten to a white midpoint. Works on dark backgrounds where + color-dip-to-black is invisible. +
+
+ +
+
+ + + + diff --git a/packages/studio/data/projects/threejs-shaders-catalog/compositions/glitch.html b/packages/studio/data/projects/threejs-shaders-catalog/compositions/glitch.html new file mode 100644 index 000000000..79fc5206b --- /dev/null +++ b/packages/studio/data/projects/threejs-shaders-catalog/compositions/glitch.html @@ -0,0 +1,366 @@ + + + + + + + + +
+ + +
+
+
09 / 14
+ + +
+
SCENE A
+
+
+
SCENE B
+
+ + + + + + +
+
Glitch
+
Prompt
+
"use glitch shader transition"
+
+ Scan lines, block scramble, chromatic aberration, brightness flicker, and color + posterization. +
+
+ +
+
+ + + + diff --git a/packages/studio/data/projects/threejs-shaders-catalog/compositions/gravitational-lens.html b/packages/studio/data/projects/threejs-shaders-catalog/compositions/gravitational-lens.html new file mode 100644 index 000000000..2feb83f7a --- /dev/null +++ b/packages/studio/data/projects/threejs-shaders-catalog/compositions/gravitational-lens.html @@ -0,0 +1,365 @@ + + + + + + + + +
+ + +
+
+
06 / 14
+ + +
+
SCENE A
+
+
+
SCENE B
+
+ + + + + + +
+
Gravitational Lens
+
Prompt
+
"use gravitational lens shader transition"
+
+ Content warps toward a gravity well with chromatic aberration and event horizon darkening. +
+
+ +
+
+ + + + diff --git a/packages/studio/data/projects/threejs-shaders-catalog/compositions/light-leak.html b/packages/studio/data/projects/threejs-shaders-catalog/compositions/light-leak.html new file mode 100644 index 000000000..8385d63a1 --- /dev/null +++ b/packages/studio/data/projects/threejs-shaders-catalog/compositions/light-leak.html @@ -0,0 +1,365 @@ + + + + + + + + +
+ + +
+
+
14 / 14
+ + +
+
SCENE A
+
+
+
SCENE B
+
+ + + + + + +
+
Light Leak
+
Prompt
+
"use light leak shader transition"
+
+ Beer-Lambert exponential falloff with ACES tone mapping and directional flare streak. +
+
+ +
+
+ + + + diff --git a/packages/studio/data/projects/threejs-shaders-catalog/compositions/ridged-burn.html b/packages/studio/data/projects/threejs-shaders-catalog/compositions/ridged-burn.html new file mode 100644 index 000000000..94629341c --- /dev/null +++ b/packages/studio/data/projects/threejs-shaders-catalog/compositions/ridged-burn.html @@ -0,0 +1,377 @@ + + + + + + + + +
+ + +
+
+
02 / 14
+ + +
+
SCENE A
+
+
+
SCENE B
+
+ + + + + + +
+
Ridged Burn
+
Prompt
+
"use ridged burn shader transition"
+
+ abs(noise) creates sharp lightning-crack edges. Blackbody color gradient with + high-frequency ember sparks. +
+
+ +
+
+ + + + diff --git a/packages/studio/data/projects/threejs-shaders-catalog/compositions/ripple-waves.html b/packages/studio/data/projects/threejs-shaders-catalog/compositions/ripple-waves.html new file mode 100644 index 000000000..27e09015e --- /dev/null +++ b/packages/studio/data/projects/threejs-shaders-catalog/compositions/ripple-waves.html @@ -0,0 +1,366 @@ + + + + + + + + +
+ + +
+
+
05 / 14
+ + +
+
SCENE A
+
+
+
SCENE B
+
+ + + + + + +
+
Ripple Waves
+
Prompt
+
"use ripple waves shader transition"
+
+ Exponential sine waves with sharp crests and broad troughs. Both scenes ripple in opposite + phases. +
+
+ +
+
+ + + + diff --git a/packages/studio/data/projects/threejs-shaders-catalog/compositions/sdf-iris.html b/packages/studio/data/projects/threejs-shaders-catalog/compositions/sdf-iris.html new file mode 100644 index 000000000..21a13a91d --- /dev/null +++ b/packages/studio/data/projects/threejs-shaders-catalog/compositions/sdf-iris.html @@ -0,0 +1,366 @@ + + + + + + + + +
+ + +
+
+
04 / 14
+ + +
+
SCENE A
+
+
+
SCENE B
+
+ + + + + + +
+
SDF Iris
+
Prompt
+
"use sdf iris shader transition"
+
+ Aspect-corrected circle SDF opening from center. Triple onion ring glow with warm + exponential falloff. +
+
+ +
+
+ + + + diff --git a/packages/studio/data/projects/threejs-shaders-catalog/compositions/swirl-vortex.html b/packages/studio/data/projects/threejs-shaders-catalog/compositions/swirl-vortex.html new file mode 100644 index 000000000..6b7dc8bf8 --- /dev/null +++ b/packages/studio/data/projects/threejs-shaders-catalog/compositions/swirl-vortex.html @@ -0,0 +1,376 @@ + + + + + + + + +
+ + +
+
+
10 / 14
+ + +
+
SCENE A
+
+
+
SCENE B
+
+ + + + + + +
+
Swirl Vortex
+
Prompt
+
"use swirl vortex shader transition"
+
+ Both scenes swirl in opposite directions with FBM-warped spiral path. Organic distortion. +
+
+ +
+
+ + + + diff --git a/packages/studio/data/projects/threejs-shaders-catalog/compositions/thermal-distortion.html b/packages/studio/data/projects/threejs-shaders-catalog/compositions/thermal-distortion.html new file mode 100644 index 000000000..ec5c04f02 --- /dev/null +++ b/packages/studio/data/projects/threejs-shaders-catalog/compositions/thermal-distortion.html @@ -0,0 +1,377 @@ + + + + + + + + +
+ + +
+
+
11 / 14
+ + +
+
SCENE A
+
+
+
SCENE B
+
+ + + + + + +
+
Thermal Distortion
+
Prompt
+
"use thermal distortion shader transition"
+
+ FBM-driven heat shimmer rising from the bottom. Sine displacement modulated by noise with + warm haze. +
+
+ +
+
+ + + + diff --git a/packages/studio/data/projects/threejs-shaders-catalog/compositions/vfx-iphone-device.html b/packages/studio/data/projects/threejs-shaders-catalog/compositions/vfx-iphone-device.html new file mode 100644 index 000000000..4902b729e --- /dev/null +++ b/packages/studio/data/projects/threejs-shaders-catalog/compositions/vfx-iphone-device.html @@ -0,0 +1,819 @@ + + + + + + Devices Canvas — HTML-in-Canvas API Showcase + + + + + + + +
+ + +
+
+
+ HyperFramesLog in +
+
+ Community Playground +
+
+ Made with HyperFrames +
+
+ ExamplesCatalog 0 +
+
+
+
+
+ + +
+
+
+ HyperFrames +
+ DocsPlaygroundPricing +
+ Log in +
+
+ Community Playground +
+
+ Create videos with HTML — powered by HyperFrames +
+
+ ExamplesCatalog 0 +
+
+
+
+
+ + +
+
+ + + diff --git a/packages/studio/data/projects/threejs-shaders-catalog/compositions/vfx-liquid-glass.html b/packages/studio/data/projects/threejs-shaders-catalog/compositions/vfx-liquid-glass.html new file mode 100644 index 000000000..2ac7ac054 --- /dev/null +++ b/packages/studio/data/projects/threejs-shaders-catalog/compositions/vfx-liquid-glass.html @@ -0,0 +1,752 @@ + + + + + + Liquid Glass Parallax + + + + + + + + + + + + +
+ +
+
Write HTML → Render Video
+

Ship videos 10x faster

+
+ HTML is the source of truth for video. No timeline editors, no After Effects — just + code. +
+
+
+
47x
+
Faster than AE
+
+
+
12.4K
+
Creators
+
+
+
2.4M
+
Videos Rendered
+
+
+
+
+ + + +
+
+ + + + diff --git a/packages/studio/data/projects/threejs-shaders-catalog/compositions/vfx-shatter.html b/packages/studio/data/projects/threejs-shaders-catalog/compositions/vfx-shatter.html new file mode 100644 index 000000000..ab5a7dd21 --- /dev/null +++ b/packages/studio/data/projects/threejs-shaders-catalog/compositions/vfx-shatter.html @@ -0,0 +1,1156 @@ + + + + + + HTML Glass Shatter + + + + + + +
+ + +
+
+
+ + Write HTML, Render Video +
+ +

Everything you loved.
Now 10x faster.

+ +
+
+
+
+
+
+
Render Speed
+
+ v1 + v2 +
+
+
+
+
+
+
+
Throughput
+
+ v1 + v2 +
+
+
+
+
+
+
+
Reliability
+
+ v1 + v2 +
+
+
+ +
+
+
+
+

Instant Deploys

+

Zero-downtime rollouts in under 3 seconds globally.

+
+
+
+
🔒
+
+

End-to-End Encryption

+

AES-256 at rest, TLS 1.3 in transit. SOC2 certified.

+
+
+
+
📈
+
+

Real-time Analytics

+

Sub-second queries across billions of events.

+
+
+
+ + +
+
+
+ + + +
+
+
+ +
✓ Powered by HyperFrames
+ +

HTML is Video

+ +
+
+
10x
+
Render Speed
+
+
+
50%
+
File Size
+
+
+
99.99%
+
Reliability
+
+
+ + +
+
+
+ + + + + +
+

HTML-in-Canvas Required

+

+ This composition requires the experimental CanvasDrawElement API. Enable it in Chrome or + Brave: +

+

chrome://flags/#canvas-draw-element

+
+ + +
+
+ + + + diff --git a/packages/studio/data/projects/threejs-shaders-catalog/compositions/whip-pan.html b/packages/studio/data/projects/threejs-shaders-catalog/compositions/whip-pan.html new file mode 100644 index 000000000..d1ea5aa1c --- /dev/null +++ b/packages/studio/data/projects/threejs-shaders-catalog/compositions/whip-pan.html @@ -0,0 +1,366 @@ + + + + + + + + +
+ + +
+
+
03 / 14
+ + +
+
SCENE A
+
+
+
SCENE B
+
+ + + + + + +
+
Whip Pan
+
Prompt
+
"use whip pan shader transition"
+
+ Both scenes slide horizontally with 10-sample directional motion blur simulating a fast + camera pan. +
+
+ +
+
+ + + + diff --git a/packages/studio/data/projects/threejs-shaders-catalog/index.html b/packages/studio/data/projects/threejs-shaders-catalog/index.html new file mode 100644 index 000000000..9a70e7b54 --- /dev/null +++ b/packages/studio/data/projects/threejs-shaders-catalog/index.html @@ -0,0 +1,184 @@ + + + + + Three.js Shaders Catalog + + + + +
+ + + + +
+ + +
+ + +
+ + + + +
+ +
+ +
+ + +
+ +
+ +
+ + +
+ +
+ +
+ + +
+ +
+ +
+ + +
+ +
+ +
+ + + + + diff --git a/packages/studio/data/projects/threejs-shaders-catalog/meta.json b/packages/studio/data/projects/threejs-shaders-catalog/meta.json new file mode 100644 index 000000000..289054b15 --- /dev/null +++ b/packages/studio/data/projects/threejs-shaders-catalog/meta.json @@ -0,0 +1,7 @@ +{ + "name": "threejs-shaders-catalog", + "description": "Three.js Shaders Catalog — showcase of 14 GPU transitions and 3 flagship HTML-in-Canvas effects for the HyperFrames tech article", + "width": 1920, + "height": 1080, + "fps": 30 +} diff --git a/packages/studio/data/projects/threejs-shaders-catalog/models/hyperframes-desktop.png b/packages/studio/data/projects/threejs-shaders-catalog/models/hyperframes-desktop.png new file mode 100644 index 000000000..2f168445c Binary files /dev/null and b/packages/studio/data/projects/threejs-shaders-catalog/models/hyperframes-desktop.png differ diff --git a/packages/studio/data/projects/threejs-shaders-catalog/models/hyperframes-mobile.png b/packages/studio/data/projects/threejs-shaders-catalog/models/hyperframes-mobile.png new file mode 100644 index 000000000..3995bb6f9 Binary files /dev/null and b/packages/studio/data/projects/threejs-shaders-catalog/models/hyperframes-mobile.png differ diff --git a/packages/studio/data/projects/threejs-shaders-catalog/models/iphone.glb b/packages/studio/data/projects/threejs-shaders-catalog/models/iphone.glb new file mode 100644 index 000000000..fd7336805 Binary files /dev/null and b/packages/studio/data/projects/threejs-shaders-catalog/models/iphone.glb differ diff --git a/packages/studio/data/projects/threejs-shaders-catalog/models/macbook.glb b/packages/studio/data/projects/threejs-shaders-catalog/models/macbook.glb new file mode 100644 index 000000000..2fed3be1b Binary files /dev/null and b/packages/studio/data/projects/threejs-shaders-catalog/models/macbook.glb differ diff --git a/registry/blocks/vfx-cinematic-camera/models/DamagedHelmet.glb b/registry/blocks/vfx-cinematic-camera/models/DamagedHelmet.glb new file mode 100644 index 000000000..2cee76d76 Binary files /dev/null and b/registry/blocks/vfx-cinematic-camera/models/DamagedHelmet.glb differ diff --git a/registry/blocks/vfx-cinematic-camera/registry-item.json b/registry/blocks/vfx-cinematic-camera/registry-item.json new file mode 100644 index 000000000..14cdb3194 --- /dev/null +++ b/registry/blocks/vfx-cinematic-camera/registry-item.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://hyperframes.heygen.com/schema/registry-item.json", + "name": "vfx-cinematic-camera", + "type": "hyperframes:block", + "title": "Cinematic Camera", + "description": "Three.js cinematic camera controller with a GLTF model — dolly zoom, crane shot, orbit, and rack focus on the DamagedHelmet", + "stability": "experimental", + "dimensions": { + "width": 1920, + "height": 1080 + }, + "duration": 16, + "tags": ["html-in-canvas", "three-js", "camera", "cinematic", "gltf"], + "files": [ + { + "path": "vfx-cinematic-camera.html", + "target": "compositions/vfx-cinematic-camera.html", + "type": "hyperframes:composition" + }, + { + "path": "models/DamagedHelmet.glb", + "target": "models/DamagedHelmet.glb" + } + ] +} diff --git a/registry/blocks/vfx-cinematic-camera/vfx-cinematic-camera.html b/registry/blocks/vfx-cinematic-camera/vfx-cinematic-camera.html new file mode 100644 index 000000000..37f177f64 --- /dev/null +++ b/registry/blocks/vfx-cinematic-camera/vfx-cinematic-camera.html @@ -0,0 +1,302 @@ + + + + + + Cinematic Camera + + + + + + + + +
+ + +
+
Camera Move
+
Dolly Zoom
+
+ +
+
+ + + + diff --git a/registry/blocks/vfx-god-rays/registry-item.json b/registry/blocks/vfx-god-rays/registry-item.json new file mode 100644 index 000000000..de34ad99e --- /dev/null +++ b/registry/blocks/vfx-god-rays/registry-item.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://hyperframes.heygen.com/schema/registry-item.json", + "name": "vfx-god-rays", + "type": "hyperframes:block", + "title": "Volumetric Light Rays", + "description": "Post-process god rays (crepuscular rays) radiating from a light source through a Three.js scene with occluding geometry", + "stability": "experimental", + "dimensions": { + "width": 1920, + "height": 1080 + }, + "duration": 8, + "tags": ["three-js", "shader", "volumetric", "post-process"], + "files": [ + { + "path": "vfx-god-rays.html", + "target": "compositions/vfx-god-rays.html", + "type": "hyperframes:composition" + } + ] +} diff --git a/registry/blocks/vfx-god-rays/vfx-god-rays.html b/registry/blocks/vfx-god-rays/vfx-god-rays.html new file mode 100644 index 000000000..ce701a988 --- /dev/null +++ b/registry/blocks/vfx-god-rays/vfx-god-rays.html @@ -0,0 +1,345 @@ + + + + + + Volumetric Light Rays + + + + + + +
+ +
+
+ + + + diff --git a/registry/blocks/vfx-particle-field/registry-item.json b/registry/blocks/vfx-particle-field/registry-item.json new file mode 100644 index 000000000..6103dfa51 --- /dev/null +++ b/registry/blocks/vfx-particle-field/registry-item.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://hyperframes.heygen.com/schema/registry-item.json", + "name": "vfx-particle-field", + "type": "hyperframes:block", + "title": "Particle Field", + "description": "GPU particle system — thousands of points that form text, dissolve into noise, and reform into shapes", + "stability": "experimental", + "dimensions": { + "width": 1920, + "height": 1080 + }, + "duration": 10, + "tags": ["three-js", "particles", "gpu", "morphing"], + "files": [ + { + "path": "vfx-particle-field.html", + "target": "compositions/vfx-particle-field.html", + "type": "hyperframes:composition" + } + ] +} diff --git a/registry/blocks/vfx-particle-field/vfx-particle-field.html b/registry/blocks/vfx-particle-field/vfx-particle-field.html new file mode 100644 index 000000000..1cb9609e7 --- /dev/null +++ b/registry/blocks/vfx-particle-field/vfx-particle-field.html @@ -0,0 +1,301 @@ + + + + + + Particle Field + + + + + +
+ +
+
+ + + + diff --git a/registry/blocks/vfx-ray-march/registry-item.json b/registry/blocks/vfx-ray-march/registry-item.json new file mode 100644 index 000000000..bbc7ccce4 --- /dev/null +++ b/registry/blocks/vfx-ray-march/registry-item.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://hyperframes.heygen.com/schema/registry-item.json", + "name": "vfx-ray-march", + "type": "hyperframes:block", + "title": "SDF Ray March", + "description": "GPU ray marching through morphing signed distance fields — infinite tunnel with fractal geometry", + "stability": "experimental", + "dimensions": { + "width": 1920, + "height": 1080 + }, + "duration": 8, + "tags": ["three-js", "shader", "ray-march", "sdf", "fractal"], + "files": [ + { + "path": "vfx-ray-march.html", + "target": "compositions/vfx-ray-march.html", + "type": "hyperframes:composition" + } + ] +} diff --git a/registry/blocks/vfx-ray-march/vfx-ray-march.html b/registry/blocks/vfx-ray-march/vfx-ray-march.html new file mode 100644 index 000000000..594c52383 --- /dev/null +++ b/registry/blocks/vfx-ray-march/vfx-ray-march.html @@ -0,0 +1,223 @@ + + + + + + SDF Ray March + + + + +
+ +
+
+ + + +