@wave3d/core 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"WaveRenderer.js","names":[],"sources":["../../src/renderer/WaveRenderer.ts"],"sourcesContent":["import * as THREE from \"three\";\nimport { EffectComposer } from \"three/addons/postprocessing/EffectComposer.js\";\nimport { RenderPass } from \"three/addons/postprocessing/RenderPass.js\";\nimport { OutputPass } from \"three/addons/postprocessing/OutputPass.js\";\nimport { ShaderPass } from \"three/addons/postprocessing/ShaderPass.js\";\nimport { UnrealBloomPass } from \"three/addons/postprocessing/UnrealBloomPass.js\";\nimport {\n vertexShader,\n fragmentShader,\n lineFragmentShader,\n postVertexShader,\n postFragmentShader,\n} from \"./shaders\";\nimport { WaveGeometry } from \"./WaveGeometry\";\nimport {\n buildPaletteTexture,\n configurePaletteTexture,\n paletteSignature,\n PALETTE_MAPS,\n paletteMapCanvas,\n canvasToTexture,\n loadPaletteImage,\n buildBackgroundGradientCanvas,\n buildBackgroundMeshCanvas,\n buildBackgroundImageCanvas,\n drawBackgroundMediaFrame,\n} from \"./palette\";\nimport { buildHeroPaletteCanvas, buildHeroPaletteTexture } from \"./heroPalette\";\nimport {\n MAX_COLORS,\n MAX_LIGHTS,\n MAX_MESH_POINTS,\n MAX_NOISE_BANDS,\n ensureStudioConfig,\n} from \"../config/model\";\nimport type { StudioConfig, WaveConfig, BlendMode } from \"../config/model\";\n\nconst BASE_SEGMENTS = 220; // base segment count along the ribbon; denser = smoother (scaled down per wave — see get segments)\n\n/** Reference frame (world units) the orthographic camera fills at cameraZoom 1. The wave is\n * framed by COVERING this FRAME_W × FRAME_H rectangle (centred on cameraTarget) into the canvas\n * — scaled to fill both dimensions, cropping the aspect overflow — so a given cameraZoom /\n * cameraTarget frames the wave the SAME at any canvas size or aspect (only the cropped margin\n * differs). FRAME_H = FRAME_W / (16/9) makes the reference a 16:9 rectangle; for canvases wider\n * than that the width binds, narrower ones zoom in to fill instead of\n * showing empty bands. This is what makes a saved preset reproduce on anyone's screen. */\nexport const FRAME_W = 1333;\nexport const FRAME_H = 750;\n\nexport interface WaveRendererOptions {\n /** Honor prefers-reduced-motion by freezing animation. Default true. */\n respectReducedMotion?: boolean;\n /**\n * Skip the intro time-ramp (the ~1s ease-in of animation on load), rendering at full speed\n * immediately. The studio passes `import.meta.env.DEV` here so a fresh renderer on every HMR\n * hot-swap doesn't replay the ease-in (which reads as a \"speed up\" when you tab back). Default\n * false — production embeds keep the ease-in. Ignored while paused (a paused frame is always the\n * full frame). Default false.\n */\n skipIntroRamp?: boolean;\n}\n\n/**\n * Per-wave 2D palette texture (+ optional looping video). One instance per wave, so each\n * wave carries its own palette. Guarded by a signature so it only rebuilds when that wave's\n * palette actually changes (not every refresh).\n */\nclass WavePalette {\n texture?: THREE.Texture;\n private sig = \"\";\n private video?: HTMLVideoElement;\n private videoUrl = \"\";\n private failedUrl = \"\";\n\n constructor(\n private readonly makeVideo: (url: string) => HTMLVideoElement,\n private readonly onVideoReady: () => void,\n ) {}\n\n /** Rebuild this wave's palette texture from its config (video / custom image / stops /\n * built-in map / hero LUT) and point the wave's palette uniforms at it. */\n apply(cfg: WaveConfig, uniforms: Record<string, THREE.IUniform>): void {\n const videoUrl = cfg.paletteVideoUrl;\n const url = cfg.paletteImageUrl;\n const source = cfg.paletteSource ?? \"hero\";\n let sig: string;\n let build: () => THREE.Texture;\n if (videoUrl) {\n this.ensureVideo(videoUrl);\n if (this.video?.readyState && this.video.readyState >= 2) {\n sig = \"video|\" + videoUrl;\n build = () => configurePaletteTexture(new THREE.VideoTexture(this.video!));\n } else {\n sig = \"video-loading|\" + videoUrl;\n build = () => buildHeroPaletteTexture();\n }\n } else if (url) {\n this.clearVideo();\n sig = \"url|\" + url;\n build = () => loadPaletteImage(url);\n } else if (source === \"stops\") {\n this.clearVideo();\n const opts = {\n stops: cfg.palette,\n edgeColor: cfg.paletteEdgeColor ?? \"#8e9dff\",\n edgeAmount: cfg.paletteEdgeAmount ?? 0.3,\n };\n sig = \"stops|\" + paletteSignature(opts);\n build = () => buildPaletteTexture(opts);\n } else if (PALETTE_MAPS[source]) {\n this.clearVideo();\n sig = \"map|\" + source;\n build = () => canvasToTexture(paletteMapCanvas(PALETTE_MAPS[source]));\n } else {\n this.clearVideo();\n sig = \"hero\";\n build = () => buildHeroPaletteTexture();\n }\n if (sig !== this.sig || !this.texture) {\n this.texture?.dispose();\n this.texture = build();\n this.sig = sig;\n }\n uniforms.uPalette.value = this.texture;\n uniforms.uUsePalette.value = cfg.usePaletteTexture === false ? 0 : 1;\n uniforms.uPaletteRaw.value = 1;\n }\n\n /** Play/pause this wave's palette video with the render loop. */\n syncPlayback(cfg: WaveConfig, running: boolean): void {\n const v = this.video;\n if (!v) return;\n const active =\n running &&\n cfg.gradientType !== \"mesh\" &&\n cfg.usePaletteTexture !== false &&\n !!cfg.paletteVideoUrl;\n if (active) void v.play().catch(() => {});\n else v.pause();\n }\n\n private ensureVideo(url: string): void {\n if (this.videoUrl === url || this.failedUrl === url) return;\n this.clearVideo();\n this.videoUrl = url;\n const video = this.makeVideo(url);\n video.addEventListener(\n \"loadeddata\",\n () => {\n this.video = video;\n this.failedUrl = \"\";\n this.sig = \"\";\n this.onVideoReady();\n },\n { once: true },\n );\n video.addEventListener(\n \"error\",\n () => {\n this.failedUrl = url;\n this.sig = \"\";\n this.onVideoReady();\n },\n { once: true },\n );\n this.video = video;\n video.load();\n }\n\n private clearVideo(): void {\n if (this.video) {\n this.video.pause();\n this.video.removeAttribute(\"src\");\n this.video.load();\n }\n this.video = undefined;\n this.videoUrl = \"\";\n }\n\n dispose(): void {\n this.texture?.dispose();\n this.texture = undefined;\n this.clearVideo();\n this.failedUrl = \"\";\n }\n}\n\ntype Wave = {\n mesh: THREE.Mesh;\n material: THREE.ShaderMaterial;\n geometry: WaveGeometry;\n /** This wave's own 2D palette texture + optional video. */\n palette: WavePalette;\n};\n\n// Parse scratch: refresh() converts ~25 hex colours per wave per call (i.e. per slider input),\n// so reuse one Color instead of allocating each time.\nconst HEX_SCRATCH = new THREE.Color();\n\n/** Convert an sRGB hex string to a linear-space RGB vector (three's ColorManagement does the\n * sRGB→linear conversion on parse). Exported for the studio subclass's live light-uniform push. */\nexport function hexToLinearVec3(hex: string, target: THREE.Vector3): THREE.Vector3 {\n // three's ColorManagement (on by default in r169) already converts the sRGB hex to\n // LINEAR when parsing the hex — its .r/.g/.b are linear. Calling\n // convertSRGBToLinear() again would double-linearize (crushing greens → everything\n // turns red), so we read the components directly.\n const c = HEX_SCRATCH.set(hex);\n return target.set(c.r, c.g, c.b);\n}\n\n/**\n * Renders a gradient \"wave of light\" from a {@link StudioConfig}. Framework-agnostic:\n * it needs only a DOM container and a config. The studio mutates the config in\n * place and calls `refresh()` / `rebuild()`.\n */\nexport class WaveRenderer {\n readonly renderer: THREE.WebGLRenderer;\n protected readonly scene = new THREE.Scene();\n protected readonly camera: THREE.OrthographicCamera;\n protected readonly group = new THREE.Group();\n private readonly composer: EffectComposer;\n private readonly postPass: ShaderPass;\n /** Optional bloom pass — created lazily when bloomStrength first goes >0, removed at 0. */\n private bloomPass?: UnrealBloomPass;\n protected readonly container: HTMLElement;\n private readonly respectReducedMotion: boolean;\n private readonly skipIntroRamp: boolean;\n\n protected config: StudioConfig;\n protected waves: Wave[] = [];\n\n // 2D palette textures (+ any palette videos) live per-wave — see WavePalette on each Wave.\n private backgroundTexture?: THREE.Texture;\n private backgroundSig = \"\";\n private backgroundImage?: HTMLImageElement;\n private backgroundImageUrl = \"\";\n private failedBackgroundImageUrl = \"\";\n private backgroundVideo?: HTMLVideoElement;\n private backgroundVideoUrl = \"\";\n private failedBackgroundVideoUrl = \"\";\n private backgroundVideoCanvas?: HTMLCanvasElement;\n /** Authored default camera pose, for \"Reset camera\". */\n protected readonly homeCamPos = new THREE.Vector3();\n protected readonly homeCamTarget = new THREE.Vector3();\n\n // Reused scratch for per-frame clip-plane fitting (see updateClipPlanes) — hoisted so the\n // render loop allocates nothing.\n private readonly clipBox = new THREE.Box3();\n private readonly clipSphere = new THREE.Sphere();\n private readonly clipTmpA = new THREE.Vector3();\n private readonly clipTmpB = new THREE.Vector3();\n\n private readonly clock = new THREE.Clock();\n private time = 0;\n private rafId = 0;\n protected running = false;\n private started = false;\n\n private visible = true;\n private pageVisible = true;\n private reducedMotion = false;\n /** Intro ramp: eases animation time 0→1 over ~1s on load (when config.introRamp). */\n private introTimeRamp = 0;\n\n private readonly resizeObserver: ResizeObserver;\n private readonly intersectionObserver: IntersectionObserver;\n private readonly motionQuery: MediaQueryList;\n\n protected capturing = false;\n /** Fixed backing-buffer dimensions used by the studio's visible export frame. Embeds leave\n * this unset and continue to resize responsively with their container and device DPR. */\n private outputSize?: { width: number; height: number };\n\n constructor(container: HTMLElement, config: StudioConfig, options: WaveRendererOptions = {}) {\n this.container = container;\n this.config = ensureStudioConfig(config);\n this.respectReducedMotion = options.respectReducedMotion ?? true;\n this.skipIntroRamp = options.skipIntroRamp ?? false;\n\n this.renderer = new THREE.WebGLRenderer({\n // antialias smooths edges; preserveDrawingBuffer keeps the drawing buffer readable so we\n // can export PNG/WebM. Both cost a little performance, but this authoring tool needs them.\n antialias: true,\n alpha: true,\n preserveDrawingBuffer: true,\n powerPreference: \"high-performance\",\n });\n this.renderer.outputColorSpace = THREE.SRGBColorSpace;\n this.renderer.setClearColor(0x000000, 0);\n container.appendChild(this.renderer.domElement);\n\n // Resilience: if the GPU drops the context (memory pressure, sleep/wake), don't\n // let the browser hard-crash the page — prevent the default and rebuild on restore.\n this.renderer.domElement.addEventListener(\"webglcontextlost\", this.onContextLost, false);\n this.renderer.domElement.addEventListener(\n \"webglcontextrestored\",\n this.onContextRestored,\n false,\n );\n\n // Orthographic, framed in device pixels: resize() sets the frustum to the canvas size, and\n // the mesh is scaled up so the wave overflows the frame, leaving only the twist on screen.\n // The left/right/top/bottom bounds here are placeholders overwritten by the first resize();\n // near/far are placeholders too — updateClipPlanes() refits them to the scene every frame so\n // no camera angle ever clips the wave (a fixed slab does — see updateClipPlanes).\n this.camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 1, 10000);\n this.camera.position.set(\n config.cameraPosition.x,\n config.cameraPosition.y,\n config.cameraPosition.z,\n );\n this.camera.zoom = config.cameraZoom ?? 1;\n this.camera.lookAt(config.cameraTarget.x, config.cameraTarget.y, config.cameraTarget.z);\n this.camera.updateProjectionMatrix();\n // Remember the authored default pose so \"Reset camera\" returns to it (orbit mutates\n // config.cameraPosition, so we can't read it back from config later).\n this.homeCamPos.copy(this.camera.position);\n this.homeCamTarget.set(config.cameraTarget.x, config.cameraTarget.y, config.cameraTarget.z);\n this.scene.add(this.group);\n\n this.composer = new EffectComposer(this.renderer);\n this.composer.addPass(new RenderPass(this.scene, this.camera));\n this.postPass = new ShaderPass({\n uniforms: {\n tDiffuse: { value: null },\n uResolution: { value: new THREE.Vector2(1, 1) },\n uBlurAmount: { value: config.blur },\n uBlurSamples: { value: 6 },\n uGrainAmount: { value: config.grain },\n uTime: { value: 0 },\n },\n vertexShader: postVertexShader,\n fragmentShader: postFragmentShader,\n });\n this.composer.addPass(this.postPass);\n this.composer.addPass(new OutputPass());\n\n this.motionQuery = window.matchMedia(\"(prefers-reduced-motion: reduce)\");\n this.reducedMotion = this.respectReducedMotion && this.motionQuery.matches;\n this.motionQuery.addEventListener(\"change\", this.onMotionChange);\n document.addEventListener(\"visibilitychange\", this.onVisibilityChange);\n\n this.intersectionObserver = new IntersectionObserver(\n (entries) => {\n this.visible = entries[0]?.isIntersecting ?? true;\n this.updateRunning();\n },\n { rootMargin: \"100px\" },\n );\n this.intersectionObserver.observe(container);\n\n this.resizeObserver = new ResizeObserver(this.onResize);\n this.resizeObserver.observe(container);\n\n this.applyBackground();\n this.buildWaves();\n this.resize();\n }\n\n private get segments(): number {\n // Scale detail down as waves multiply, so total geometry stays bounded.\n const q = this.config.quality / Math.sqrt(Math.max(1, this.config.waves.length));\n return THREE.MathUtils.clamp(Math.round(BASE_SEGMENTS * q), 24, 360);\n }\n\n private makeUniforms(): Record<string, THREE.IUniform> {\n const colors: THREE.Vector3[] = [];\n const colorPos: number[] = [];\n for (let i = 0; i < MAX_COLORS; i++) {\n colors.push(new THREE.Vector3(1, 1, 1));\n colorPos.push(MAX_COLORS > 1 ? i / (MAX_COLORS - 1) : 0);\n }\n const meshPointPos: THREE.Vector2[] = [];\n const meshPointColor: THREE.Vector3[] = [];\n const meshPointInfluence: number[] = [];\n for (let i = 0; i < MAX_MESH_POINTS; i++) {\n meshPointPos.push(new THREE.Vector2(0.5, 0.5));\n meshPointColor.push(new THREE.Vector3(1, 1, 1));\n meshPointInfluence.push(0.65);\n }\n const lightPos: THREE.Vector3[] = [];\n const lightColor: THREE.Vector3[] = [];\n const lightIntensity: number[] = [];\n for (let i = 0; i < MAX_LIGHTS; i++) {\n lightPos.push(new THREE.Vector3());\n lightColor.push(new THREE.Vector3(1, 1, 1));\n lightIntensity.push(0);\n }\n const bandBounds: THREE.Vector4[] = [];\n const bandParams: THREE.Vector4[] = [];\n const bandParaPow: number[] = [];\n for (let i = 0; i < MAX_NOISE_BANDS; i++) {\n bandBounds.push(new THREE.Vector4());\n bandParams.push(new THREE.Vector4());\n bandParaPow.push(0);\n }\n return {\n // Deformation (vertex)\n uTime: { value: 0 },\n uSpeed: { value: 0.05 },\n uSeed: { value: 0 },\n uDispFreqX: { value: 0.003234 },\n uDispFreqZ: { value: 0.00799 },\n uDispAmount: { value: 6.051 },\n uDetailFreq: { value: 0.04 },\n uDetailAmount: { value: 0 }, // 2nd displacement octave (read only under DETAIL_OCTAVE)\n uTwFreqX: { value: -0.055 },\n uTwFreqY: { value: 0.077 },\n uTwFreqZ: { value: -0.518 },\n uTwPowX: { value: 3.95 },\n uTwPowY: { value: 5.85 },\n uTwPowZ: { value: 6.33 },\n uLoopSeconds: { value: 0 }, // seamless-loop period (read only under the LOOP_MOTION define)\n // Colour + light (fragment)\n uColors: { value: colors },\n uColorPos: { value: colorPos },\n uColorCount: { value: 2 },\n uGradType: { value: 0 },\n uGradAngle: { value: 0 },\n uGradShift: { value: 0.15 },\n uMeshPointPos: { value: meshPointPos },\n uMeshPointColor: { value: meshPointColor },\n uMeshPointInfluence: { value: meshPointInfluence },\n uMeshPointCount: { value: 0 },\n uMeshSoftness: { value: 0.62 },\n uPalette: { value: null },\n uUsePalette: { value: 1 },\n uPaletteRaw: { value: 1 },\n uPaletteScale: { value: new THREE.Vector2(1, 1) },\n uPaletteOffset: { value: new THREE.Vector2(0, 0) },\n uPaletteRotation: { value: 0 },\n uDebug: { value: 0 },\n uSheen: { value: 1 },\n uRoundness: { value: 0.35 },\n uIridescence: { value: 0 },\n uDepthTint: { value: 0 }, // solid-theme depth tint (read only under DEPTH_TINT)\n uDepthTintColor: { value: new THREE.Vector3() },\n uHueShift: { value: 0 },\n uContrast: { value: 1 },\n uSaturation: { value: 1 },\n uFiberCount: { value: 90 },\n uFiberStrength: { value: 0.25 },\n uTexture: { value: 0 },\n uCreaseLight: { value: 0.15 },\n uCreaseSharpness: { value: 2.0 },\n uCreaseSoftness: { value: 1.0 },\n uEdgeFade: { value: 0.06 },\n uEdgeFeather: { value: 0.1 }, // ribbon-edge softness (read only under EDGE_FEATHER)\n uOpacity: { value: 1 },\n uSquared: { value: 1 }, // \"squared\" deep-colour mode: square the colour in-shader (see applyBlendMode)\n uResolution: { value: new THREE.Vector2(1, 1) },\n uAmbient: { value: 0.45 },\n uNumLights: { value: 1 },\n uLightPos: { value: lightPos },\n uLightColor: { value: lightColor },\n uLightIntensity: { value: lightIntensity },\n uNumNoiseBands: { value: 0 },\n uNoiseBandBounds: { value: bandBounds },\n uNoiseBandParams: { value: bandParams },\n uNoiseBandParaPow: { value: bandParaPow },\n // Wireframe thin-line theme (used only by lineFragmentShader)\n uLineAmount: { value: 425 },\n uLineThickness: { value: 1 },\n uLineDerivativePower: { value: 0.95 },\n uMaxWidth: { value: 1232 },\n uClearColor: { value: new THREE.Vector3(1, 1, 1) },\n };\n }\n\n /** Vertex-shader #defines for a wave: TWIST_MOTION (per-wave animated twist wobble) and\n * LOOP_MOTION (scene-level seamless loop). Both select #ifdef-gated code paths; an empty\n * object compiles the default (linear-time) program. */\n private waveDefines(sc: WaveConfig | undefined): Record<string, string> {\n const defines: Record<string, string> = {};\n if (sc?.twistMotion) defines.TWIST_MOTION = \"\";\n if ((this.config.loopSeconds ?? 0) > 0) defines.LOOP_MOTION = \"\";\n if ((sc?.detailAmount ?? 0) !== 0) defines.DETAIL_OCTAVE = \"\";\n if ((sc?.depthTint ?? 0) > 0) defines.DEPTH_TINT = \"\";\n if ((sc?.edgeFeather ?? 0.1) !== 0.1) defines.EDGE_FEATHER = \"\";\n return defines;\n }\n\n private addWave(): void {\n const geo = new WaveGeometry(this.segments);\n // Initialise defines/fragment/blend from the wave this material will represent, so the\n // first refresh() doesn't force a needless program recompile. Falls back to the first wave.\n const sc = this.config.waves[this.waves.length] ?? this.config.waves[0];\n const material = new THREE.ShaderMaterial({\n uniforms: this.makeUniforms(),\n // TWIST_MOTION / LOOP_MOTION select variant vertex-shader paths. Toggled live in refresh().\n defines: this.waveDefines(sc),\n vertexShader,\n // solid theme = surfaceColor shader; wireframe theme = thin-line shader.\n // Swapped live in refresh() when the wave's theme changes.\n fragmentShader: sc?.theme === \"wireframe\" ? lineFragmentShader : fragmentShader,\n transparent: true,\n depthTest: true,\n depthWrite: true,\n side: THREE.DoubleSide,\n });\n // Blending (incl. the squaring blend) is set from the wave's blendMode — see applyBlendMode —\n // so it survives refresh() instead of being a dead constructor flag.\n this.applyBlendMode(material, sc?.blendMode ?? \"squared\");\n const mesh = new THREE.Mesh(geo.geometry, material);\n mesh.frustumCulled = false;\n this.group.add(mesh);\n const palette = new WavePalette(\n (url) => this.createLoopingVideo(url),\n () => {\n this.updatePaletteTextures();\n this.syncVideoPlayback();\n if (!this.running) this.renderOnce();\n },\n );\n this.waves.push({ mesh, material, geometry: geo, palette });\n }\n\n /**\n * Apply config.blendMode to a material. \"squared\" (the default) is the hero blend:\n * CustomBlending with AddEquation, src = SrcColorFactor, dst = ZeroFactor, so the\n * framebuffer result is fragColor² — the squaring deepens the colours into the vivid\n * hero look (without it the wave reads pastel). \"additive\"/\"normal\"/\"multiply\" are\n * authoring overrides. Multiply uses Three's premultiplied-alpha path; the custom\n * fragment shaders premultiply their output when Three injects PREMULTIPLIED_ALPHA.\n * Returns true if material state changed (caller flags needsUpdate).\n */\n private applyBlendMode(material: THREE.ShaderMaterial, mode: BlendMode): boolean {\n // \"squared\" is the deep hero look. It used to be a framebuffer-squaring CustomBlending\n // (src·src, dst×0) — which REPLACES the destination rather than compositing over it, so any\n // semi-transparent pixel (soft ribbon edges, and the large near-edge-on regions at oblique\n // camera angles) wiped the framebuffer's colour AND alpha, punching dark / see-through holes\n // through the background and through other waves. On a transparent page you never saw it; on\n // an opaque background or with overlapping waves it's the \"chunks vanish\" artifact.\n //\n // Fix: do the colour-squaring in the shader (uSquared) and composite it with ordinary\n // premultiplied alpha (NormalBlending). Over an opaque body the result is identical (col²);\n // soft edges now blend into what's behind them instead of erasing it.\n const squared = mode === \"squared\";\n const blending =\n mode === \"additive\"\n ? THREE.AdditiveBlending\n : mode === \"multiply\"\n ? THREE.MultiplyBlending\n : THREE.NormalBlending; // \"squared\" (deep, via uSquared) and \"normal\" both composite\n // Premultiplied so the squared/multiply colour composites correctly (the shaders premultiply\n // their output under the PREMULTIPLIED_ALPHA define Three injects for this).\n const premultipliedAlpha = mode === \"multiply\" || squared;\n material.uniforms.uSquared.value = squared ? 1 : 0;\n if (material.blending === blending && material.premultipliedAlpha === premultipliedAlpha) {\n return false;\n }\n material.blending = blending;\n material.premultipliedAlpha = premultipliedAlpha;\n return true;\n }\n\n private disposeWaves(): void {\n for (const s of this.waves) {\n this.group.remove(s.mesh);\n s.material.dispose();\n s.geometry.dispose();\n s.palette.dispose();\n }\n this.waves = [];\n }\n\n /**\n * Reconcile the wave pool to `waveCount` WITHOUT tearing everything down:\n * keep existing waves (so the compiled shader program is never deleted and\n * re-compiled — that churn can crash some GPU drivers), add/remove only the\n * delta, and resize each geometry to the current quality.\n */\n private buildWaves(): void {\n const target = Math.max(1, this.config.waves.length);\n while (this.waves.length > target) {\n const s = this.waves.pop();\n if (!s) break;\n this.group.remove(s.mesh);\n s.material.dispose();\n s.geometry.dispose();\n s.palette.dispose();\n }\n while (this.waves.length < target) this.addWave();\n\n const segments = this.segments;\n this.waves.forEach((s, i) => {\n s.geometry.resize(segments);\n s.mesh.renderOrder = i;\n });\n this.refresh();\n }\n\n /** Re-read per-frame-independent values from the (mutated) config. */\n refresh(): void {\n this.applyBackground();\n this.applyPost();\n // Once an external driver (orbit / edit gizmo) owns the camera, don't fight it here; the shell\n // (no orbit) applies the saved camera position/target so it matches the authored view.\n if (!this.isCameraExternallyDriven()) {\n const p = this.config.cameraPosition;\n const tg = this.config.cameraTarget;\n this.camera.position.set(p.x, p.y, p.z);\n this.camera.lookAt(tg.x, tg.y, tg.z);\n }\n\n this.waves.forEach((wave, i) => {\n const sc = this.config.waves[i] ?? this.config.waves[this.config.waves.length - 1];\n const u = wave.material.uniforms;\n if (this.applyBlendMode(wave.material, sc.blendMode)) wave.material.needsUpdate = true;\n // Recompile the program when its #define set changes: TWIST_MOTION / DETAIL_OCTAVE / DEPTH_TINT\n // (per wave) and LOOP_MOTION (scene-level). Compare the whole set so any combination is handled.\n const wantDefines = this.waveDefines(sc);\n const curDefines = wave.material.defines ?? {};\n if (Object.keys(wantDefines).sort().join(\",\") !== Object.keys(curDefines).sort().join(\",\")) {\n wave.material.defines = wantDefines;\n wave.material.needsUpdate = true;\n }\n // Swap the fragment shader when this wave's theme changes: solid surfaceColor <->\n // wireframe thin-line. Three recompiles the program on needsUpdate.\n const wantFrag = sc.theme === \"wireframe\" ? lineFragmentShader : fragmentShader;\n if (wave.material.fragmentShader !== wantFrag) {\n wave.material.fragmentShader = wantFrag;\n wave.material.needsUpdate = true;\n }\n\n const stops = [...sc.palette].sort((a, b) => a.pos - b.pos);\n const colorCount = Math.max(1, Math.min(stops.length, MAX_COLORS));\n const colors = u.uColors.value as THREE.Vector3[];\n const colorPos = u.uColorPos.value as number[];\n for (let c = 0; c < MAX_COLORS; c++) {\n const stop = stops[Math.min(c, colorCount - 1)] ?? { color: \"#ffffff\", pos: 0 };\n hexToLinearVec3(stop.color, colors[c]);\n colorPos[c] = stop.pos;\n }\n u.uColorCount.value = colorCount;\n u.uGradType.value =\n sc.gradientType === \"radial\"\n ? 1\n : sc.gradientType === \"conic\"\n ? 2\n : sc.gradientType === \"mesh\"\n ? 3\n : 0;\n u.uGradAngle.value = ((sc.gradientAngle ?? 0) * Math.PI) / 180;\n u.uGradShift.value = sc.gradientShift ?? 0;\n const meshPoints = sc.meshGradientPoints.slice(0, MAX_MESH_POINTS);\n const meshPointPos = u.uMeshPointPos.value as THREE.Vector2[];\n const meshPointColor = u.uMeshPointColor.value as THREE.Vector3[];\n const meshPointInfluence = u.uMeshPointInfluence.value as number[];\n for (let pointIndex = 0; pointIndex < MAX_MESH_POINTS; pointIndex++) {\n const point = meshPoints[pointIndex] ?? meshPoints[meshPoints.length - 1];\n if (!point) continue;\n meshPointPos[pointIndex].set(point.x, point.y);\n hexToLinearVec3(point.color, meshPointColor[pointIndex]);\n meshPointInfluence[pointIndex] = point.influence;\n }\n u.uMeshPointCount.value = meshPoints.length;\n u.uMeshSoftness.value = sc.meshGradientSoftness;\n u.uPaletteScale.value.set(sc.paletteTextureScale?.x ?? 1, sc.paletteTextureScale?.y ?? 1);\n u.uPaletteOffset.value.set(sc.paletteTextureOffset?.x ?? 0, sc.paletteTextureOffset?.y ?? 0);\n u.uPaletteRotation.value = ((sc.paletteTextureRotation ?? 0) * Math.PI) / 180;\n u.uHueShift.value = sc.hueShift;\n u.uContrast.value = sc.colorContrast;\n u.uSaturation.value = sc.colorSaturation;\n // Wireframe thin-line theme params (used only by lineFragmentShader). uClearColor is the\n // between-line colour = the page background (scene), fed in linear space like the palette.\n u.uLineAmount.value = sc.lineAmount ?? 425;\n u.uLineThickness.value = sc.lineThickness ?? 1;\n u.uLineDerivativePower.value = sc.lineDerivativePower ?? 0.95;\n u.uMaxWidth.value = sc.maxWidth ?? 1232;\n hexToLinearVec3(this.config.background, u.uClearColor.value as THREE.Vector3);\n u.uFiberCount.value = sc.fiberCount;\n u.uFiberStrength.value = sc.fiberStrength;\n u.uTexture.value = sc.texture;\n u.uCreaseLight.value = sc.creaseLight;\n u.uCreaseSharpness.value = sc.creaseSharpness;\n u.uCreaseSoftness.value = sc.creaseSoftness;\n u.uSheen.value = sc.sheen ?? 1;\n u.uRoundness.value = sc.roundness ?? 0.35;\n u.uIridescence.value = sc.iridescence ?? 0;\n u.uDepthTint.value = sc.depthTint ?? 0;\n hexToLinearVec3(sc.depthTintColor ?? \"#0a2540\", u.uDepthTintColor.value as THREE.Vector3);\n u.uEdgeFade.value = sc.edgeFade;\n u.uEdgeFeather.value = sc.edgeFeather ?? 0.1;\n // Lights + ambient are scene-level (shared by every wave).\n const lights = this.config.lights ?? [];\n u.uAmbient.value = this.config.ambient ?? 0.45;\n u.uNumLights.value = Math.min(lights.length, MAX_LIGHTS);\n const lPos = u.uLightPos.value as THREE.Vector3[];\n const lCol = u.uLightColor.value as THREE.Vector3[];\n const lInt = u.uLightIntensity.value as number[];\n for (let li = 0; li < MAX_LIGHTS; li++) {\n const light = lights[li];\n if (light) {\n lPos[li].set(light.position.x, light.position.y, light.position.z);\n hexToLinearVec3(light.color, lCol[li]);\n lInt[li] = light.intensity;\n } else {\n lInt[li] = 0;\n }\n }\n // Noise bands (per-region fiber overrides) — per wave\n const bands = sc.noiseBands ?? [];\n u.uNumNoiseBands.value = Math.min(bands.length, MAX_NOISE_BANDS);\n const bBounds = u.uNoiseBandBounds.value as THREE.Vector4[];\n const bParams = u.uNoiseBandParams.value as THREE.Vector4[];\n const bPara = u.uNoiseBandParaPow.value as number[];\n for (let bi = 0; bi < MAX_NOISE_BANDS; bi++) {\n const band = bands[bi];\n if (band) {\n bBounds[bi].set(band.startX, band.endX, band.startY, band.endY);\n bParams[bi].set(band.feather, band.strength, band.frequency, band.colorAttenuation);\n bPara[bi] = band.parabolaPower;\n }\n }\n // Deformation (absolute per wave)\n u.uSpeed.value = sc.speed;\n u.uSeed.value = sc.seed;\n u.uLoopSeconds.value = this.config.loopSeconds ?? 0; // scene-level; shared by every wave\n u.uDispFreqX.value = sc.displaceFrequency.x;\n u.uDispFreqZ.value = sc.displaceFrequency.y;\n u.uDispAmount.value = sc.displaceAmount;\n u.uDetailFreq.value = sc.detailFrequency ?? 0.04;\n u.uDetailAmount.value = sc.detailAmount ?? 0;\n u.uTwFreqX.value = sc.twistFrequency.x;\n u.uTwFreqY.value = sc.twistFrequency.y;\n u.uTwFreqZ.value = sc.twistFrequency.z;\n u.uTwPowX.value = sc.twistPower.x;\n u.uTwPowY.value = sc.twistPower.y;\n u.uTwPowZ.value = sc.twistPower.z;\n // Mesh transform — each wave's ABSOLUTE scale / rotation / position, applied via\n // modelMatrix using THREE's Euler XYZ order so the on-screen orientation matches the\n // authored view.\n wave.mesh.scale.set(sc.scale.x, sc.scale.y, sc.scale.z);\n wave.mesh.rotation.set(\n THREE.MathUtils.degToRad(sc.rotation.x),\n THREE.MathUtils.degToRad(sc.rotation.y),\n THREE.MathUtils.degToRad(sc.rotation.z),\n );\n wave.mesh.position.set(sc.position.x, sc.position.y, sc.position.z);\n u.uOpacity.value = sc.opacity;\n });\n\n this.updatePaletteTextures();\n this.syncVideoPlayback();\n\n // Whole-wave mirror (world-space flip ≈ screen flip for the near-frontal camera).\n this.group.scale.set(this.config.mirrorH ? -1 : 1, this.config.mirrorV ? -1 : 1, 1);\n\n this.onAfterRefresh();\n if (!this.running) this.renderOnce();\n }\n\n /** Point every wave's palette sampler at its own WavePalette texture (each rebuilt only\n * when that wave's palette actually changes — see WavePalette.apply). */\n private updatePaletteTextures(): void {\n this.waves.forEach((wave, i) => {\n const sc = this.config.waves[i] ?? this.config.waves[this.config.waves.length - 1];\n wave.palette.apply(sc, wave.material.uniforms);\n });\n }\n\n /** Dev: 0 = normal, 1 = visualise the crease value, 2 = visualise derivative normal. */\n setDebug(v: number): void {\n for (const s of this.waves) s.material.uniforms.uDebug.value = v;\n this.renderOnce();\n }\n\n /** Rebuild geometry + waves (call when waveCount or quality changes). */\n rebuild(): void {\n this.buildWaves();\n }\n\n private applyBackground(): void {\n if (this.config.transparentBackground) {\n this.scene.background = null;\n this.renderer.setClearColor(0x000000, 0);\n return;\n }\n\n const matte = new THREE.Color(this.config.background);\n this.renderer.setClearColor(matte, 1);\n if (this.config.backgroundMode === \"color\") {\n this.applyColorBackground(matte);\n return;\n }\n // Live video is redrawn every frame, so cap its staging canvas near 1080p. Still images\n // and procedural maps retain the full 4K-capable background texture path.\n const { width, height } = this.backgroundCanvasSize(\n this.config.backgroundVideoUrl ? 2048 : 4096,\n );\n if (this.config.backgroundMode === \"gradient\") this.applyGradientBackground(width, height);\n else this.applyImageBackground(matte, width, height);\n }\n\n private applyColorBackground(matte: THREE.Color): void {\n this.clearBackgroundVideo();\n this.backgroundTexture?.dispose();\n this.backgroundTexture = undefined;\n this.backgroundSig = \"\";\n this.scene.background = matte;\n }\n\n private applyGradientBackground(width: number, height: number): void {\n this.clearBackgroundVideo();\n const gradType = this.config.backgroundGradientType;\n if (gradType === \"mesh\") {\n const pts = this.config.backgroundMeshPoints ?? [];\n const softness = this.config.backgroundMeshSoftness ?? 0.62;\n const ptSig = pts\n .map((p) => `${p.color}@${p.x.toFixed(3)},${p.y.toFixed(3)},${p.influence.toFixed(3)}`)\n .join(\",\");\n const sig = [\"mesh\", softness.toFixed(3), ptSig, width, height].join(\"|\");\n if (sig !== this.backgroundSig || !this.backgroundTexture) {\n const canvas = buildBackgroundMeshCanvas(pts, softness, width, height);\n this.backgroundTexture?.dispose();\n this.backgroundTexture = canvasToTexture(canvas);\n this.backgroundSig = sig;\n }\n this.scene.background = this.backgroundTexture;\n return;\n }\n const source = this.config.backgroundGradientSource ?? \"stops\";\n const def = PALETTE_MAPS[source];\n const stops =\n source !== \"stops\" && def?.kind === \"gradient\" && def.stops\n ? def.stops\n : this.config.backgroundPalette;\n const stopSig = stops.map((stop) => `${stop.color}@${stop.pos.toFixed(3)}`).join(\",\");\n const sig = [\n \"gradient\",\n source,\n gradType,\n this.config.backgroundGradientAngle,\n stopSig,\n width,\n height,\n ].join(\"|\");\n if (sig !== this.backgroundSig || !this.backgroundTexture) {\n const canvas = buildBackgroundGradientCanvas({\n stops,\n type: gradType,\n angle: this.config.backgroundGradientAngle,\n width,\n height,\n });\n this.backgroundTexture?.dispose();\n this.backgroundTexture = canvasToTexture(canvas);\n this.backgroundSig = sig;\n }\n this.scene.background = this.backgroundTexture;\n }\n\n /** Image mode: a live video, a user-loaded image, or a built-in map. `matte` shows while an\n * async source is still loading. */\n private applyImageBackground(matte: THREE.Color, width: number, height: number): void {\n const fit = this.config.backgroundImageFit ?? \"cover\";\n const zoom = this.config.backgroundImageZoom ?? 1;\n const position = this.config.backgroundImagePosition ?? { x: 0, y: 0 };\n const videoUrl = this.config.backgroundVideoUrl;\n const customUrl = this.config.backgroundImageUrl;\n let source: CanvasImageSource;\n let sourceWidth: number;\n let sourceHeight: number;\n let sourceSig: string;\n if (videoUrl) {\n this.ensureBackgroundVideo(videoUrl);\n if (!this.backgroundVideo || this.backgroundVideo.readyState < 2) {\n this.scene.background = matte;\n return;\n }\n source = this.backgroundVideo;\n sourceWidth = this.backgroundVideo.videoWidth;\n sourceHeight = this.backgroundVideo.videoHeight;\n sourceSig = `video|${videoUrl}`;\n } else if (customUrl) {\n this.clearBackgroundVideo();\n if (\n !this.backgroundImage ||\n this.backgroundImageUrl !== customUrl ||\n !this.backgroundImage.complete\n ) {\n this.loadBackgroundImage(customUrl);\n this.scene.background = matte;\n return;\n }\n source = this.backgroundImage;\n sourceWidth = this.backgroundImage.naturalWidth;\n sourceHeight = this.backgroundImage.naturalHeight;\n sourceSig = `custom|${customUrl}`;\n } else {\n this.clearBackgroundVideo();\n const imageSource = this.config.backgroundImageSource ?? \"vaporwave\";\n const canvas =\n imageSource === \"hero\"\n ? buildHeroPaletteCanvas()\n : PALETTE_MAPS[imageSource]?.kind === \"image\"\n ? paletteMapCanvas(PALETTE_MAPS[imageSource], Math.max(width, height))\n : null;\n if (!canvas) {\n this.scene.background = matte;\n return;\n }\n source = canvas;\n sourceWidth = canvas.width;\n sourceHeight = canvas.height;\n sourceSig = `map|${imageSource}`;\n }\n\n const sig = [\n \"image\",\n sourceSig,\n fit,\n zoom,\n position.x,\n position.y,\n this.config.background,\n width,\n height,\n ].join(\"|\");\n if (sig !== this.backgroundSig || !this.backgroundTexture) {\n const canvas = buildBackgroundImageCanvas(\n source,\n sourceWidth,\n sourceHeight,\n width,\n height,\n fit,\n this.config.background,\n zoom,\n position.x,\n position.y,\n );\n this.backgroundTexture?.dispose();\n this.backgroundTexture = canvasToTexture(canvas);\n this.backgroundSig = sig;\n this.backgroundVideoCanvas = videoUrl ? canvas : undefined;\n }\n this.scene.background = this.backgroundTexture;\n }\n\n private backgroundCanvasSize(maxRequestedEdge = 4096): { width: number; height: number } {\n const rawWidth = this.outputSize?.width ?? Math.max(1, this.container.clientWidth);\n const rawHeight = this.outputSize?.height ?? Math.max(1, this.container.clientHeight);\n const maxEdge = Math.min(maxRequestedEdge, this.renderer.capabilities.maxTextureSize);\n const scale = Math.min(1, maxEdge / Math.max(rawWidth, rawHeight));\n return {\n width: Math.max(1, Math.round(rawWidth * scale)),\n height: Math.max(1, Math.round(rawHeight * scale)),\n };\n }\n\n private loadBackgroundImage(url: string): void {\n if (this.backgroundImageUrl === url || this.failedBackgroundImageUrl === url) return;\n this.backgroundImageUrl = url;\n this.backgroundImage = undefined;\n const image = new Image();\n image.decoding = \"async\";\n if (!url.startsWith(\"data:\") && !url.startsWith(\"blob:\")) image.crossOrigin = \"anonymous\";\n image.addEventListener(\n \"load\",\n () => {\n if (this.config.backgroundImageUrl !== url) return;\n this.backgroundImage = image;\n this.failedBackgroundImageUrl = \"\";\n this.backgroundSig = \"\";\n this.applyBackground();\n if (!this.running) this.renderOnce();\n },\n { once: true },\n );\n image.addEventListener(\n \"error\",\n () => {\n if (this.config.backgroundImageUrl !== url) return;\n this.failedBackgroundImageUrl = url;\n this.backgroundSig = \"\";\n this.applyBackground();\n if (!this.running) this.renderOnce();\n },\n { once: true },\n );\n image.src = url;\n }\n\n private ensureBackgroundVideo(url: string): void {\n if (this.backgroundVideoUrl === url || this.failedBackgroundVideoUrl === url) return;\n this.clearBackgroundVideo();\n this.backgroundVideoUrl = url;\n const video = this.createLoopingVideo(url);\n video.addEventListener(\n \"loadeddata\",\n () => {\n if (this.config.backgroundVideoUrl !== url) return;\n this.backgroundVideo = video;\n this.failedBackgroundVideoUrl = \"\";\n this.backgroundSig = \"\";\n this.applyBackground();\n this.syncVideoPlayback();\n if (!this.running) this.renderOnce();\n },\n { once: true },\n );\n video.addEventListener(\n \"error\",\n () => {\n if (this.config.backgroundVideoUrl !== url) return;\n this.failedBackgroundVideoUrl = url;\n this.backgroundSig = \"\";\n this.applyBackground();\n if (!this.running) this.renderOnce();\n },\n { once: true },\n );\n this.backgroundVideo = video;\n video.load();\n }\n\n private clearBackgroundVideo(): void {\n if (this.backgroundVideo) {\n this.backgroundVideo.pause();\n this.backgroundVideo.removeAttribute(\"src\");\n this.backgroundVideo.load();\n }\n this.backgroundVideo = undefined;\n this.backgroundVideoUrl = \"\";\n this.failedBackgroundVideoUrl = \"\";\n this.backgroundVideoCanvas = undefined;\n }\n\n private createLoopingVideo(url: string): HTMLVideoElement {\n const video = document.createElement(\"video\");\n video.muted = true;\n video.defaultMuted = true;\n video.loop = true;\n video.playsInline = true;\n video.preload = \"auto\";\n if (!url.startsWith(\"data:\") && !url.startsWith(\"blob:\")) video.crossOrigin = \"anonymous\";\n video.src = url;\n return video;\n }\n\n private syncVideoPlayback(): void {\n const backgroundActive =\n this.running &&\n !this.config.transparentBackground &&\n this.config.backgroundMode === \"image\" &&\n !!this.config.backgroundVideoUrl;\n if (this.backgroundVideo) {\n if (backgroundActive) void this.backgroundVideo.play().catch(() => {});\n else this.backgroundVideo.pause();\n }\n // Palette videos are per wave.\n this.waves.forEach((wave, i) => {\n const sc = this.config.waves[i] ?? this.config.waves[this.config.waves.length - 1];\n wave.palette.syncPlayback(sc, this.running);\n });\n }\n\n private updateBackgroundVideoFrame(): void {\n const video = this.backgroundVideo;\n const canvas = this.backgroundVideoCanvas;\n if (!video || !canvas || video.readyState < 2 || !this.backgroundTexture) return;\n const position = this.config.backgroundImagePosition ?? { x: 0, y: 0 };\n drawBackgroundMediaFrame(\n canvas,\n video,\n video.videoWidth,\n video.videoHeight,\n this.config.backgroundImageFit ?? \"cover\",\n this.config.background,\n this.config.backgroundImageZoom ?? 1,\n position.x,\n position.y,\n );\n this.backgroundTexture.needsUpdate = true;\n }\n\n private applyPost(): void {\n const u = this.postPass.uniforms;\n u.uBlurAmount.value = this.config.blur;\n u.uGrainAmount.value = this.config.grain;\n u.uBlurSamples.value = Math.round(this.config.blurSamples ?? 6);\n this.applyBloom();\n }\n\n /** Insert / tune / remove the bloom pass. strength 0 removes it from the composer entirely, so\n * cost and pixels are identical to bloom-off; the pass (and its mip-chain render targets) is\n * created lazily the first time bloom is enabled and disposed when turned back off. It sits\n * right after the scene RenderPass so it blooms the wave before the grain/blur pass. */\n private applyBloom(): void {\n const strength = this.config.bloomStrength ?? 0;\n if (strength > 0) {\n if (!this.bloomPass) {\n const size = this.renderer.getDrawingBufferSize(new THREE.Vector2());\n this.bloomPass = new UnrealBloomPass(\n size,\n strength,\n this.config.bloomRadius ?? 0.4,\n this.config.bloomThreshold ?? 0.85,\n );\n this.composer.insertPass(this.bloomPass, 1); // after RenderPass, before postPass/OutputPass\n }\n this.bloomPass.strength = strength;\n this.bloomPass.radius = this.config.bloomRadius ?? 0.4;\n this.bloomPass.threshold = this.config.bloomThreshold ?? 0.85;\n } else if (this.bloomPass) {\n this.composer.removePass(this.bloomPass);\n this.bloomPass.dispose();\n this.bloomPass = undefined;\n }\n }\n\n private onResize = (): void => {\n this.resize();\n };\n\n private onContextLost = (e: Event): void => {\n e.preventDefault(); // tell the browser we'll recover → no \"Aw, Snap\" crash\n cancelAnimationFrame(this.rafId);\n this.running = false;\n };\n\n private onContextRestored = (): void => {\n this.disposeWaves(); // old GPU resources are invalid on a fresh context (per-wave palettes too)\n this.backgroundTexture?.dispose();\n this.backgroundTexture = undefined;\n this.backgroundSig = \"\";\n this.buildWaves();\n this.resize();\n this.updateRunning();\n };\n\n resize(): void {\n const w = this.outputSize?.width ?? Math.max(1, this.container.clientWidth);\n const h = this.outputSize?.height ?? Math.max(1, this.container.clientHeight);\n const dpr = this.outputSize ? 1 : Math.min(window.devicePixelRatio || 1, this.config.dprMax);\n this.renderer.setPixelRatio(dpr);\n this.renderer.setSize(w, h, !this.outputSize);\n if (this.outputSize) {\n // setSize(..., false) preserves the exact backing buffer without writing fixed CSS\n // pixel dimensions. Keep the canvas stretched to the visible aspect-ratio frame.\n this.renderer.domElement.style.width = \"100%\";\n this.renderer.domElement.style.height = \"100%\";\n }\n this.composer.setPixelRatio(dpr);\n this.composer.setSize(w, h);\n const dw = w * dpr;\n const dh = h * dpr;\n (this.postPass.uniforms.uResolution.value as THREE.Vector2).set(dw, dh);\n for (const s of this.waves) {\n (s.material.uniforms.uResolution.value as THREE.Vector2).set(dw, dh);\n }\n // The responsive ortho framing: the frustum = the canvas in DEVICE pixels (1 world unit =\n // 1px at zoom 1). Combined with the ×10 mesh scale, the wave overflows the frame.\n this.camera.left = -dw / 2;\n this.camera.right = dw / 2;\n this.camera.top = dh / 2;\n this.camera.bottom = -dh / 2;\n this.applyZoom(); // responsive ortho zoom (maps FRAME_W world units onto the canvas)\n this.applyBackground();\n this.onAfterResize();\n if (!this.running) this.renderOnce();\n }\n\n /** Set an exact output buffer while CSS scales the canvas into the on-screen export frame. */\n setOutputSize(width: number, height: number): void {\n this.outputSize = {\n width: THREE.MathUtils.clamp(Math.round(width), 64, 8192),\n height: THREE.MathUtils.clamp(Math.round(height), 64, 8192),\n };\n this.resize();\n }\n\n start(): void {\n this.started = true;\n this.updateRunning();\n }\n\n stop(): void {\n this.started = false;\n this.updateRunning();\n }\n\n private onMotionChange = (e: MediaQueryListEvent): void => {\n this.reducedMotion = this.respectReducedMotion && e.matches;\n this.updateRunning();\n };\n\n private onVisibilityChange = (): void => {\n this.pageVisible = document.visibilityState === \"visible\";\n this.updateRunning();\n };\n\n private updateRunning(): void {\n const shouldAnimate =\n this.started &&\n this.visible &&\n this.pageVisible &&\n !this.config.paused &&\n !this.reducedMotion;\n\n if (shouldAnimate && !this.running) {\n this.running = true;\n this.clock.start();\n this.clock.getDelta();\n this.rafId = requestAnimationFrame(this.loop);\n } else if (!shouldAnimate && this.running) {\n this.running = false;\n cancelAnimationFrame(this.rafId);\n }\n // When not animating (paused / reduced-motion / static export) show the FULL frame, not a\n // frozen mid-ease, by forcing introTimeRamp = 1.\n if (!this.running) {\n this.introTimeRamp = 1;\n this.renderOnce();\n }\n this.syncVideoPlayback();\n }\n\n private loop = (): void => {\n if (!this.running) return;\n this.time += this.clock.getDelta();\n if (this.introTimeRamp < 1) this.introTimeRamp = Math.min(1, this.introTimeRamp + 0.016); // ~1s to full at 60fps\n this.renderOnce();\n this.rafId = requestAnimationFrame(this.loop);\n };\n\n /** Advance the per-frame clock uniforms (geometry itself is static). Time model:\n * time = elapsed·introTimeRamp + timeOffset — the ramp eases the animation in on load. */\n private updateTime(): void {\n // Skip the ease-in when asked: a fresh renderer (first load, or the studio's HMR hot-swap)\n // starts introTimeRamp at 0, and replaying the ramp on every save reads as a \"speed up\" when\n // you tab back. The studio passes skipIntroRamp in dev; prod builds + the embed keep the ease-in.\n const ramp = this.config.introRamp === false || this.skipIntroRamp ? 1 : this.introTimeRamp;\n const t = this.time * ramp + (this.config.timeOffset ?? 0);\n // Indexed loop (no per-frame closure) — this runs every frame.\n for (let i = 0; i < this.waves.length; i++) {\n const u = this.waves[i].material.uniforms;\n u.uTime.value = t;\n // Palette drift: flow the colour along the ribbon independently of the geometry by drifting\n // uPaletteOffset over time. Only touch it when nonzero, so refresh()'s static base offset —\n // and every drift-off preset — is left byte-for-byte unchanged.\n const sc = this.config.waves[i] ?? this.config.waves[this.config.waves.length - 1];\n const dx = sc.paletteDriftX ?? 0;\n const dy = sc.paletteDriftY ?? 0;\n if (dx !== 0 || dy !== 0) {\n const base = sc.paletteTextureOffset;\n (u.uPaletteOffset.value as THREE.Vector2).set(base.x + dx * t, base.y + dy * t);\n }\n }\n this.postPass.uniforms.uTime.value = t;\n }\n\n /** Render exactly one frame at the current time. */\n renderOnce(): void {\n this.updateBackgroundVideoFrame();\n this.updateTime();\n this.updateClipPlanes(); // keep near/far bracketing the scene so no camera angle clips the wave\n this.composer.render();\n // Editor overlays (gizmo/helpers + camera-rig minimap) draw on top of the composed frame —\n // the studio subclass plugs them in here; the base renders nothing extra.\n this.onAfterRenderFrame();\n }\n\n /** Re-evaluate play/pause after `config.paused` changes. */\n refreshPlayback(): void {\n this.updateRunning();\n }\n\n /** Jump the camera to the config's authored framing (cameraPosition / cameraTarget /\n * cameraZoom). Called on whole-config swaps (preset / reset / randomize / import). The base\n * applies the pose directly; the studio subclass overrides it to also drive the orbit target\n * and sync the panel. Hook ⑤. */\n protected applyCameraFromConfig(): void {\n const p = this.config.cameraPosition;\n const tg = this.config.cameraTarget;\n this.camera.position.set(p.x, p.y, p.z);\n this.camera.up.set(0, 1, 0);\n this.camera.lookAt(tg.x, tg.y, tg.z);\n this.applyZoom();\n if (!this.running) this.renderOnce();\n }\n\n // ---- Editor hook points (no-ops in the base; the studio subclass overrides them) ----\n\n /** Hook ①: true while orbit or an edit gizmo owns the camera, so refresh() won't reset it. */\n protected isCameraExternallyDriven(): boolean {\n return false;\n }\n\n /** Hook ②: called at the end of refresh(), before the trailing renderOnce(). */\n protected onAfterRefresh(): void {}\n\n /** Hook ③: called at the end of renderOnce(), after the composed frame is drawn. */\n protected onAfterRenderFrame(): void {}\n\n /** Hook ④: called at the end of resize(), before the trailing renderOnce(). */\n protected onAfterResize(): void {}\n\n /** Responsive ortho zoom: COVER the FRAME_W × FRAME_H reference frame onto the canvas so the\n * wave frames the same at any size/aspect/dpr (only the cropped margin differs), times the\n * user's cameraZoom. `max(...)` = cover (fill both axes, crop overflow); `min(...)` would be\n * contain (fit with letterbox bands). Cover keeps the wave filling the frame on every screen. */\n protected applyZoom(): void {\n const dw = this.camera.right - this.camera.left; // device px (set in resize)\n const dh = this.camera.top - this.camera.bottom;\n this.camera.zoom = Math.max(dw / FRAME_W, dh / FRAME_H) * (this.config.cameraZoom ?? 1);\n this.camera.updateProjectionMatrix();\n }\n\n /** Fit the orthographic near/far planes to the scene before every render, so no part of a wave\n * is ever clipped as the camera orbits / dollies / pans (or when waves are added or scaled).\n *\n * The camera is *constructed* with fixed 1..10000 planes that only suit the authored hero\n * framing; once the view moves, the wave's depth extent along the view axis easily crosses\n * them and the GPU hard-slices the geometry along a flat plane — chunks of the wave vanish.\n *\n * The in-shader twist rotates each vertex about its LOCAL origin, so it can never move a vertex\n * further from that origin than the base geometry already sits. A sphere at each mesh's origin\n * (radius = base extent × the mesh's largest world scale, plus the Y displacement, ×1.2 slack)\n * therefore safely contains the fully-deformed wave. We union those, then bracket the union\n * along the view axis with a margin. Bracketing to the scene (rather than a fixed huge slab)\n * keeps clip-space depth scene-normalised — so the wireframe theme's depth fade, which reads\n * gl_Position.z, looks the same at any camera distance instead of washing out. Runs each frame;\n * it's a handful of vector ops over 1–8 meshes with no allocation. */\n private updateClipPlanes(): void {\n this.clipBox.makeEmpty();\n for (const wave of this.waves) {\n const mesh = wave.mesh;\n mesh.updateWorldMatrix(true, false);\n const bs = mesh.geometry.boundingSphere;\n if (!bs) continue;\n // The twist pivot (the mesh's local origin) in world space = the safe sphere's centre.\n const center = this.clipTmpA.setFromMatrixPosition(mesh.matrixWorld);\n const disp = Math.abs(Number(wave.material.uniforms.uDispAmount.value) || 0);\n const localRadius = bs.center.length() + bs.radius + disp;\n const radius = localRadius * mesh.matrixWorld.getMaxScaleOnAxis() * 1.2;\n // Enclose this wave's sphere by adding its axis-aligned bounding cube corners.\n this.clipBox.expandByPoint(this.clipTmpB.copy(center).addScalar(radius));\n this.clipBox.expandByPoint(this.clipTmpB.copy(center).addScalar(-radius));\n }\n if (this.clipBox.isEmpty()) return;\n this.clipBox.getBoundingSphere(this.clipSphere);\n const viewDir = this.camera.getWorldDirection(this.clipTmpA); // normalised view axis\n const centerDepth = this.clipTmpB\n .copy(this.clipSphere.center)\n .sub(this.camera.position)\n .dot(viewDir);\n const radius = this.clipSphere.radius;\n const margin = radius * 0.25 + 10;\n // Orthographic: a negative near is legal — the slab may extend behind the camera origin.\n this.camera.near = centerDepth - radius - margin;\n this.camera.far = centerDepth + radius + margin;\n this.camera.updateProjectionMatrix();\n }\n\n /** A world-space position delta that drops a duplicated wave into open frame space beside the\n * one it was copied from — screen-left and a touch down, sized to the visible frame — instead\n * of hidden exactly on top of it or (for the hero, which fills the right of the frame) pushed\n * off-frame. Camera-relative: it's \"left on screen\" no matter how the view is rotated/zoomed,\n * because it's built from the camera's right/up axes and the frame's visible world size. */\n\n async captureImage(mime: string, transparent = true, quality?: number): Promise<Blob> {\n const prev = this.config.transparentBackground;\n if (transparent !== prev) {\n this.config.transparentBackground = transparent;\n this.applyBackground();\n }\n let blob: Blob | null = null;\n try {\n this.capturing = true;\n this.renderOnce();\n blob = await new Promise<Blob | null>((resolve) =>\n this.canvas.toBlob(resolve, mime, quality),\n );\n } finally {\n this.capturing = false;\n if (transparent !== prev) {\n this.config.transparentBackground = prev;\n this.applyBackground();\n }\n this.renderOnce();\n }\n if (!blob || blob.type !== mime) throw new Error(`Failed to capture ${mime}`);\n return blob;\n }\n\n captureStream(fps = 60): MediaStream {\n return this.canvas.captureStream(fps);\n }\n\n get canvas(): HTMLCanvasElement {\n return this.renderer.domElement;\n }\n\n getConfig(): StudioConfig {\n return this.config;\n }\n\n setConfig(config: StudioConfig): void {\n const next = ensureStudioConfig(config);\n const structural =\n next.waves.length !== this.waves.length || next.quality !== this.config.quality;\n this.config = next;\n if (structural) this.rebuild();\n else this.refresh();\n // A whole new config (preset/reset/randomize/import) carries its own authored framing —\n // apply it even in the studio, where refresh() leaves the camera to orbit. Without this,\n // selecting a preset updated the wave but kept the previous camera (wrong framing).\n this.applyCameraFromConfig();\n }\n\n dispose(): void {\n cancelAnimationFrame(this.rafId);\n this.running = false;\n this.resizeObserver.disconnect();\n this.intersectionObserver.disconnect();\n this.motionQuery.removeEventListener(\"change\", this.onMotionChange);\n document.removeEventListener(\"visibilitychange\", this.onVisibilityChange);\n this.renderer.domElement.removeEventListener(\"webglcontextlost\", this.onContextLost);\n this.renderer.domElement.removeEventListener(\"webglcontextrestored\", this.onContextRestored);\n this.clearBackgroundVideo();\n this.backgroundTexture?.dispose();\n for (const s of this.waves) {\n s.material.dispose();\n s.geometry.dispose();\n s.palette.dispose();\n }\n this.bloomPass?.dispose();\n this.composer.dispose();\n this.renderer.dispose();\n this.renderer.domElement.remove();\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"WaveRenderer.js","names":[],"sources":["../../src/renderer/WaveRenderer.ts"],"sourcesContent":["import * as THREE from \"three\";\nimport { EffectComposer } from \"three/addons/postprocessing/EffectComposer.js\";\nimport { RenderPass } from \"three/addons/postprocessing/RenderPass.js\";\nimport { OutputPass } from \"three/addons/postprocessing/OutputPass.js\";\nimport { ShaderPass } from \"three/addons/postprocessing/ShaderPass.js\";\nimport { UnrealBloomPass } from \"three/addons/postprocessing/UnrealBloomPass.js\";\nimport {\n vertexShader,\n fragmentShader,\n lineFragmentShader,\n postVertexShader,\n postFragmentShader,\n} from \"./shaders\";\nimport { WaveGeometry } from \"./WaveGeometry\";\nimport {\n buildPaletteTexture,\n configurePaletteTexture,\n paletteSignature,\n PALETTE_MAPS,\n paletteMapCanvas,\n canvasToTexture,\n loadPaletteImage,\n buildBackgroundGradientCanvas,\n buildBackgroundMeshCanvas,\n buildBackgroundImageCanvas,\n drawBackgroundMediaFrame,\n} from \"./palette\";\nimport { buildHeroPaletteCanvas, buildHeroPaletteTexture } from \"./heroPalette\";\nimport {\n MAX_COLORS,\n MAX_LIGHTS,\n MAX_MESH_POINTS,\n MAX_NOISE_BANDS,\n ensureStudioConfig,\n} from \"../config/model\";\nimport type { StudioConfig, WaveConfig, BlendMode } from \"../config/model\";\n\nconst BASE_SEGMENTS = 220; // base segment count along the ribbon; denser = smoother (scaled down per wave — see get segments)\n\n/** Reference frame (world units) the orthographic camera fills at cameraZoom 1. The wave is\n * framed by COVERING this FRAME_W × FRAME_H rectangle (centred on cameraTarget) into the canvas\n * — scaled to fill both dimensions, cropping the aspect overflow — so a given cameraZoom /\n * cameraTarget frames the wave the SAME at any canvas size or aspect (only the cropped margin\n * differs). FRAME_H = FRAME_W / (16/9) makes the reference a 16:9 rectangle; for canvases wider\n * than that the width binds, narrower ones zoom in to fill instead of\n * showing empty bands. This is what makes a saved preset reproduce on anyone's screen. */\nexport const FRAME_W = 1333;\nexport const FRAME_H = 750;\n\nexport interface WaveRendererOptions {\n /** Honor prefers-reduced-motion by freezing animation. Default true. */\n respectReducedMotion?: boolean;\n /**\n * Skip the intro time-ramp (the ~1s ease-in of animation on load), rendering at full speed\n * immediately. The studio passes `import.meta.env.DEV` here so a fresh renderer on every HMR\n * hot-swap doesn't replay the ease-in (which reads as a \"speed up\" when you tab back). Default\n * false — production embeds keep the ease-in. Ignored while paused (a paused frame is always the\n * full frame). Default false.\n */\n skipIntroRamp?: boolean;\n}\n\n/**\n * Per-wave 2D palette texture (+ optional looping video). One instance per wave, so each\n * wave carries its own palette. Guarded by a signature so it only rebuilds when that wave's\n * palette actually changes (not every refresh).\n */\nclass WavePalette {\n texture?: THREE.Texture;\n private sig = \"\";\n private video?: HTMLVideoElement;\n private videoUrl = \"\";\n private failedUrl = \"\";\n\n constructor(\n private readonly makeVideo: (url: string) => HTMLVideoElement,\n private readonly onVideoReady: () => void,\n ) {}\n\n /** Rebuild this wave's palette texture from its config (video / custom image / stops /\n * built-in map / hero LUT) and point the wave's palette uniforms at it. */\n apply(cfg: WaveConfig, uniforms: Record<string, THREE.IUniform>): void {\n const videoUrl = cfg.paletteVideoUrl;\n const url = cfg.paletteImageUrl;\n const source = cfg.paletteSource ?? \"hero\";\n let sig: string;\n let build: () => THREE.Texture;\n if (videoUrl) {\n this.ensureVideo(videoUrl);\n if (this.video?.readyState && this.video.readyState >= 2) {\n sig = \"video|\" + videoUrl;\n build = () => configurePaletteTexture(new THREE.VideoTexture(this.video!));\n } else {\n sig = \"video-loading|\" + videoUrl;\n build = () => buildHeroPaletteTexture();\n }\n } else if (url) {\n this.clearVideo();\n sig = \"url|\" + url;\n build = () => loadPaletteImage(url);\n } else if (source === \"stops\") {\n this.clearVideo();\n const opts = {\n stops: cfg.palette,\n edgeColor: cfg.paletteEdgeColor ?? \"#8e9dff\",\n edgeAmount: cfg.paletteEdgeAmount ?? 0.3,\n };\n sig = \"stops|\" + paletteSignature(opts);\n build = () => buildPaletteTexture(opts);\n } else if (PALETTE_MAPS[source]) {\n this.clearVideo();\n sig = \"map|\" + source;\n build = () => canvasToTexture(paletteMapCanvas(PALETTE_MAPS[source]));\n } else {\n this.clearVideo();\n sig = \"hero\";\n build = () => buildHeroPaletteTexture();\n }\n if (sig !== this.sig || !this.texture) {\n this.texture?.dispose();\n this.texture = build();\n this.sig = sig;\n }\n uniforms.uPalette.value = this.texture;\n uniforms.uUsePalette.value = cfg.usePaletteTexture === false ? 0 : 1;\n uniforms.uPaletteRaw.value = 1;\n }\n\n /** Play/pause this wave's palette video with the render loop. */\n syncPlayback(cfg: WaveConfig, running: boolean): void {\n const v = this.video;\n if (!v) return;\n const active =\n running &&\n cfg.gradientType !== \"mesh\" &&\n cfg.usePaletteTexture !== false &&\n !!cfg.paletteVideoUrl;\n if (active) void v.play().catch(() => {});\n else v.pause();\n }\n\n private ensureVideo(url: string): void {\n if (this.videoUrl === url || this.failedUrl === url) return;\n this.clearVideo();\n this.videoUrl = url;\n const video = this.makeVideo(url);\n video.addEventListener(\n \"loadeddata\",\n () => {\n this.video = video;\n this.failedUrl = \"\";\n this.sig = \"\";\n this.onVideoReady();\n },\n { once: true },\n );\n video.addEventListener(\n \"error\",\n () => {\n this.failedUrl = url;\n this.sig = \"\";\n this.onVideoReady();\n },\n { once: true },\n );\n this.video = video;\n video.load();\n }\n\n private clearVideo(): void {\n if (this.video) {\n this.video.pause();\n this.video.removeAttribute(\"src\");\n this.video.load();\n }\n this.video = undefined;\n this.videoUrl = \"\";\n }\n\n dispose(): void {\n this.texture?.dispose();\n this.texture = undefined;\n this.clearVideo();\n this.failedUrl = \"\";\n }\n}\n\ntype Wave = {\n mesh: THREE.Mesh;\n material: THREE.ShaderMaterial;\n geometry: WaveGeometry;\n /** This wave's own 2D palette texture + optional video. */\n palette: WavePalette;\n};\n\n// Parse scratch: refresh() converts ~25 hex colours per wave per call (i.e. per slider input),\n// so reuse one Color instead of allocating each time.\nconst HEX_SCRATCH = new THREE.Color();\n\n/** Convert an sRGB hex string to a linear-space RGB vector (three's ColorManagement does the\n * sRGB→linear conversion on parse). Exported for the studio subclass's live light-uniform push. */\nexport function hexToLinearVec3(hex: string, target: THREE.Vector3): THREE.Vector3 {\n // three's ColorManagement (on by default in r169) already converts the sRGB hex to\n // LINEAR when parsing the hex — its .r/.g/.b are linear. Calling\n // convertSRGBToLinear() again would double-linearize (crushing greens → everything\n // turns red), so we read the components directly.\n const c = HEX_SCRATCH.set(hex);\n return target.set(c.r, c.g, c.b);\n}\n\n/**\n * Renders a gradient \"wave of light\" from a {@link StudioConfig}. Framework-agnostic:\n * it needs only a DOM container and a config. The studio mutates the config in\n * place and calls `refresh()` / `rebuild()`.\n */\nexport class WaveRenderer {\n readonly renderer: THREE.WebGLRenderer;\n protected readonly scene = new THREE.Scene();\n protected readonly camera: THREE.OrthographicCamera;\n protected readonly group = new THREE.Group();\n private readonly composer: EffectComposer;\n private readonly postPass: ShaderPass;\n /** Optional bloom pass — created lazily when bloomStrength first goes >0, removed at 0. */\n private bloomPass?: UnrealBloomPass;\n protected readonly container: HTMLElement;\n private readonly respectReducedMotion: boolean;\n private readonly skipIntroRamp: boolean;\n\n protected config: StudioConfig;\n protected waves: Wave[] = [];\n\n // 2D palette textures (+ any palette videos) live per-wave — see WavePalette on each Wave.\n private backgroundTexture?: THREE.Texture;\n private backgroundSig = \"\";\n private backgroundImage?: HTMLImageElement;\n private backgroundImageUrl = \"\";\n private failedBackgroundImageUrl = \"\";\n private backgroundVideo?: HTMLVideoElement;\n private backgroundVideoUrl = \"\";\n private failedBackgroundVideoUrl = \"\";\n private backgroundVideoCanvas?: HTMLCanvasElement;\n /** Authored default camera pose, for \"Reset camera\". */\n protected readonly homeCamPos = new THREE.Vector3();\n protected readonly homeCamTarget = new THREE.Vector3();\n\n // Reused scratch for per-frame clip-plane fitting (see updateClipPlanes) — hoisted so the\n // render loop allocates nothing.\n private readonly clipBox = new THREE.Box3();\n private readonly clipSphere = new THREE.Sphere();\n private readonly clipTmpA = new THREE.Vector3();\n private readonly clipTmpB = new THREE.Vector3();\n\n private readonly timer = new THREE.Timer();\n private time = 0;\n private rafId = 0;\n protected running = false;\n private started = false;\n\n private visible = true;\n private pageVisible = true;\n private reducedMotion = false;\n /** Intro ramp: eases animation time 0→1 over ~1s on load (when config.introRamp). */\n private introTimeRamp = 0;\n\n private readonly resizeObserver: ResizeObserver;\n private readonly intersectionObserver: IntersectionObserver;\n private readonly motionQuery: MediaQueryList;\n\n protected capturing = false;\n /** Fixed backing-buffer dimensions used by the studio's visible export frame. Embeds leave\n * this unset and continue to resize responsively with their container and device DPR. */\n private outputSize?: { width: number; height: number };\n\n constructor(container: HTMLElement, config: StudioConfig, options: WaveRendererOptions = {}) {\n this.container = container;\n this.config = ensureStudioConfig(config);\n this.respectReducedMotion = options.respectReducedMotion ?? true;\n this.skipIntroRamp = options.skipIntroRamp ?? false;\n\n this.renderer = new THREE.WebGLRenderer({\n // antialias smooths edges; preserveDrawingBuffer keeps the drawing buffer readable so we\n // can export PNG/WebM. Both cost a little performance, but this authoring tool needs them.\n antialias: true,\n alpha: true,\n preserveDrawingBuffer: true,\n powerPreference: \"high-performance\",\n });\n this.renderer.outputColorSpace = THREE.SRGBColorSpace;\n this.renderer.setClearColor(0x000000, 0);\n container.appendChild(this.renderer.domElement);\n\n // Resilience: if the GPU drops the context (memory pressure, sleep/wake), don't\n // let the browser hard-crash the page — prevent the default and rebuild on restore.\n this.renderer.domElement.addEventListener(\"webglcontextlost\", this.onContextLost, false);\n this.renderer.domElement.addEventListener(\n \"webglcontextrestored\",\n this.onContextRestored,\n false,\n );\n\n // Orthographic, framed in device pixels: resize() sets the frustum to the canvas size, and\n // the mesh is scaled up so the wave overflows the frame, leaving only the twist on screen.\n // The left/right/top/bottom bounds here are placeholders overwritten by the first resize();\n // near/far are placeholders too — updateClipPlanes() refits them to the scene every frame so\n // no camera angle ever clips the wave (a fixed slab does — see updateClipPlanes).\n this.camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 1, 10000);\n this.camera.position.set(\n config.cameraPosition.x,\n config.cameraPosition.y,\n config.cameraPosition.z,\n );\n this.camera.zoom = config.cameraZoom ?? 1;\n this.camera.lookAt(config.cameraTarget.x, config.cameraTarget.y, config.cameraTarget.z);\n this.camera.updateProjectionMatrix();\n // Remember the authored default pose so \"Reset camera\" returns to it (orbit mutates\n // config.cameraPosition, so we can't read it back from config later).\n this.homeCamPos.copy(this.camera.position);\n this.homeCamTarget.set(config.cameraTarget.x, config.cameraTarget.y, config.cameraTarget.z);\n this.scene.add(this.group);\n\n this.composer = new EffectComposer(this.renderer);\n this.composer.addPass(new RenderPass(this.scene, this.camera));\n this.postPass = new ShaderPass({\n uniforms: {\n tDiffuse: { value: null },\n uResolution: { value: new THREE.Vector2(1, 1) },\n uBlurAmount: { value: config.blur },\n uBlurSamples: { value: 6 },\n uGrainAmount: { value: config.grain },\n uTime: { value: 0 },\n },\n vertexShader: postVertexShader,\n fragmentShader: postFragmentShader,\n });\n this.composer.addPass(this.postPass);\n this.composer.addPass(new OutputPass());\n\n this.motionQuery = window.matchMedia(\"(prefers-reduced-motion: reduce)\");\n this.reducedMotion = this.respectReducedMotion && this.motionQuery.matches;\n this.motionQuery.addEventListener(\"change\", this.onMotionChange);\n document.addEventListener(\"visibilitychange\", this.onVisibilityChange);\n\n this.intersectionObserver = new IntersectionObserver(\n (entries) => {\n this.visible = entries[0]?.isIntersecting ?? true;\n this.updateRunning();\n },\n { rootMargin: \"100px\" },\n );\n this.intersectionObserver.observe(container);\n\n this.resizeObserver = new ResizeObserver(this.onResize);\n this.resizeObserver.observe(container);\n\n this.applyBackground();\n this.buildWaves();\n this.resize();\n }\n\n private get segments(): number {\n // Scale detail down as waves multiply, so total geometry stays bounded.\n const q = this.config.quality / Math.sqrt(Math.max(1, this.config.waves.length));\n return THREE.MathUtils.clamp(Math.round(BASE_SEGMENTS * q), 24, 360);\n }\n\n private makeUniforms(): Record<string, THREE.IUniform> {\n const colors: THREE.Vector3[] = [];\n const colorPos: number[] = [];\n for (let i = 0; i < MAX_COLORS; i++) {\n colors.push(new THREE.Vector3(1, 1, 1));\n colorPos.push(MAX_COLORS > 1 ? i / (MAX_COLORS - 1) : 0);\n }\n const meshPointPos: THREE.Vector2[] = [];\n const meshPointColor: THREE.Vector3[] = [];\n const meshPointInfluence: number[] = [];\n for (let i = 0; i < MAX_MESH_POINTS; i++) {\n meshPointPos.push(new THREE.Vector2(0.5, 0.5));\n meshPointColor.push(new THREE.Vector3(1, 1, 1));\n meshPointInfluence.push(0.65);\n }\n const lightPos: THREE.Vector3[] = [];\n const lightColor: THREE.Vector3[] = [];\n const lightIntensity: number[] = [];\n for (let i = 0; i < MAX_LIGHTS; i++) {\n lightPos.push(new THREE.Vector3());\n lightColor.push(new THREE.Vector3(1, 1, 1));\n lightIntensity.push(0);\n }\n const bandBounds: THREE.Vector4[] = [];\n const bandParams: THREE.Vector4[] = [];\n const bandParaPow: number[] = [];\n for (let i = 0; i < MAX_NOISE_BANDS; i++) {\n bandBounds.push(new THREE.Vector4());\n bandParams.push(new THREE.Vector4());\n bandParaPow.push(0);\n }\n return {\n // Deformation (vertex)\n uTime: { value: 0 },\n uSpeed: { value: 0.05 },\n uSeed: { value: 0 },\n uDispFreqX: { value: 0.003234 },\n uDispFreqZ: { value: 0.00799 },\n uDispAmount: { value: 6.051 },\n uDetailFreq: { value: 0.04 },\n uDetailAmount: { value: 0 }, // 2nd displacement octave (read only under DETAIL_OCTAVE)\n uTwFreqX: { value: -0.055 },\n uTwFreqY: { value: 0.077 },\n uTwFreqZ: { value: -0.518 },\n uTwPowX: { value: 3.95 },\n uTwPowY: { value: 5.85 },\n uTwPowZ: { value: 6.33 },\n uLoopSeconds: { value: 0 }, // seamless-loop period (read only under the LOOP_MOTION define)\n // Colour + light (fragment)\n uColors: { value: colors },\n uColorPos: { value: colorPos },\n uColorCount: { value: 2 },\n uGradType: { value: 0 },\n uGradAngle: { value: 0 },\n uGradShift: { value: 0.15 },\n uMeshPointPos: { value: meshPointPos },\n uMeshPointColor: { value: meshPointColor },\n uMeshPointInfluence: { value: meshPointInfluence },\n uMeshPointCount: { value: 0 },\n uMeshSoftness: { value: 0.62 },\n uPalette: { value: null },\n uUsePalette: { value: 1 },\n uPaletteRaw: { value: 1 },\n uPaletteScale: { value: new THREE.Vector2(1, 1) },\n uPaletteOffset: { value: new THREE.Vector2(0, 0) },\n uPaletteRotation: { value: 0 },\n uDebug: { value: 0 },\n uSheen: { value: 1 },\n uRoundness: { value: 0.35 },\n uIridescence: { value: 0 },\n uDepthTint: { value: 0 }, // solid-theme depth tint (read only under DEPTH_TINT)\n uDepthTintColor: { value: new THREE.Vector3() },\n uHueShift: { value: 0 },\n uContrast: { value: 1 },\n uSaturation: { value: 1 },\n uFiberCount: { value: 90 },\n uFiberStrength: { value: 0.25 },\n uTexture: { value: 0 },\n uCreaseLight: { value: 0.15 },\n uCreaseSharpness: { value: 2.0 },\n uCreaseSoftness: { value: 1.0 },\n uEdgeFade: { value: 0.06 },\n uEdgeFeather: { value: 0.1 }, // ribbon-edge softness (read only under EDGE_FEATHER)\n uOpacity: { value: 1 },\n uSquared: { value: 1 }, // \"squared\" deep-colour mode: square the colour in-shader (see applyBlendMode)\n uResolution: { value: new THREE.Vector2(1, 1) },\n uAmbient: { value: 0.45 },\n uNumLights: { value: 1 },\n uLightPos: { value: lightPos },\n uLightColor: { value: lightColor },\n uLightIntensity: { value: lightIntensity },\n uNumNoiseBands: { value: 0 },\n uNoiseBandBounds: { value: bandBounds },\n uNoiseBandParams: { value: bandParams },\n uNoiseBandParaPow: { value: bandParaPow },\n // Wireframe thin-line theme (used only by lineFragmentShader)\n uLineAmount: { value: 425 },\n uLineThickness: { value: 1 },\n uLineDerivativePower: { value: 0.95 },\n uMaxWidth: { value: 1232 },\n uClearColor: { value: new THREE.Vector3(1, 1, 1) },\n };\n }\n\n /** Vertex-shader #defines for a wave: TWIST_MOTION (per-wave animated twist wobble) and\n * LOOP_MOTION (scene-level seamless loop). Both select #ifdef-gated code paths; an empty\n * object compiles the default (linear-time) program. */\n private waveDefines(sc: WaveConfig | undefined): Record<string, string> {\n const defines: Record<string, string> = {};\n if (sc?.twistMotion) defines.TWIST_MOTION = \"\";\n if ((this.config.loopSeconds ?? 0) > 0) defines.LOOP_MOTION = \"\";\n if ((sc?.detailAmount ?? 0) !== 0) defines.DETAIL_OCTAVE = \"\";\n if ((sc?.depthTint ?? 0) > 0) defines.DEPTH_TINT = \"\";\n if ((sc?.edgeFeather ?? 0.1) !== 0.1) defines.EDGE_FEATHER = \"\";\n return defines;\n }\n\n private addWave(): void {\n const geo = new WaveGeometry(this.segments);\n // Initialise defines/fragment/blend from the wave this material will represent, so the\n // first refresh() doesn't force a needless program recompile. Falls back to the first wave.\n const sc = this.config.waves[this.waves.length] ?? this.config.waves[0];\n const material = new THREE.ShaderMaterial({\n uniforms: this.makeUniforms(),\n // TWIST_MOTION / LOOP_MOTION select variant vertex-shader paths. Toggled live in refresh().\n defines: this.waveDefines(sc),\n vertexShader,\n // solid theme = surfaceColor shader; wireframe theme = thin-line shader.\n // Swapped live in refresh() when the wave's theme changes.\n fragmentShader: sc?.theme === \"wireframe\" ? lineFragmentShader : fragmentShader,\n transparent: true,\n depthTest: true,\n depthWrite: true,\n side: THREE.DoubleSide,\n });\n // Blending (incl. the squaring blend) is set from the wave's blendMode — see applyBlendMode —\n // so it survives refresh() instead of being a dead constructor flag.\n this.applyBlendMode(material, sc?.blendMode ?? \"squared\");\n const mesh = new THREE.Mesh(geo.geometry, material);\n mesh.frustumCulled = false;\n this.group.add(mesh);\n const palette = new WavePalette(\n (url) => this.createLoopingVideo(url),\n () => {\n this.updatePaletteTextures();\n this.syncVideoPlayback();\n if (!this.running) this.renderOnce();\n },\n );\n this.waves.push({ mesh, material, geometry: geo, palette });\n }\n\n /**\n * Apply config.blendMode to a material. \"squared\" (the default) is the hero blend:\n * CustomBlending with AddEquation, src = SrcColorFactor, dst = ZeroFactor, so the\n * framebuffer result is fragColor² — the squaring deepens the colours into the vivid\n * hero look (without it the wave reads pastel). \"additive\"/\"normal\"/\"multiply\" are\n * authoring overrides. Multiply uses Three's premultiplied-alpha path; the custom\n * fragment shaders premultiply their output when Three injects PREMULTIPLIED_ALPHA.\n * Returns true if material state changed (caller flags needsUpdate).\n */\n private applyBlendMode(material: THREE.ShaderMaterial, mode: BlendMode): boolean {\n // \"squared\" is the deep hero look. It used to be a framebuffer-squaring CustomBlending\n // (src·src, dst×0) — which REPLACES the destination rather than compositing over it, so any\n // semi-transparent pixel (soft ribbon edges, and the large near-edge-on regions at oblique\n // camera angles) wiped the framebuffer's colour AND alpha, punching dark / see-through holes\n // through the background and through other waves. On a transparent page you never saw it; on\n // an opaque background or with overlapping waves it's the \"chunks vanish\" artifact.\n //\n // Fix: do the colour-squaring in the shader (uSquared) and composite it with ordinary\n // premultiplied alpha (NormalBlending). Over an opaque body the result is identical (col²);\n // soft edges now blend into what's behind them instead of erasing it.\n const squared = mode === \"squared\";\n const blending =\n mode === \"additive\"\n ? THREE.AdditiveBlending\n : mode === \"multiply\"\n ? THREE.MultiplyBlending\n : THREE.NormalBlending; // \"squared\" (deep, via uSquared) and \"normal\" both composite\n // Premultiplied so the squared/multiply colour composites correctly (the shaders premultiply\n // their output under the PREMULTIPLIED_ALPHA define Three injects for this).\n const premultipliedAlpha = mode === \"multiply\" || squared;\n material.uniforms.uSquared.value = squared ? 1 : 0;\n if (material.blending === blending && material.premultipliedAlpha === premultipliedAlpha) {\n return false;\n }\n material.blending = blending;\n material.premultipliedAlpha = premultipliedAlpha;\n return true;\n }\n\n private disposeWaves(): void {\n for (const s of this.waves) {\n this.group.remove(s.mesh);\n s.material.dispose();\n s.geometry.dispose();\n s.palette.dispose();\n }\n this.waves = [];\n }\n\n /**\n * Reconcile the wave pool to `waveCount` WITHOUT tearing everything down:\n * keep existing waves (so the compiled shader program is never deleted and\n * re-compiled — that churn can crash some GPU drivers), add/remove only the\n * delta, and resize each geometry to the current quality.\n */\n private buildWaves(): void {\n const target = Math.max(1, this.config.waves.length);\n while (this.waves.length > target) {\n const s = this.waves.pop();\n if (!s) break;\n this.group.remove(s.mesh);\n s.material.dispose();\n s.geometry.dispose();\n s.palette.dispose();\n }\n while (this.waves.length < target) this.addWave();\n\n const segments = this.segments;\n this.waves.forEach((s, i) => {\n s.geometry.resize(segments);\n s.mesh.renderOrder = i;\n });\n this.refresh();\n }\n\n /** Re-read per-frame-independent values from the (mutated) config. */\n refresh(): void {\n this.applyBackground();\n this.applyPost();\n // Once an external driver (orbit / edit gizmo) owns the camera, don't fight it here; the shell\n // (no orbit) applies the saved camera position/target so it matches the authored view.\n if (!this.isCameraExternallyDriven()) {\n const p = this.config.cameraPosition;\n const tg = this.config.cameraTarget;\n this.camera.position.set(p.x, p.y, p.z);\n this.camera.lookAt(tg.x, tg.y, tg.z);\n }\n\n this.waves.forEach((wave, i) => {\n const sc = this.config.waves[i] ?? this.config.waves[this.config.waves.length - 1];\n const u = wave.material.uniforms;\n if (this.applyBlendMode(wave.material, sc.blendMode)) wave.material.needsUpdate = true;\n // Recompile the program when its #define set changes: TWIST_MOTION / DETAIL_OCTAVE / DEPTH_TINT\n // (per wave) and LOOP_MOTION (scene-level). Compare the whole set so any combination is handled.\n const wantDefines = this.waveDefines(sc);\n const curDefines = wave.material.defines ?? {};\n if (Object.keys(wantDefines).sort().join(\",\") !== Object.keys(curDefines).sort().join(\",\")) {\n wave.material.defines = wantDefines;\n wave.material.needsUpdate = true;\n }\n // Swap the fragment shader when this wave's theme changes: solid surfaceColor <->\n // wireframe thin-line. Three recompiles the program on needsUpdate.\n const wantFrag = sc.theme === \"wireframe\" ? lineFragmentShader : fragmentShader;\n if (wave.material.fragmentShader !== wantFrag) {\n wave.material.fragmentShader = wantFrag;\n wave.material.needsUpdate = true;\n }\n\n const stops = [...sc.palette].sort((a, b) => a.pos - b.pos);\n const colorCount = Math.max(1, Math.min(stops.length, MAX_COLORS));\n const colors = u.uColors.value as THREE.Vector3[];\n const colorPos = u.uColorPos.value as number[];\n for (let c = 0; c < MAX_COLORS; c++) {\n const stop = stops[Math.min(c, colorCount - 1)] ?? { color: \"#ffffff\", pos: 0 };\n hexToLinearVec3(stop.color, colors[c]);\n colorPos[c] = stop.pos;\n }\n u.uColorCount.value = colorCount;\n u.uGradType.value =\n sc.gradientType === \"radial\"\n ? 1\n : sc.gradientType === \"conic\"\n ? 2\n : sc.gradientType === \"mesh\"\n ? 3\n : 0;\n u.uGradAngle.value = ((sc.gradientAngle ?? 0) * Math.PI) / 180;\n u.uGradShift.value = sc.gradientShift ?? 0;\n const meshPoints = sc.meshGradientPoints.slice(0, MAX_MESH_POINTS);\n const meshPointPos = u.uMeshPointPos.value as THREE.Vector2[];\n const meshPointColor = u.uMeshPointColor.value as THREE.Vector3[];\n const meshPointInfluence = u.uMeshPointInfluence.value as number[];\n for (let pointIndex = 0; pointIndex < MAX_MESH_POINTS; pointIndex++) {\n const point = meshPoints[pointIndex] ?? meshPoints[meshPoints.length - 1];\n if (!point) continue;\n meshPointPos[pointIndex].set(point.x, point.y);\n hexToLinearVec3(point.color, meshPointColor[pointIndex]);\n meshPointInfluence[pointIndex] = point.influence;\n }\n u.uMeshPointCount.value = meshPoints.length;\n u.uMeshSoftness.value = sc.meshGradientSoftness;\n u.uPaletteScale.value.set(sc.paletteTextureScale?.x ?? 1, sc.paletteTextureScale?.y ?? 1);\n u.uPaletteOffset.value.set(sc.paletteTextureOffset?.x ?? 0, sc.paletteTextureOffset?.y ?? 0);\n u.uPaletteRotation.value = ((sc.paletteTextureRotation ?? 0) * Math.PI) / 180;\n u.uHueShift.value = sc.hueShift;\n u.uContrast.value = sc.colorContrast;\n u.uSaturation.value = sc.colorSaturation;\n // Wireframe thin-line theme params (used only by lineFragmentShader). uClearColor is the\n // between-line colour = the page background (scene), fed in linear space like the palette.\n u.uLineAmount.value = sc.lineAmount ?? 425;\n u.uLineThickness.value = sc.lineThickness ?? 1;\n u.uLineDerivativePower.value = sc.lineDerivativePower ?? 0.95;\n u.uMaxWidth.value = sc.maxWidth ?? 1232;\n hexToLinearVec3(this.config.background, u.uClearColor.value as THREE.Vector3);\n u.uFiberCount.value = sc.fiberCount;\n u.uFiberStrength.value = sc.fiberStrength;\n u.uTexture.value = sc.texture;\n u.uCreaseLight.value = sc.creaseLight;\n u.uCreaseSharpness.value = sc.creaseSharpness;\n u.uCreaseSoftness.value = sc.creaseSoftness;\n u.uSheen.value = sc.sheen ?? 1;\n u.uRoundness.value = sc.roundness ?? 0.35;\n u.uIridescence.value = sc.iridescence ?? 0;\n u.uDepthTint.value = sc.depthTint ?? 0;\n hexToLinearVec3(sc.depthTintColor ?? \"#0a2540\", u.uDepthTintColor.value as THREE.Vector3);\n u.uEdgeFade.value = sc.edgeFade;\n u.uEdgeFeather.value = sc.edgeFeather ?? 0.1;\n // Lights + ambient are scene-level (shared by every wave).\n const lights = this.config.lights ?? [];\n u.uAmbient.value = this.config.ambient ?? 0.45;\n u.uNumLights.value = Math.min(lights.length, MAX_LIGHTS);\n const lPos = u.uLightPos.value as THREE.Vector3[];\n const lCol = u.uLightColor.value as THREE.Vector3[];\n const lInt = u.uLightIntensity.value as number[];\n for (let li = 0; li < MAX_LIGHTS; li++) {\n const light = lights[li];\n if (light) {\n lPos[li].set(light.position.x, light.position.y, light.position.z);\n hexToLinearVec3(light.color, lCol[li]);\n lInt[li] = light.intensity;\n } else {\n lInt[li] = 0;\n }\n }\n // Noise bands (per-region fiber overrides) — per wave\n const bands = sc.noiseBands ?? [];\n u.uNumNoiseBands.value = Math.min(bands.length, MAX_NOISE_BANDS);\n const bBounds = u.uNoiseBandBounds.value as THREE.Vector4[];\n const bParams = u.uNoiseBandParams.value as THREE.Vector4[];\n const bPara = u.uNoiseBandParaPow.value as number[];\n for (let bi = 0; bi < MAX_NOISE_BANDS; bi++) {\n const band = bands[bi];\n if (band) {\n bBounds[bi].set(band.startX, band.endX, band.startY, band.endY);\n bParams[bi].set(band.feather, band.strength, band.frequency, band.colorAttenuation);\n bPara[bi] = band.parabolaPower;\n }\n }\n // Deformation (absolute per wave)\n u.uSpeed.value = sc.speed;\n u.uSeed.value = sc.seed;\n u.uLoopSeconds.value = this.config.loopSeconds ?? 0; // scene-level; shared by every wave\n u.uDispFreqX.value = sc.displaceFrequency.x;\n u.uDispFreqZ.value = sc.displaceFrequency.y;\n u.uDispAmount.value = sc.displaceAmount;\n u.uDetailFreq.value = sc.detailFrequency ?? 0.04;\n u.uDetailAmount.value = sc.detailAmount ?? 0;\n u.uTwFreqX.value = sc.twistFrequency.x;\n u.uTwFreqY.value = sc.twistFrequency.y;\n u.uTwFreqZ.value = sc.twistFrequency.z;\n u.uTwPowX.value = sc.twistPower.x;\n u.uTwPowY.value = sc.twistPower.y;\n u.uTwPowZ.value = sc.twistPower.z;\n // Mesh transform — each wave's ABSOLUTE scale / rotation / position, applied via\n // modelMatrix using THREE's Euler XYZ order so the on-screen orientation matches the\n // authored view.\n wave.mesh.scale.set(sc.scale.x, sc.scale.y, sc.scale.z);\n wave.mesh.rotation.set(\n THREE.MathUtils.degToRad(sc.rotation.x),\n THREE.MathUtils.degToRad(sc.rotation.y),\n THREE.MathUtils.degToRad(sc.rotation.z),\n );\n wave.mesh.position.set(sc.position.x, sc.position.y, sc.position.z);\n u.uOpacity.value = sc.opacity;\n });\n\n this.updatePaletteTextures();\n this.syncVideoPlayback();\n\n // Whole-wave mirror (world-space flip ≈ screen flip for the near-frontal camera).\n this.group.scale.set(this.config.mirrorH ? -1 : 1, this.config.mirrorV ? -1 : 1, 1);\n\n this.onAfterRefresh();\n if (!this.running) this.renderOnce();\n }\n\n /** Point every wave's palette sampler at its own WavePalette texture (each rebuilt only\n * when that wave's palette actually changes — see WavePalette.apply). */\n private updatePaletteTextures(): void {\n this.waves.forEach((wave, i) => {\n const sc = this.config.waves[i] ?? this.config.waves[this.config.waves.length - 1];\n wave.palette.apply(sc, wave.material.uniforms);\n });\n }\n\n /** Dev: 0 = normal, 1 = visualise the crease value, 2 = visualise derivative normal. */\n setDebug(v: number): void {\n for (const s of this.waves) s.material.uniforms.uDebug.value = v;\n this.renderOnce();\n }\n\n /** Rebuild geometry + waves (call when waveCount or quality changes). */\n rebuild(): void {\n this.buildWaves();\n }\n\n private applyBackground(): void {\n if (this.config.transparentBackground) {\n this.scene.background = null;\n this.renderer.setClearColor(0x000000, 0);\n return;\n }\n\n const matte = new THREE.Color(this.config.background);\n this.renderer.setClearColor(matte, 1);\n if (this.config.backgroundMode === \"color\") {\n this.applyColorBackground(matte);\n return;\n }\n // Live video is redrawn every frame, so cap its staging canvas near 1080p. Still images\n // and procedural maps retain the full 4K-capable background texture path.\n const { width, height } = this.backgroundCanvasSize(\n this.config.backgroundVideoUrl ? 2048 : 4096,\n );\n if (this.config.backgroundMode === \"gradient\") this.applyGradientBackground(width, height);\n else this.applyImageBackground(matte, width, height);\n }\n\n private applyColorBackground(matte: THREE.Color): void {\n this.clearBackgroundVideo();\n this.backgroundTexture?.dispose();\n this.backgroundTexture = undefined;\n this.backgroundSig = \"\";\n this.scene.background = matte;\n }\n\n private applyGradientBackground(width: number, height: number): void {\n this.clearBackgroundVideo();\n const gradType = this.config.backgroundGradientType;\n if (gradType === \"mesh\") {\n const pts = this.config.backgroundMeshPoints ?? [];\n const softness = this.config.backgroundMeshSoftness ?? 0.62;\n const ptSig = pts\n .map((p) => `${p.color}@${p.x.toFixed(3)},${p.y.toFixed(3)},${p.influence.toFixed(3)}`)\n .join(\",\");\n const sig = [\"mesh\", softness.toFixed(3), ptSig, width, height].join(\"|\");\n if (sig !== this.backgroundSig || !this.backgroundTexture) {\n const canvas = buildBackgroundMeshCanvas(pts, softness, width, height);\n this.backgroundTexture?.dispose();\n this.backgroundTexture = canvasToTexture(canvas);\n this.backgroundSig = sig;\n }\n this.scene.background = this.backgroundTexture;\n return;\n }\n const source = this.config.backgroundGradientSource ?? \"stops\";\n const def = PALETTE_MAPS[source];\n const stops =\n source !== \"stops\" && def?.kind === \"gradient\" && def.stops\n ? def.stops\n : this.config.backgroundPalette;\n const stopSig = stops.map((stop) => `${stop.color}@${stop.pos.toFixed(3)}`).join(\",\");\n const sig = [\n \"gradient\",\n source,\n gradType,\n this.config.backgroundGradientAngle,\n stopSig,\n width,\n height,\n ].join(\"|\");\n if (sig !== this.backgroundSig || !this.backgroundTexture) {\n const canvas = buildBackgroundGradientCanvas({\n stops,\n type: gradType,\n angle: this.config.backgroundGradientAngle,\n width,\n height,\n });\n this.backgroundTexture?.dispose();\n this.backgroundTexture = canvasToTexture(canvas);\n this.backgroundSig = sig;\n }\n this.scene.background = this.backgroundTexture;\n }\n\n /** Image mode: a live video, a user-loaded image, or a built-in map. `matte` shows while an\n * async source is still loading. */\n private applyImageBackground(matte: THREE.Color, width: number, height: number): void {\n const fit = this.config.backgroundImageFit ?? \"cover\";\n const zoom = this.config.backgroundImageZoom ?? 1;\n const position = this.config.backgroundImagePosition ?? { x: 0, y: 0 };\n const videoUrl = this.config.backgroundVideoUrl;\n const customUrl = this.config.backgroundImageUrl;\n let source: CanvasImageSource;\n let sourceWidth: number;\n let sourceHeight: number;\n let sourceSig: string;\n if (videoUrl) {\n this.ensureBackgroundVideo(videoUrl);\n if (!this.backgroundVideo || this.backgroundVideo.readyState < 2) {\n this.scene.background = matte;\n return;\n }\n source = this.backgroundVideo;\n sourceWidth = this.backgroundVideo.videoWidth;\n sourceHeight = this.backgroundVideo.videoHeight;\n sourceSig = `video|${videoUrl}`;\n } else if (customUrl) {\n this.clearBackgroundVideo();\n if (\n !this.backgroundImage ||\n this.backgroundImageUrl !== customUrl ||\n !this.backgroundImage.complete\n ) {\n this.loadBackgroundImage(customUrl);\n this.scene.background = matte;\n return;\n }\n source = this.backgroundImage;\n sourceWidth = this.backgroundImage.naturalWidth;\n sourceHeight = this.backgroundImage.naturalHeight;\n sourceSig = `custom|${customUrl}`;\n } else {\n this.clearBackgroundVideo();\n const imageSource = this.config.backgroundImageSource ?? \"vaporwave\";\n const canvas =\n imageSource === \"hero\"\n ? buildHeroPaletteCanvas()\n : PALETTE_MAPS[imageSource]?.kind === \"image\"\n ? paletteMapCanvas(PALETTE_MAPS[imageSource], Math.max(width, height))\n : null;\n if (!canvas) {\n this.scene.background = matte;\n return;\n }\n source = canvas;\n sourceWidth = canvas.width;\n sourceHeight = canvas.height;\n sourceSig = `map|${imageSource}`;\n }\n\n const sig = [\n \"image\",\n sourceSig,\n fit,\n zoom,\n position.x,\n position.y,\n this.config.background,\n width,\n height,\n ].join(\"|\");\n if (sig !== this.backgroundSig || !this.backgroundTexture) {\n const canvas = buildBackgroundImageCanvas(\n source,\n sourceWidth,\n sourceHeight,\n width,\n height,\n fit,\n this.config.background,\n zoom,\n position.x,\n position.y,\n );\n this.backgroundTexture?.dispose();\n this.backgroundTexture = canvasToTexture(canvas);\n this.backgroundSig = sig;\n this.backgroundVideoCanvas = videoUrl ? canvas : undefined;\n }\n this.scene.background = this.backgroundTexture;\n }\n\n private backgroundCanvasSize(maxRequestedEdge = 4096): { width: number; height: number } {\n const rawWidth = this.outputSize?.width ?? Math.max(1, this.container.clientWidth);\n const rawHeight = this.outputSize?.height ?? Math.max(1, this.container.clientHeight);\n const maxEdge = Math.min(maxRequestedEdge, this.renderer.capabilities.maxTextureSize);\n const scale = Math.min(1, maxEdge / Math.max(rawWidth, rawHeight));\n return {\n width: Math.max(1, Math.round(rawWidth * scale)),\n height: Math.max(1, Math.round(rawHeight * scale)),\n };\n }\n\n private loadBackgroundImage(url: string): void {\n if (this.backgroundImageUrl === url || this.failedBackgroundImageUrl === url) return;\n this.backgroundImageUrl = url;\n this.backgroundImage = undefined;\n const image = new Image();\n image.decoding = \"async\";\n if (!url.startsWith(\"data:\") && !url.startsWith(\"blob:\")) image.crossOrigin = \"anonymous\";\n image.addEventListener(\n \"load\",\n () => {\n if (this.config.backgroundImageUrl !== url) return;\n this.backgroundImage = image;\n this.failedBackgroundImageUrl = \"\";\n this.backgroundSig = \"\";\n this.applyBackground();\n if (!this.running) this.renderOnce();\n },\n { once: true },\n );\n image.addEventListener(\n \"error\",\n () => {\n if (this.config.backgroundImageUrl !== url) return;\n this.failedBackgroundImageUrl = url;\n this.backgroundSig = \"\";\n this.applyBackground();\n if (!this.running) this.renderOnce();\n },\n { once: true },\n );\n image.src = url;\n }\n\n private ensureBackgroundVideo(url: string): void {\n if (this.backgroundVideoUrl === url || this.failedBackgroundVideoUrl === url) return;\n this.clearBackgroundVideo();\n this.backgroundVideoUrl = url;\n const video = this.createLoopingVideo(url);\n video.addEventListener(\n \"loadeddata\",\n () => {\n if (this.config.backgroundVideoUrl !== url) return;\n this.backgroundVideo = video;\n this.failedBackgroundVideoUrl = \"\";\n this.backgroundSig = \"\";\n this.applyBackground();\n this.syncVideoPlayback();\n if (!this.running) this.renderOnce();\n },\n { once: true },\n );\n video.addEventListener(\n \"error\",\n () => {\n if (this.config.backgroundVideoUrl !== url) return;\n this.failedBackgroundVideoUrl = url;\n this.backgroundSig = \"\";\n this.applyBackground();\n if (!this.running) this.renderOnce();\n },\n { once: true },\n );\n this.backgroundVideo = video;\n video.load();\n }\n\n private clearBackgroundVideo(): void {\n if (this.backgroundVideo) {\n this.backgroundVideo.pause();\n this.backgroundVideo.removeAttribute(\"src\");\n this.backgroundVideo.load();\n }\n this.backgroundVideo = undefined;\n this.backgroundVideoUrl = \"\";\n this.failedBackgroundVideoUrl = \"\";\n this.backgroundVideoCanvas = undefined;\n }\n\n private createLoopingVideo(url: string): HTMLVideoElement {\n const video = document.createElement(\"video\");\n video.muted = true;\n video.defaultMuted = true;\n video.loop = true;\n video.playsInline = true;\n video.preload = \"auto\";\n if (!url.startsWith(\"data:\") && !url.startsWith(\"blob:\")) video.crossOrigin = \"anonymous\";\n video.src = url;\n return video;\n }\n\n private syncVideoPlayback(): void {\n const backgroundActive =\n this.running &&\n !this.config.transparentBackground &&\n this.config.backgroundMode === \"image\" &&\n !!this.config.backgroundVideoUrl;\n if (this.backgroundVideo) {\n if (backgroundActive) void this.backgroundVideo.play().catch(() => {});\n else this.backgroundVideo.pause();\n }\n // Palette videos are per wave.\n this.waves.forEach((wave, i) => {\n const sc = this.config.waves[i] ?? this.config.waves[this.config.waves.length - 1];\n wave.palette.syncPlayback(sc, this.running);\n });\n }\n\n private updateBackgroundVideoFrame(): void {\n const video = this.backgroundVideo;\n const canvas = this.backgroundVideoCanvas;\n if (!video || !canvas || video.readyState < 2 || !this.backgroundTexture) return;\n const position = this.config.backgroundImagePosition ?? { x: 0, y: 0 };\n drawBackgroundMediaFrame(\n canvas,\n video,\n video.videoWidth,\n video.videoHeight,\n this.config.backgroundImageFit ?? \"cover\",\n this.config.background,\n this.config.backgroundImageZoom ?? 1,\n position.x,\n position.y,\n );\n this.backgroundTexture.needsUpdate = true;\n }\n\n private applyPost(): void {\n const u = this.postPass.uniforms;\n u.uBlurAmount.value = this.config.blur;\n u.uGrainAmount.value = this.config.grain;\n u.uBlurSamples.value = Math.round(this.config.blurSamples ?? 6);\n this.applyBloom();\n }\n\n /** Insert / tune / remove the bloom pass. strength 0 removes it from the composer entirely, so\n * cost and pixels are identical to bloom-off; the pass (and its mip-chain render targets) is\n * created lazily the first time bloom is enabled and disposed when turned back off. It sits\n * right after the scene RenderPass so it blooms the wave before the grain/blur pass. */\n private applyBloom(): void {\n const strength = this.config.bloomStrength ?? 0;\n if (strength > 0) {\n if (!this.bloomPass) {\n const size = this.renderer.getDrawingBufferSize(new THREE.Vector2());\n this.bloomPass = new UnrealBloomPass(\n size,\n strength,\n this.config.bloomRadius ?? 0.4,\n this.config.bloomThreshold ?? 0.85,\n );\n this.composer.insertPass(this.bloomPass, 1); // after RenderPass, before postPass/OutputPass\n }\n this.bloomPass.strength = strength;\n this.bloomPass.radius = this.config.bloomRadius ?? 0.4;\n this.bloomPass.threshold = this.config.bloomThreshold ?? 0.85;\n } else if (this.bloomPass) {\n this.composer.removePass(this.bloomPass);\n this.bloomPass.dispose();\n this.bloomPass = undefined;\n }\n }\n\n private onResize = (): void => {\n this.resize();\n };\n\n private onContextLost = (e: Event): void => {\n e.preventDefault(); // tell the browser we'll recover → no \"Aw, Snap\" crash\n cancelAnimationFrame(this.rafId);\n this.running = false;\n };\n\n private onContextRestored = (): void => {\n this.disposeWaves(); // old GPU resources are invalid on a fresh context (per-wave palettes too)\n this.backgroundTexture?.dispose();\n this.backgroundTexture = undefined;\n this.backgroundSig = \"\";\n this.buildWaves();\n this.resize();\n this.updateRunning();\n };\n\n resize(): void {\n const w = this.outputSize?.width ?? Math.max(1, this.container.clientWidth);\n const h = this.outputSize?.height ?? Math.max(1, this.container.clientHeight);\n const dpr = this.outputSize ? 1 : Math.min(window.devicePixelRatio || 1, this.config.dprMax);\n this.renderer.setPixelRatio(dpr);\n this.renderer.setSize(w, h, !this.outputSize);\n if (this.outputSize) {\n // setSize(..., false) preserves the exact backing buffer without writing fixed CSS\n // pixel dimensions. Keep the canvas stretched to the visible aspect-ratio frame.\n this.renderer.domElement.style.width = \"100%\";\n this.renderer.domElement.style.height = \"100%\";\n }\n this.composer.setPixelRatio(dpr);\n this.composer.setSize(w, h);\n const dw = w * dpr;\n const dh = h * dpr;\n (this.postPass.uniforms.uResolution.value as THREE.Vector2).set(dw, dh);\n for (const s of this.waves) {\n (s.material.uniforms.uResolution.value as THREE.Vector2).set(dw, dh);\n }\n // The responsive ortho framing: the frustum = the canvas in DEVICE pixels (1 world unit =\n // 1px at zoom 1). Combined with the ×10 mesh scale, the wave overflows the frame.\n this.camera.left = -dw / 2;\n this.camera.right = dw / 2;\n this.camera.top = dh / 2;\n this.camera.bottom = -dh / 2;\n this.applyZoom(); // responsive ortho zoom (maps FRAME_W world units onto the canvas)\n this.applyBackground();\n this.onAfterResize();\n if (!this.running) this.renderOnce();\n }\n\n /** Set an exact output buffer while CSS scales the canvas into the on-screen export frame. */\n setOutputSize(width: number, height: number): void {\n this.outputSize = {\n width: THREE.MathUtils.clamp(Math.round(width), 64, 8192),\n height: THREE.MathUtils.clamp(Math.round(height), 64, 8192),\n };\n this.resize();\n }\n\n start(): void {\n this.started = true;\n this.updateRunning();\n }\n\n stop(): void {\n this.started = false;\n this.updateRunning();\n }\n\n private onMotionChange = (e: MediaQueryListEvent): void => {\n this.reducedMotion = this.respectReducedMotion && e.matches;\n this.updateRunning();\n };\n\n private onVisibilityChange = (): void => {\n this.pageVisible = document.visibilityState === \"visible\";\n this.updateRunning();\n };\n\n private updateRunning(): void {\n const shouldAnimate =\n this.started &&\n this.visible &&\n this.pageVisible &&\n !this.config.paused &&\n !this.reducedMotion;\n\n if (shouldAnimate && !this.running) {\n this.running = true;\n // Reset the delta baseline (as Clock.start() + a discarded getDelta() used to) so the first\n // frame advances by ~one frame, not by the whole idle/pause gap that elapsed while stopped.\n this.timer.update();\n this.rafId = requestAnimationFrame(this.loop);\n } else if (!shouldAnimate && this.running) {\n this.running = false;\n cancelAnimationFrame(this.rafId);\n }\n // When not animating (paused / reduced-motion / static export) show the FULL frame, not a\n // frozen mid-ease, by forcing introTimeRamp = 1.\n if (!this.running) {\n this.introTimeRamp = 1;\n this.renderOnce();\n }\n this.syncVideoPlayback();\n }\n\n private loop = (): void => {\n if (!this.running) return;\n this.timer.update();\n this.time += this.timer.getDelta();\n if (this.introTimeRamp < 1) this.introTimeRamp = Math.min(1, this.introTimeRamp + 0.016); // ~1s to full at 60fps\n this.renderOnce();\n this.rafId = requestAnimationFrame(this.loop);\n };\n\n /** Advance the per-frame time uniforms (geometry itself is static). Time model:\n * time = elapsed·introTimeRamp + timeOffset — the ramp eases the animation in on load. */\n private updateTime(): void {\n // Skip the ease-in when asked: a fresh renderer (first load, or the studio's HMR hot-swap)\n // starts introTimeRamp at 0, and replaying the ramp on every save reads as a \"speed up\" when\n // you tab back. The studio passes skipIntroRamp in dev; prod builds + the embed keep the ease-in.\n const ramp = this.config.introRamp === false || this.skipIntroRamp ? 1 : this.introTimeRamp;\n const t = this.time * ramp + (this.config.timeOffset ?? 0);\n // Indexed loop (no per-frame closure) — this runs every frame.\n for (let i = 0; i < this.waves.length; i++) {\n const u = this.waves[i].material.uniforms;\n u.uTime.value = t;\n // Palette drift: flow the colour along the ribbon independently of the geometry by drifting\n // uPaletteOffset over time. Only touch it when nonzero, so refresh()'s static base offset —\n // and every drift-off preset — is left byte-for-byte unchanged.\n const sc = this.config.waves[i] ?? this.config.waves[this.config.waves.length - 1];\n const dx = sc.paletteDriftX ?? 0;\n const dy = sc.paletteDriftY ?? 0;\n if (dx !== 0 || dy !== 0) {\n const base = sc.paletteTextureOffset;\n (u.uPaletteOffset.value as THREE.Vector2).set(base.x + dx * t, base.y + dy * t);\n }\n }\n this.postPass.uniforms.uTime.value = t;\n }\n\n /** Render exactly one frame at the current time. */\n renderOnce(): void {\n this.updateBackgroundVideoFrame();\n this.updateTime();\n this.updateClipPlanes(); // keep near/far bracketing the scene so no camera angle clips the wave\n this.composer.render();\n // Editor overlays (gizmo/helpers + camera-rig minimap) draw on top of the composed frame —\n // the studio subclass plugs them in here; the base renders nothing extra.\n this.onAfterRenderFrame();\n }\n\n /** Re-evaluate play/pause after `config.paused` changes. */\n refreshPlayback(): void {\n this.updateRunning();\n }\n\n /** Jump the camera to the config's authored framing (cameraPosition / cameraTarget /\n * cameraZoom). Called on whole-config swaps (preset / reset / randomize / import). The base\n * applies the pose directly; the studio subclass overrides it to also drive the orbit target\n * and sync the panel. Hook ⑤. */\n protected applyCameraFromConfig(): void {\n const p = this.config.cameraPosition;\n const tg = this.config.cameraTarget;\n this.camera.position.set(p.x, p.y, p.z);\n this.camera.up.set(0, 1, 0);\n this.camera.lookAt(tg.x, tg.y, tg.z);\n this.applyZoom();\n if (!this.running) this.renderOnce();\n }\n\n // ---- Editor hook points (no-ops in the base; the studio subclass overrides them) ----\n\n /** Hook ①: true while orbit or an edit gizmo owns the camera, so refresh() won't reset it. */\n protected isCameraExternallyDriven(): boolean {\n return false;\n }\n\n /** Hook ②: called at the end of refresh(), before the trailing renderOnce(). */\n protected onAfterRefresh(): void {}\n\n /** Hook ③: called at the end of renderOnce(), after the composed frame is drawn. */\n protected onAfterRenderFrame(): void {}\n\n /** Hook ④: called at the end of resize(), before the trailing renderOnce(). */\n protected onAfterResize(): void {}\n\n /** Responsive ortho zoom: COVER the FRAME_W × FRAME_H reference frame onto the canvas so the\n * wave frames the same at any size/aspect/dpr (only the cropped margin differs), times the\n * user's cameraZoom. `max(...)` = cover (fill both axes, crop overflow); `min(...)` would be\n * contain (fit with letterbox bands). Cover keeps the wave filling the frame on every screen. */\n protected applyZoom(): void {\n const dw = this.camera.right - this.camera.left; // device px (set in resize)\n const dh = this.camera.top - this.camera.bottom;\n this.camera.zoom = Math.max(dw / FRAME_W, dh / FRAME_H) * (this.config.cameraZoom ?? 1);\n this.camera.updateProjectionMatrix();\n }\n\n /** Fit the orthographic near/far planes to the scene before every render, so no part of a wave\n * is ever clipped as the camera orbits / dollies / pans (or when waves are added or scaled).\n *\n * The camera is *constructed* with fixed 1..10000 planes that only suit the authored hero\n * framing; once the view moves, the wave's depth extent along the view axis easily crosses\n * them and the GPU hard-slices the geometry along a flat plane — chunks of the wave vanish.\n *\n * The in-shader twist rotates each vertex about its LOCAL origin, so it can never move a vertex\n * further from that origin than the base geometry already sits. A sphere at each mesh's origin\n * (radius = base extent × the mesh's largest world scale, plus the Y displacement, ×1.2 slack)\n * therefore safely contains the fully-deformed wave. We union those, then bracket the union\n * along the view axis with a margin. Bracketing to the scene (rather than a fixed huge slab)\n * keeps clip-space depth scene-normalised — so the wireframe theme's depth fade, which reads\n * gl_Position.z, looks the same at any camera distance instead of washing out. Runs each frame;\n * it's a handful of vector ops over 1–8 meshes with no allocation. */\n private updateClipPlanes(): void {\n this.clipBox.makeEmpty();\n for (const wave of this.waves) {\n const mesh = wave.mesh;\n mesh.updateWorldMatrix(true, false);\n const bs = mesh.geometry.boundingSphere;\n if (!bs) continue;\n // The twist pivot (the mesh's local origin) in world space = the safe sphere's centre.\n const center = this.clipTmpA.setFromMatrixPosition(mesh.matrixWorld);\n const disp = Math.abs(Number(wave.material.uniforms.uDispAmount.value) || 0);\n const localRadius = bs.center.length() + bs.radius + disp;\n const radius = localRadius * mesh.matrixWorld.getMaxScaleOnAxis() * 1.2;\n // Enclose this wave's sphere by adding its axis-aligned bounding cube corners.\n this.clipBox.expandByPoint(this.clipTmpB.copy(center).addScalar(radius));\n this.clipBox.expandByPoint(this.clipTmpB.copy(center).addScalar(-radius));\n }\n if (this.clipBox.isEmpty()) return;\n this.clipBox.getBoundingSphere(this.clipSphere);\n const viewDir = this.camera.getWorldDirection(this.clipTmpA); // normalised view axis\n const centerDepth = this.clipTmpB\n .copy(this.clipSphere.center)\n .sub(this.camera.position)\n .dot(viewDir);\n const radius = this.clipSphere.radius;\n const margin = radius * 0.25 + 10;\n // Orthographic: a negative near is legal — the slab may extend behind the camera origin.\n this.camera.near = centerDepth - radius - margin;\n this.camera.far = centerDepth + radius + margin;\n this.camera.updateProjectionMatrix();\n }\n\n /** A world-space position delta that drops a duplicated wave into open frame space beside the\n * one it was copied from — screen-left and a touch down, sized to the visible frame — instead\n * of hidden exactly on top of it or (for the hero, which fills the right of the frame) pushed\n * off-frame. Camera-relative: it's \"left on screen\" no matter how the view is rotated/zoomed,\n * because it's built from the camera's right/up axes and the frame's visible world size. */\n\n async captureImage(\n mime: string,\n transparent = true,\n quality?: number,\n time?: number,\n ): Promise<Blob> {\n const prevBg = this.config.transparentBackground;\n if (transparent !== prevBg) {\n this.config.transparentBackground = transparent;\n this.applyBackground();\n }\n // Deterministic frame: render at a fixed animation-time (with the ease-in ramp forced full) so\n // a captured poster is reproducible instead of whatever frame happened to be on screen — pass\n // time = 0 for the frame the wave opens on. Restored in `finally`. Undefined = the live frame.\n const fixTime = time !== undefined;\n const prevTime = this.time;\n const prevRamp = this.introTimeRamp;\n if (fixTime) {\n this.time = time;\n this.introTimeRamp = 1;\n }\n let blob: Blob | null = null;\n try {\n this.capturing = true;\n this.renderOnce();\n blob = await new Promise<Blob | null>((resolve) =>\n this.canvas.toBlob(resolve, mime, quality),\n );\n } finally {\n this.capturing = false;\n if (transparent !== prevBg) {\n this.config.transparentBackground = prevBg;\n this.applyBackground();\n }\n if (fixTime) {\n this.time = prevTime;\n this.introTimeRamp = prevRamp;\n }\n this.renderOnce();\n }\n if (!blob || blob.type !== mime) throw new Error(`Failed to capture ${mime}`);\n return blob;\n }\n\n captureStream(fps = 60): MediaStream {\n return this.canvas.captureStream(fps);\n }\n\n get canvas(): HTMLCanvasElement {\n return this.renderer.domElement;\n }\n\n getConfig(): StudioConfig {\n return this.config;\n }\n\n setConfig(config: StudioConfig): void {\n const next = ensureStudioConfig(config);\n const structural =\n next.waves.length !== this.waves.length || next.quality !== this.config.quality;\n this.config = next;\n if (structural) this.rebuild();\n else this.refresh();\n // A whole new config (preset/reset/randomize/import) carries its own authored framing —\n // apply it even in the studio, where refresh() leaves the camera to orbit. Without this,\n // selecting a preset updated the wave but kept the previous camera (wrong framing).\n this.applyCameraFromConfig();\n }\n\n dispose(): void {\n cancelAnimationFrame(this.rafId);\n this.running = false;\n this.resizeObserver.disconnect();\n this.intersectionObserver.disconnect();\n this.motionQuery.removeEventListener(\"change\", this.onMotionChange);\n document.removeEventListener(\"visibilitychange\", this.onVisibilityChange);\n this.renderer.domElement.removeEventListener(\"webglcontextlost\", this.onContextLost);\n this.renderer.domElement.removeEventListener(\"webglcontextrestored\", this.onContextRestored);\n this.clearBackgroundVideo();\n this.backgroundTexture?.dispose();\n for (const s of this.waves) {\n s.material.dispose();\n s.geometry.dispose();\n s.palette.dispose();\n }\n this.bloomPass?.dispose();\n this.composer.dispose();\n this.renderer.dispose();\n this.renderer.domElement.remove();\n }\n}\n"],"mappings":"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"}
@@ -34,9 +34,24 @@ interface WaveOptions {
34
34
  /** Seam for the standalone/CDN build to supply the core synchronously (three already bundled). */
35
35
  loadCore?(): Promise<CoreModule>;
36
36
  }
37
+ interface SnapshotOptions {
38
+ /** Image MIME type. Default `"image/webp"`. */
39
+ type?: string;
40
+ /** Encoder quality 0–1 for lossy types. */
41
+ quality?: number;
42
+ /** Render with a transparent background. Default true. */
43
+ transparent?: boolean;
44
+ /** Render a fixed animation-time for a reproducible frame (default: the live frame). Poster
45
+ * captures should pass `0` — the frame the wave opens on, so the file doesn't churn per capture. */
46
+ time?: number;
47
+ }
37
48
  interface WaveHandle {
38
49
  readonly state: WaveState;
39
50
  readonly renderer: WaveRenderer | null;
51
+ /** Capture the current live frame as an image Blob (a poster you can host/cache). Resolves `null`
52
+ * until the wave is running (poster / fallback / pre-upgrade) — wait for {@link WaveOptions.onReady}
53
+ * (or the element's `wave3d-ready` event) first. */
54
+ snapshot(options?: SnapshotOptions): Promise<Blob | null>;
40
55
  /** Merge a partial config. Staged before upgrade; after, setConfig() then refreshPlayback(). */
41
56
  set(config: Partial<StudioConfig>): void;
42
57
  play(): void;
@@ -54,5 +69,5 @@ declare function createWave(container: HTMLElement, config?: Partial<StudioConfi
54
69
  /** The drop-in embed contract: an alias of {@link createWave}. */
55
70
  declare const mountWave: typeof createWave;
56
71
  //#endregion
57
- export { FallbackReason, WaveHandle, WaveOptions, WaveState, createWave, mountWave };
72
+ export { FallbackReason, SnapshotOptions, WaveHandle, WaveOptions, WaveState, createWave, mountWave };
58
73
  //# sourceMappingURL=createWave.d.ts.map
@@ -108,6 +108,11 @@ function createWaveImpl(loadCore, container, config, options) {
108
108
  get renderer() {
109
109
  return renderer;
110
110
  },
111
+ snapshot(opts = {}) {
112
+ if (!renderer) return Promise.resolve(null);
113
+ const { type = "image/webp", quality, transparent = true, time } = opts;
114
+ return renderer.captureImage(type, transparent, quality, time);
115
+ },
111
116
  set(next) {
112
117
  if (renderer) {
113
118
  renderer.setConfig({
@@ -1 +1 @@
1
- {"version":3,"file":"createWave.js","names":[],"sources":["../../src/shell/createWave.ts"],"sourcesContent":["import type { StudioConfig } from \"../config/model\";\nimport type { WaveRenderer, WaveRendererOptions } from \"../renderer/WaveRenderer\";\nimport { hasWebGL, prefersReducedMotion, prefersReducedData } from \"./probe\";\nimport { setupPoster, ensurePositioned, type Poster } from \"./poster\";\n\n/** Why the shell showed the poster instead of a live wave. */\nexport type FallbackReason =\n | \"no-webgl\"\n | \"reduced-motion\"\n | \"save-data\"\n | \"context-lost\"\n | \"load-error\";\n\n/** poster → loading → running, or → fallback (permanent poster). */\nexport type WaveState = \"poster\" | \"loading\" | \"running\" | \"fallback\";\n\n/** The heavy module fetched on upgrade. */\ntype CoreModule = typeof import(\"../core-loader\");\n\nexport interface WaveOptions {\n /** Poster URL / data-URI. Defaults to adopting the container's `<img data-wave3d-poster>` (SSR). */\n poster?: string;\n /** Wait until the container nears the viewport before fetching the engine. Default true. */\n lazy?: boolean;\n /** IntersectionObserver margin for the lazy trigger. Default \"200px\". */\n rootMargin?: string;\n /** \"auto\" probes WebGL (with failIfMajorPerformanceCaveat); \"force\" skips the probe; \"off\" stays a poster. */\n webgl?: \"auto\" | \"force\" | \"off\";\n /** Forward prefers-reduced-motion to the renderer (freezes to a full static frame). Default true. */\n respectReducedMotion?: boolean;\n /** With reduced motion: \"static\" upgrades to a frozen frame; \"poster\" stays a poster. Default \"static\". */\n reducedMotionBehavior?: \"static\" | \"poster\";\n /** Keep a permanent poster when the user has Save-Data on. Default true. */\n respectSaveData?: boolean;\n /** Poster→canvas crossfade duration (ms). Default 300. */\n fadeMs?: number;\n /** Start paused. */\n paused?: boolean;\n onReady?(renderer: WaveRenderer): void;\n onFallback?(reason: FallbackReason): void;\n onStateChange?(state: WaveState): void;\n /** Seam for the standalone/CDN build to supply the core synchronously (three already bundled). */\n loadCore?(): Promise<CoreModule>;\n}\n\nexport interface WaveHandle {\n readonly state: WaveState;\n readonly renderer: WaveRenderer | null;\n /** Merge a partial config. Staged before upgrade; after, setConfig() then refreshPlayback(). */\n set(config: Partial<StudioConfig>): void;\n play(): void;\n pause(): void;\n /** Safe to call in any state (aborts a pending upgrade, disposes a live renderer, removes the poster). */\n destroy(): void;\n}\n\n/**\n * The shell implementation. `loadCore` is an explicit parameter (not read from options) so the\n * standalone/CDN build can pass a synchronous core and NOT bundle the dynamic-import path — its\n * output stays a single file. The public {@link createWave} supplies the dynamic-import default.\n */\nexport function createWaveImpl(\n loadCore: () => Promise<CoreModule>,\n container: HTMLElement,\n config: Partial<StudioConfig>,\n options: WaveOptions,\n): WaveHandle {\n const {\n lazy = true,\n rootMargin = \"200px\",\n webgl = \"auto\",\n respectReducedMotion = true,\n reducedMotionBehavior = \"static\",\n respectSaveData = true,\n fadeMs = 300,\n } = options;\n\n let state: WaveState = \"poster\";\n let renderer: WaveRenderer | null = null;\n let staged: Partial<StudioConfig> = { ...config };\n if (options.paused !== undefined) staged.paused = options.paused;\n\n let aborted = false;\n let io: IntersectionObserver | null = null;\n let lostTimer: ReturnType<typeof setTimeout> | undefined;\n let lossCount = 0;\n\n ensurePositioned(container);\n const poster: Poster | null = setupPoster(container, options.poster);\n\n function setState(next: WaveState): void {\n if (state === next) return;\n state = next;\n options.onStateChange?.(next);\n }\n\n function fallback(reason: FallbackReason): void {\n setState(\"fallback\");\n poster?.show();\n options.onFallback?.(reason);\n }\n\n function onContextRestored(): void {\n clearTimeout(lostTimer); // three rebuilt the context in time; stay live\n }\n\n function onContextLost(): void {\n lossCount += 1;\n clearTimeout(lostTimer);\n if (lossCount >= 2) {\n teardownRenderer();\n fallback(\"context-lost\");\n return;\n }\n // three (WaveRenderer) tries to restore; if it hasn't within ~4s, give up to the poster.\n lostTimer = setTimeout(() => {\n teardownRenderer();\n fallback(\"context-lost\");\n }, 4000);\n }\n\n function teardownRenderer(): void {\n if (!renderer) return;\n const canvas = renderer.renderer.domElement;\n canvas.removeEventListener(\"webglcontextlost\", onContextLost);\n canvas.removeEventListener(\"webglcontextrestored\", onContextRestored);\n renderer.dispose();\n renderer = null;\n }\n\n async function upgrade(): Promise<void> {\n setState(\"loading\");\n let core: CoreModule;\n try {\n core = await loadCore();\n } catch {\n if (!aborted) fallback(\"load-error\");\n return;\n }\n if (aborted) return;\n\n const full: StudioConfig = { ...core.createDefaultConfig(), ...staged };\n const rendererOptions: WaveRendererOptions = { respectReducedMotion };\n renderer = new core.WaveRenderer(container, full, rendererOptions);\n const canvas = renderer.renderer.domElement;\n canvas.addEventListener(\"webglcontextlost\", onContextLost, false);\n canvas.addEventListener(\"webglcontextrestored\", onContextRestored, false);\n renderer.start();\n setState(\"running\");\n options.onReady?.(renderer);\n\n if (poster) {\n // Crossfade only after two frames, so the wave has definitely painted first.\n requestAnimationFrame(() =>\n requestAnimationFrame(() => {\n if (!aborted && renderer) poster.fadeOut(fadeMs);\n }),\n );\n }\n }\n\n function probeAndUpgrade(): void {\n if (aborted) return;\n if (webgl === \"auto\" && !hasWebGL()) {\n fallback(\"no-webgl\");\n return;\n }\n void upgrade();\n }\n\n function begin(): void {\n // Permanent-poster gates (checked before any lazy wait or engine fetch).\n if (webgl === \"off\") return; // deliberate poster-only mode — stay \"poster\", no fallback callback\n if (respectSaveData && prefersReducedData()) return fallback(\"save-data\");\n if (respectReducedMotion && reducedMotionBehavior === \"poster\" && prefersReducedMotion()) {\n return fallback(\"reduced-motion\");\n }\n if (lazy && typeof IntersectionObserver !== \"undefined\") {\n io = new IntersectionObserver(\n (entries) => {\n if (entries.some((e) => e.isIntersecting)) {\n io?.disconnect();\n io = null;\n probeAndUpgrade();\n }\n },\n { rootMargin },\n );\n io.observe(container);\n } else {\n probeAndUpgrade();\n }\n }\n\n const handle: WaveHandle = {\n get state() {\n return state;\n },\n get renderer() {\n return renderer;\n },\n set(next) {\n if (renderer) {\n renderer.setConfig({ ...renderer.getConfig(), ...next });\n renderer.refreshPlayback(); // setConfig doesn't re-evaluate `paused` on its own\n } else {\n staged = { ...staged, ...next };\n }\n },\n play() {\n if (renderer) {\n renderer.getConfig().paused = false;\n renderer.refreshPlayback();\n } else {\n staged.paused = false;\n }\n },\n pause() {\n if (renderer) {\n renderer.getConfig().paused = true;\n renderer.refreshPlayback();\n } else {\n staged.paused = true;\n }\n },\n destroy() {\n aborted = true;\n io?.disconnect();\n io = null;\n clearTimeout(lostTimer);\n teardownRenderer();\n poster?.remove();\n },\n };\n\n begin();\n return handle;\n}\n\n/**\n * Mount a self-optimizing wave into a container: shows a poster immediately, then — lazily, and only\n * when the browser can actually run it — fetches the engine, builds the renderer, and crossfades in.\n * Falls back to the poster on no-WebGL / save-data / reduced-motion / context-loss / load errors.\n * No static three import: the engine arrives via a dynamic import, so the shell stays tiny.\n */\nexport function createWave(\n container: HTMLElement,\n config: Partial<StudioConfig> = {},\n options: WaveOptions = {},\n): WaveHandle {\n return createWaveImpl(\n options.loadCore ?? (() => import(\"../core-loader\")),\n container,\n config,\n options,\n );\n}\n\n/** The drop-in embed contract: an alias of {@link createWave}. */\nexport const mountWave = createWave;\n"],"mappings":"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"}
1
+ {"version":3,"file":"createWave.js","names":[],"sources":["../../src/shell/createWave.ts"],"sourcesContent":["import type { StudioConfig } from \"../config/model\";\nimport type { WaveRenderer, WaveRendererOptions } from \"../renderer/WaveRenderer\";\nimport { hasWebGL, prefersReducedMotion, prefersReducedData } from \"./probe\";\nimport { setupPoster, ensurePositioned, type Poster } from \"./poster\";\n\n/** Why the shell showed the poster instead of a live wave. */\nexport type FallbackReason =\n | \"no-webgl\"\n | \"reduced-motion\"\n | \"save-data\"\n | \"context-lost\"\n | \"load-error\";\n\n/** poster → loading → running, or → fallback (permanent poster). */\nexport type WaveState = \"poster\" | \"loading\" | \"running\" | \"fallback\";\n\n/** The heavy module fetched on upgrade. */\ntype CoreModule = typeof import(\"../core-loader\");\n\nexport interface WaveOptions {\n /** Poster URL / data-URI. Defaults to adopting the container's `<img data-wave3d-poster>` (SSR). */\n poster?: string;\n /** Wait until the container nears the viewport before fetching the engine. Default true. */\n lazy?: boolean;\n /** IntersectionObserver margin for the lazy trigger. Default \"200px\". */\n rootMargin?: string;\n /** \"auto\" probes WebGL (with failIfMajorPerformanceCaveat); \"force\" skips the probe; \"off\" stays a poster. */\n webgl?: \"auto\" | \"force\" | \"off\";\n /** Forward prefers-reduced-motion to the renderer (freezes to a full static frame). Default true. */\n respectReducedMotion?: boolean;\n /** With reduced motion: \"static\" upgrades to a frozen frame; \"poster\" stays a poster. Default \"static\". */\n reducedMotionBehavior?: \"static\" | \"poster\";\n /** Keep a permanent poster when the user has Save-Data on. Default true. */\n respectSaveData?: boolean;\n /** Poster→canvas crossfade duration (ms). Default 300. */\n fadeMs?: number;\n /** Start paused. */\n paused?: boolean;\n onReady?(renderer: WaveRenderer): void;\n onFallback?(reason: FallbackReason): void;\n onStateChange?(state: WaveState): void;\n /** Seam for the standalone/CDN build to supply the core synchronously (three already bundled). */\n loadCore?(): Promise<CoreModule>;\n}\n\nexport interface SnapshotOptions {\n /** Image MIME type. Default `\"image/webp\"`. */\n type?: string;\n /** Encoder quality 0–1 for lossy types. */\n quality?: number;\n /** Render with a transparent background. Default true. */\n transparent?: boolean;\n /** Render a fixed animation-time for a reproducible frame (default: the live frame). Poster\n * captures should pass `0` — the frame the wave opens on, so the file doesn't churn per capture. */\n time?: number;\n}\n\nexport interface WaveHandle {\n readonly state: WaveState;\n readonly renderer: WaveRenderer | null;\n /** Capture the current live frame as an image Blob (a poster you can host/cache). Resolves `null`\n * until the wave is running (poster / fallback / pre-upgrade) — wait for {@link WaveOptions.onReady}\n * (or the element's `wave3d-ready` event) first. */\n snapshot(options?: SnapshotOptions): Promise<Blob | null>;\n /** Merge a partial config. Staged before upgrade; after, setConfig() then refreshPlayback(). */\n set(config: Partial<StudioConfig>): void;\n play(): void;\n pause(): void;\n /** Safe to call in any state (aborts a pending upgrade, disposes a live renderer, removes the poster). */\n destroy(): void;\n}\n\n/**\n * The shell implementation. `loadCore` is an explicit parameter (not read from options) so the\n * standalone/CDN build can pass a synchronous core and NOT bundle the dynamic-import path — its\n * output stays a single file. The public {@link createWave} supplies the dynamic-import default.\n */\nexport function createWaveImpl(\n loadCore: () => Promise<CoreModule>,\n container: HTMLElement,\n config: Partial<StudioConfig>,\n options: WaveOptions,\n): WaveHandle {\n const {\n lazy = true,\n rootMargin = \"200px\",\n webgl = \"auto\",\n respectReducedMotion = true,\n reducedMotionBehavior = \"static\",\n respectSaveData = true,\n fadeMs = 300,\n } = options;\n\n let state: WaveState = \"poster\";\n let renderer: WaveRenderer | null = null;\n let staged: Partial<StudioConfig> = { ...config };\n if (options.paused !== undefined) staged.paused = options.paused;\n\n let aborted = false;\n let io: IntersectionObserver | null = null;\n let lostTimer: ReturnType<typeof setTimeout> | undefined;\n let lossCount = 0;\n\n ensurePositioned(container);\n const poster: Poster | null = setupPoster(container, options.poster);\n\n function setState(next: WaveState): void {\n if (state === next) return;\n state = next;\n options.onStateChange?.(next);\n }\n\n function fallback(reason: FallbackReason): void {\n setState(\"fallback\");\n poster?.show();\n options.onFallback?.(reason);\n }\n\n function onContextRestored(): void {\n clearTimeout(lostTimer); // three rebuilt the context in time; stay live\n }\n\n function onContextLost(): void {\n lossCount += 1;\n clearTimeout(lostTimer);\n if (lossCount >= 2) {\n teardownRenderer();\n fallback(\"context-lost\");\n return;\n }\n // three (WaveRenderer) tries to restore; if it hasn't within ~4s, give up to the poster.\n lostTimer = setTimeout(() => {\n teardownRenderer();\n fallback(\"context-lost\");\n }, 4000);\n }\n\n function teardownRenderer(): void {\n if (!renderer) return;\n const canvas = renderer.renderer.domElement;\n canvas.removeEventListener(\"webglcontextlost\", onContextLost);\n canvas.removeEventListener(\"webglcontextrestored\", onContextRestored);\n renderer.dispose();\n renderer = null;\n }\n\n async function upgrade(): Promise<void> {\n setState(\"loading\");\n let core: CoreModule;\n try {\n core = await loadCore();\n } catch {\n if (!aborted) fallback(\"load-error\");\n return;\n }\n if (aborted) return;\n\n const full: StudioConfig = { ...core.createDefaultConfig(), ...staged };\n const rendererOptions: WaveRendererOptions = { respectReducedMotion };\n renderer = new core.WaveRenderer(container, full, rendererOptions);\n const canvas = renderer.renderer.domElement;\n canvas.addEventListener(\"webglcontextlost\", onContextLost, false);\n canvas.addEventListener(\"webglcontextrestored\", onContextRestored, false);\n renderer.start();\n setState(\"running\");\n options.onReady?.(renderer);\n\n if (poster) {\n // Crossfade only after two frames, so the wave has definitely painted first.\n requestAnimationFrame(() =>\n requestAnimationFrame(() => {\n if (!aborted && renderer) poster.fadeOut(fadeMs);\n }),\n );\n }\n }\n\n function probeAndUpgrade(): void {\n if (aborted) return;\n if (webgl === \"auto\" && !hasWebGL()) {\n fallback(\"no-webgl\");\n return;\n }\n void upgrade();\n }\n\n function begin(): void {\n // Permanent-poster gates (checked before any lazy wait or engine fetch).\n if (webgl === \"off\") return; // deliberate poster-only mode — stay \"poster\", no fallback callback\n if (respectSaveData && prefersReducedData()) return fallback(\"save-data\");\n if (respectReducedMotion && reducedMotionBehavior === \"poster\" && prefersReducedMotion()) {\n return fallback(\"reduced-motion\");\n }\n if (lazy && typeof IntersectionObserver !== \"undefined\") {\n io = new IntersectionObserver(\n (entries) => {\n if (entries.some((e) => e.isIntersecting)) {\n io?.disconnect();\n io = null;\n probeAndUpgrade();\n }\n },\n { rootMargin },\n );\n io.observe(container);\n } else {\n probeAndUpgrade();\n }\n }\n\n const handle: WaveHandle = {\n get state() {\n return state;\n },\n get renderer() {\n return renderer;\n },\n snapshot(opts = {}) {\n if (!renderer) return Promise.resolve(null);\n const { type = \"image/webp\", quality, transparent = true, time } = opts;\n return renderer.captureImage(type, transparent, quality, time);\n },\n set(next) {\n if (renderer) {\n renderer.setConfig({ ...renderer.getConfig(), ...next });\n renderer.refreshPlayback(); // setConfig doesn't re-evaluate `paused` on its own\n } else {\n staged = { ...staged, ...next };\n }\n },\n play() {\n if (renderer) {\n renderer.getConfig().paused = false;\n renderer.refreshPlayback();\n } else {\n staged.paused = false;\n }\n },\n pause() {\n if (renderer) {\n renderer.getConfig().paused = true;\n renderer.refreshPlayback();\n } else {\n staged.paused = true;\n }\n },\n destroy() {\n aborted = true;\n io?.disconnect();\n io = null;\n clearTimeout(lostTimer);\n teardownRenderer();\n poster?.remove();\n },\n };\n\n begin();\n return handle;\n}\n\n/**\n * Mount a self-optimizing wave into a container: shows a poster immediately, then — lazily, and only\n * when the browser can actually run it — fetches the engine, builds the renderer, and crossfades in.\n * Falls back to the poster on no-WebGL / save-data / reduced-motion / context-loss / load errors.\n * No static three import: the engine arrives via a dynamic import, so the shell stays tiny.\n */\nexport function createWave(\n container: HTMLElement,\n config: Partial<StudioConfig> = {},\n options: WaveOptions = {},\n): WaveHandle {\n return createWaveImpl(\n options.loadCore ?? (() => import(\"../core-loader\")),\n container,\n config,\n options,\n );\n}\n\n/** The drop-in embed contract: an alias of {@link createWave}. */\nexport const mountWave = createWave;\n"],"mappings":"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"}