diff --git a/packages/shader-transitions/src/engineModePageComposite.ts b/packages/shader-transitions/src/engineModePageComposite.ts index ea806a902..1040ab638 100644 --- a/packages/shader-transitions/src/engineModePageComposite.ts +++ b/packages/shader-transitions/src/engineModePageComposite.ts @@ -42,7 +42,14 @@ import { isHtmlInCanvasCaptureSupported } from "./capture.js"; interface PageCompositeTransitionConfig { time: number; - shader: ShaderName; + /** + * Shader id. Undefined entries are CSS crossfades — the page-side + * compositor skips them so the GSAP opacity timeline handles the blend, + * but the entry stays in the array to preserve `transitions[i]` ↔ + * `scenes[i]`/`scenes[i+1]` index alignment for the surrounding shader + * entries. + */ + shader?: ShaderName; duration?: number; } @@ -114,6 +121,10 @@ export function installPageSideCompositor(options: PageCompositorInstallOptions) const programs = new Map(); for (const t of transitions) { + // CSS crossfade entries (shader undefined) carry no program. Use a + // strict undefined check so a misconfigured empty string still fails + // loudly through the createProgram path below. + if (t.shader === undefined) continue; if (programs.has(t.shader)) continue; try { programs.set(t.shader, createProgram(gl, getFragSource(t.shader))); @@ -127,6 +138,10 @@ export function installPageSideCompositor(options: PageCompositorInstallOptions) for (let i = 0; i < transitions.length; i++) { const t = transitions[i]; if (!t) continue; + // CSS-only transitions stay on the GSAP opacity timeline; the page- + // side compositor only handles shader entries. Index i is preserved + // so subsequent shader transitions still pair with the right scenes. + if (t.shader === undefined) continue; const fromSceneId = scenes[i]; const toSceneId = scenes[i + 1]; const prog = programs.get(t.shader); diff --git a/packages/shader-transitions/src/hyper-shader.ts b/packages/shader-transitions/src/hyper-shader.ts index ad66bf91c..8904c4c8a 100644 --- a/packages/shader-transitions/src/hyper-shader.ts +++ b/packages/shader-transitions/src/hyper-shader.ts @@ -903,7 +903,11 @@ export function init(config: HyperShaderConfig): GsapTimeline { const programs = new Map(); for (const t of transitions) { - if (!t.shader) continue; // CSS-only transitions have no WebGL program + // Strict undefined check — an explicit empty string from a vanilla-JS + // caller (the IIFE bundle is hand-loaded via