1const e=`<!-- 2 Shader Studio (ID: mu7j1h) 3 Generated on: 2026-10-07T03:15:11.534Z 4 License: Commercial / Max Tier (Own business, finished client projects, and reusable template and asset redistribution permitted) 5 6 Post-Processing Effects: 7 1. bloom - intensity: 0.18, threshold: 0.5, radius: 0.9 8 2. colorgrade - brightness: 0, contrast: 0.16, saturation: -0.08, temperature: -0.04 9 3. vignette - intensity: 0.28, smoothness: 0.7, roundness: 0.94 10 11 Timeline Animations: 12 No timeline animations configured 13 14 Timeline Playback: 15 - Playback: starts as soon as the shader loads. 16 - Change it by editing "playback" in the timelineData object below ('auto' | 'once' | 'visible' | 'click'). 17 - The player also exposes play(), pause(), seek(t), restart() and dispose() if you want to drive it yourself. 18 - Install GSAP (npm install gsap) to unlock advanced easing curves; without it, non-linear easings fall back to linear. 19 - Full timeline JSON is included at the end of this file if you want to customize the playback logic manually. 20 21 Tailwind layout (optional): 22 The canvas fills its parent (width/height 100%). Pass Tailwind classes via the 23 'canvasClass' prop, e.g. <MuseumLiquidPlasma :canvas-class="'h-screen w-full'" />. 24 For a full-bleed hero wrap it: 25 <div class="relative h-screen w-full"> 26 <div class="absolute inset-0"><MuseumLiquidPlasma canvas-class="h-full w-full" /></div> 27 <div class="relative z-10 ...">content</div> 28 </div> 29 30 CSS variable binding (optional): 31 Pass a 'cssVars' map to drive uniforms from CSS variables on the parent element. 32 Example: <MuseumLiquidPlasma :css-vars="{ u_color1: '--shader-color1', u_speed: '--shader-speed' }" /> 33 and in CSS: .parent { --shader-color1: #ff006e; --shader-speed: 0.5; } 34 35 Interaction uniforms exposed: u_scrollVelocity (scroll velocity -1..1), 36 u_gyro (device orientation vec3 -1..1), u_clickPosition (vec2), u_lastClickTime (sec). 37--> 38<template> 39 <canvas ref="canvasEl_mu7j1h" :class="['shader-canvas-mu7j1h', props.canvasClass]"></canvas> 40</template> 41 42<script setup> 43import { ref, onMounted, onUnmounted } from 'vue'; 44import * as THREE from 'three'; 45 46const props = defineProps({ cssVars: { type: Object, default: () => null }, canvasClass: { type: String, default: '' } }); 47 48const canvasEl_mu7j1h = ref(null); 49 50const vertexShader = \`varying vec2 vUv; 51void main() { 52 vUv = uv; 53 gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 54}\`; 55 56const fragmentShader = \`precision highp float; 57 58// Safely handle smoothstep on strict software renderers (prevents NaN when edge0 >= edge1) 59float safe_smoothstep(float edge0, float edge1, float x) { 60 if (edge0 <= edge1) return smoothstep(edge0, edge1, x); 61 return 1.0 - smoothstep(edge1, edge0, x); 62} 63vec2 safe_smoothstep(float edge0, float edge1, vec2 x) { 64 if (edge0 <= edge1) return smoothstep(edge0, edge1, x); 65 return 1.0 - smoothstep(edge1, edge0, x); 66} 67vec3 safe_smoothstep(float edge0, float edge1, vec3 x) { 68 if (edge0 <= edge1) return smoothstep(edge0, edge1, x); 69 return 1.0 - smoothstep(edge1, edge0, x); 70} 71vec4 safe_smoothstep(float edge0, float edge1, vec4 x) { 72 if (edge0 <= edge1) return smoothstep(edge0, edge1, x); 73 return 1.0 - smoothstep(edge1, edge0, x); 74} 75vec2 safe_smoothstep(vec2 edge0, vec2 edge1, vec2 x) { 76 return vec2( 77 (edge0.x <= edge1.x) ? smoothstep(edge0.x, edge1.x, x.x) : (1.0 - smoothstep(edge1.x, edge0.x, x.x)), 78 (edge0.y <= edge1.y) ? smoothstep(edge0.y, edge1.y, x.y) : (1.0 - smoothstep(edge1.y, edge0.y, x.y)) 79 ); 80} 81vec3 safe_smoothstep(vec3 edge0, vec3 edge1, vec3 x) { 82 return vec3( 83 (edge0.x <= edge1.x) ? smoothstep(edge0.x, edge1.x, x.x) : (1.0 - smoothstep(edge1.x, edge0.x, x.x)), 84 (edge0.y <= edge1.y) ? smoothstep(edge0.y, edge1.y, x.y) : (1.0 - smoothstep(edge1.y, edge0.y, x.y)), 85 (edge0.z <= edge1.z) ? smoothstep(edge0.z, edge1.z, x.z) : (1.0 - smoothstep(edge1.z, edge0.z, x.z)) 86 ); 87} 88vec4 safe_smoothstep(vec4 edge0, vec4 edge1, vec4 x) { 89 return vec4( 90 (edge0.x <= edge1.x) ? smoothstep(edge0.x, edge1.x, x.x) : (1.0 - smoothstep(edge1.x, edge0.x, x.x)), 91 (edge0.y <= edge1.y) ? smoothstep(edge0.y, edge1.y, x.y) : (1.0 - smoothstep(edge1.y, edge0.y, x.y)), 92 (edge0.z <= edge1.z) ? smoothstep(edge0.z, edge1.z, x.z) : (1.0 - smoothstep(edge1.z, edge0.z, x.z)), 93 (edge0.w <= edge1.w) ? smoothstep(edge0.w, edge1.w, x.w) : (1.0 - smoothstep(edge1.w, edge0.w, x.w)) 94 ); 95} 96#define smoothstep safe_smoothstep 97 98// Add a glow to a colour without letting it change hue on the way to white. 99// 100// A plain "color += tint * glow" clips the brightest channel first, so a 101// saturated orange loses red headroom while green keeps climbing and the result 102// walks to yellow â the layer never looks brighter, only wronger. Lifting and 103// then normalising by the lifted peak keeps the channel ratios, and the crest 104// blooms toward white only as far as it actually overshot. Below the clip point 105// this is exactly "base + add", so it changes no pixel that was already legal. 106vec3 glow_lift(vec3 base, vec3 add) { 107 vec3 lifted = base + add; 108 float peak = max(max(lifted.r, lifted.g), max(lifted.b, 1.0)); 109 return mix(lifted / peak, vec3(1.0), clamp((peak - 1.0) * 0.25, 0.0, 0.75)); 110} 111 112varying vec2 vUv; 113uniform float u_time; 114uniform vec2 u_resolution; 115// Global playback rate. Was a baked literal until the timeline needed to 116// animate it without recompiling; see the time assignment in main() below. 117uniform float u_timeSpeed; 118// Pointer uniforms â see app/lib/shaders/pointer-state.ts 119uniform vec2 u_mouse; // smoothed pointer position (0..1, y up) 120uniform vec2 u_mouseVelocity; // pointer velocity in uv/second, clamped to -1..1 121uniform float u_mouseStrength; // 0..1 pointer presence; decays to 0 on leave 122uniform vec2 u_mouseTrail[8]; // recent positions, [0] = newest 123 124// Interactive input uniforms 125uniform float u_scrollVelocity; // scroll velocity (-1..1), driven by JS
126uniform vec3 u_gyro; // device orientation (x,y,z in -1..1) 127uniform vec2 u_clickPosition; // last click position (vec2 0..1) 128uniform float u_lastClickTime; // time of last click (seconds; -10 = none yet) 129 130vec2 spcAspect() { 131 return u_resolution.x > 0.0 && u_resolution.y > 0.0 132 ? vec2(u_resolution.x / u_resolution.y, 1.0) 133 : vec2(1.0); 134} 135 136vec2 spcToUvSpace(vec2 p) { 137 return (p - 0.5) * spcAspect() + 0.5; 138} 139 140vec2 spcFromUvSpace(vec2 p) { 141 return (p - 0.5) / spcAspect() + 0.5; 142} 143 144// Audio uniforms 145uniform float u_audioHigh; 146uniform float u_audioMid; 147uniform float u_audioBass; 148uniform float u_audioLevel; 149 150 151// Brand theme uniforms (no active theme â defaults emitted) 152uniform vec3 u_brandBackground; 153uniform vec3 u_brandSurface; 154uniform vec3 u_brandText; 155uniform vec3 u_brandTextMuted; 156uniform vec3 u_brandAccent; 157uniform vec3 u_brandAccentAlt; 158uniform vec3 u_brandRamp[8]; 159uniform int u_brandRampLength; 160uniform float u_brandGrainAmount; 161uniform float u_brandNoiseScale; 162uniform float u_brandSoftness; 163uniform float u_brandAmbientSpeed; 164 165 166// Dynamic Layer Uniforms 167uniform float u_layer_0_opacity; 168uniform vec3 u_layer_0_tint1; 169uniform vec3 u_layer_0_tint2; 170uniform vec3 u_layer_0_tint3; 171uniform float u_layer_0_scale; 172uniform float u_layer_0_speed; 173uniform float u_layer_0_viscosity; 174uniform float u_layer_0_intensity; 175 176// PBR Layering Struct 177struct LayerOutput { 178 vec4 color; 179 vec3 normal; 180 float height; 181 float roughness; 182}; 183 184 185 186float hash21_layer_0_fluid_sim(vec2 p) { 187 p = fract(p * vec2(123.34, 345.45)); 188 p += dot(p, p + 34.345); 189 return fract(p.x * p.y); 190} 191float vnoise_layer_0_fluid_sim(vec2 p) { 192 vec2 i = floor(p); vec2 f = fract(p); 193 vec2 u = f * f * (3.0 - 2.0 * f); 194 float a = hash21_layer_0_fluid_sim(i); 195 float b = hash21_layer_0_fluid_sim(i + vec2(1.0, 0.0)); 196 float c = hash21_layer_0_fluid_sim(i + vec2(0.0, 1.0)); 197 float d = hash21_layer_0_fluid_sim(i + vec2(1.0, 1.0)); 198 return mix(mix(a, b, u.x), mix(c, d, u.x), u.y); 199} 200float fbm_layer_0_fluid_sim(vec2 p) { 201 float v = 0.0; float a = 0.5; 202 for (int i = 0; i < 5; i++) { v += a * vnoise_layer_0_fluid_sim(p); p *= 2.0; a *= 0.5; } 203 return v; 204} 205 206vec2 layer_0_fluid_sim_flow(vec2 uv, float time) { 207 vec2 q = vec2(fbm_layer_0_fluid_sim(uv * 3.0 + time * 0.1), fbm_layer_0_fluid_sim(uv * 3.0 + vec2(5.2, 1.3) + time * 0.12)); 208 vec2 r = vec2(fbm_layer_0_fluid_sim(uv * 3.0 + q + vec2(1.7, 9.2) + time * 0.15), fbm_layer_0_fluid_sim(uv * 3.0 + q + vec2(8.3, 2.8) + time * 0.13)); 209 return r; 210} 211vec4 layer_0_fluid_sim_inner(vec2 uv, float time) { 212 float t = time * 0.6; 213 vec2 r = layer_0_fluid_sim_flow(uv, t); 214 float f = fbm_layer_0_fluid_sim(uv * 3.0 + r * 0.85 + t * 0.2); 215 vec3 c1 = vec3(0.055, 0.647, 0.914); 216 vec3 c2 = vec3(0.659, 0.333, 0.969); 217 vec3 c3 = vec3(0.133, 0.827, 0.933); 218 vec3 col = mix(c1, c2, smoothstep(0.2, 0.6, f)); 219 col = mix(col, c3, smoothstep(0.5, 0.9, length(r))); 220 col *= 1.0; 221 return vec4(clamp(col, 0.0, 1.0), 1.0); 222} 223 224LayerOutput layer_0_fluid_sim(vec2 uv, float time, LayerOutput previousLayer) { 225 vec2 distortedUv = uv; 226 #if defined(GL_OES_standard_derivatives) || __VERSION__ >= 300 227 float disp = 0.0; 228 if (abs(disp) > 0.0001) { 229 vec2 heightGradient = vec2(dFdx(previousLayer.height), dFdy(previousLayer.height)); 230 distortedUv += heightGradient * disp; 231 } 232 #endif 233 vec4 color = layer_0_fluid_sim_inner(distortedUv, time); 234 float luminance = dot(color.rgb, vec3(0.299, 0.587, 0.114)); 235 float height = luminance * color.a * 0.0; 236 vec3 normal = vec3(0.0, 0.0, 1.0); 237 #if defined(GL_OES_standard_derivatives) || __VERSION__ >= 300 238 if (height > 0.0001) { 239 vec2 dHeight = vec2(dFdx(height), dFdy(height)); 240 normal = normalize(vec3(-dHeight * 10.0, 1.0)); 241 } 242 #endif 243 float roughness = 0.5; 244 return LayerOutput(color, normal, height, roughness); 245} 246 247 248vec4 blend_normal(vec4 base, vec4 blend) { 249 return mix(base, blend, blend.a); 250} 251 252 253void main() { 254 vec2 aspectVec = spcAspect(); 255 vec2 uv = (vUv - 0.5) * aspectVec + 0.5; 256 // Map the pointer into the same aspect-corrected space as uv. Comparing a 257 // raw 0..1 u_mouse against a corrected uv leaves the effect the right shape 258 // but in the wrong place â horizontally offset from the cursor by an amount
259 // that grows with the aspect ratio. 260 vec2 mouse = spcToUvSpace(u_mouse); 261 // Uniform rather than a baked literal so the timeline can drive it without 262 // a recompile. The literal was also rounded to one decimal, which re-timed 263 // any composition whose speed was not a multiple of 0.1; the uniform now 264 // carries the authored value exactly. 265 float time = u_time * u_timeSpeed; 266 267 // Initialize PBR LayerOutput struct 268 LayerOutput accumulated; 269 accumulated.color = vec4(vec3(0.020, 0.031, 0.086), 1.0); 270 accumulated.normal = vec3(0.0, 0.0, 1.0); 271 accumulated.height = 0.0; 272 accumulated.roughness = 0.5; 273 274 // Layer 0: Fluid Simulation 275 { 276 vec2 layerUv = uv; 277 LayerOutput layerOut = layer_0_fluid_sim(layerUv, time, accumulated); 278 layerOut.color.a *= u_layer_0_opacity; 279 float layerAlpha_0 = layerOut.color.a; 280 accumulated.color = blend_normal(accumulated.color, layerOut.color); 281 vec3 blendedNormal_0 = normalize(vec3(accumulated.normal.xy + layerOut.normal.xy, accumulated.normal.z * layerOut.normal.z)); 282 accumulated.normal = normalize(mix(accumulated.normal, blendedNormal_0, layerOut.color.a)); 283 accumulated.height = mix(accumulated.height, accumulated.height + layerOut.height, layerOut.color.a); 284 accumulated.roughness = mix(accumulated.roughness, layerOut.roughness, layerOut.color.a); 285 } 286 287 // Flat 2D rendering bypassing viewport lighting 288 vec4 finalColor = accumulated.color; 289 290 // Post-Processing Effects 291 vec3 color = finalColor.rgb; 292 vec2 resolution = u_resolution; 293 294 // PostFX 0: bloom 295 { 296 vec3 bloomColor = color; 297 298 // Extract bright areas 299 float luminance = dot(bloomColor, vec3(0.299, 0.587, 0.114)); 300 vec3 bright = bloomColor * smoothstep(0.5 - 0.1, 0.5 + 0.1, luminance); 301 302 // Simple blur approximation 303 vec3 blurred = bright; 304 float blurSize = 0.9 * 0.002; 305 306 for(float x = -2.0; x <= 2.0; x += 1.0) { 307 for(float y = -2.0; y <= 2.0; y += 1.0) { 308 vec2 offset = vec2(x, y) * blurSize; 309 vec2 sampleUV = uv + offset; 310 vec3 sampleColor = color; // In real implementation, would sample from texture 311 float sampleLum = dot(sampleColor, vec3(0.299, 0.587, 0.114));
312 blurred += sampleColor * smoothstep(0.5 - 0.1, 0.5 + 0.1, sampleLum) * 0.04; 313 } 314 } 315 316 color = mix(color, color + blurred * 0.18, 0.18); 317 } 318 319 // PostFX 1: colorgrade 320 { 321 // Brightness 322 color += vec3(0.0); 323 324 // Contrast 325 color = (color - 0.5) * (1.0 + 0.16) + 0.5; 326 327 // Saturation 328 vec3 gray = vec3(dot(color, vec3(0.299, 0.587, 0.114))); 329 color = mix(gray, color, 1.0 + -0.08); 330 331 // Temperature (warm/cool) 332 color.r += -0.04 * 0.3; 333 color.b -= -0.04 * 0.3; 334 335 // Clamp 336 color = clamp(color, 0.0, 1.0); 337 } 338 339 // PostFX 2: vignette 340 { 341 vec2 vignetteUV = uv - 0.5; 342 vignetteUV.x /= mix(1.0, resolution.x / resolution.y, 0.94); 343 float vignette = length(vignetteUV); 344 // Use 1.0 - smoothstep(min, max) to avoid undefined behavior when edge0 >= edge1 345 vignette = 1.0 - smoothstep(max(0.0, 0.7 - 0.5), 0.7 + 0.01, vignette); 346 color *= mix(1.0, vignette, 0.28); 347 } 348 349 finalColor = vec4(clamp(color, 0.0, 1.0), finalColor.a); 350 // Opaque background: the matte covers every pixel, so the canvas is 351 // fully opaque regardless of what the per-layer blends left in alpha. 352 gl_FragColor = vec4(finalColor.rgb, 1.0); 353}\`; 354 355const shaderTextures = []; 356 357function createShaderPlaceholderTexture(textureInfo) { 358 if (textureInfo.type === 'cubemap') { 359 const faces = []; 360 for (let index = 0; index < 6; index++) { 361 const canvas = document.createElement('canvas'); 362 canvas.width = 4; 363 canvas.height = 4; 364 const context = canvas.getContext('2d'); 365 if (context) { 366 context.fillStyle = '#000000'; 367 context.fillRect(0, 0, 4, 4); 368 } 369 faces.push(canvas); 370 } 371 const texture = new THREE.CubeTexture(faces); 372 texture.needsUpdate = true; 373 return texture; 374 } 375 376 if (textureInfo.type === 'volume' && THREE.Data3DTexture) { 377 const size = 2; 378 const data = new Uint8Array(size * size * size); 379 const texture = new THREE.Data3DTexture(data, size, size, size); 380 texture.format = THREE.RedFormat; 381 texture.type = THREE.UnsignedByteType; 382 texture.minFilter = THREE.LinearFilter; 383 texture.magFilter = THREE.LinearFilter; 384 texture.wrapS = THREE.RepeatWrapping; 385 texture.wrapT = THREE.RepeatWrapping; 386 texture.wrapR = THREE.RepeatWrapping; 387 texture.needsUpdate = true; 388 return texture; 389 } 390 391 const alpha = textureInfo.uniformName.startsWith('u_selectionMask_') ? 0 : 255; 392 const texture = new THREE.DataTexture(new Uint8Array([0, 0, 0, alpha]), 1, 1, THREE.RGBAFormat); 393 texture.needsUpdate = true; 394 return texture; 395} 396 397function disposeShaderTextureValue(value) { 398 if (value && typeof value.dispose === 'function') { 399 value.dispose(); 400 } 401} 402 403function addShaderTexturePlaceholders(uniforms) { 404 shaderTextures.forEach(textureInfo => { 405 uniforms[textureInfo.uniformName] = { value: createShaderPlaceholderTexture(textureInfo) }; 406 }); 407} 408 409function loadShaderTextures(material) { 410 shaderTextures.forEach(textureInfo => { 411 if (textureInfo.type === 'cubemap') { 412 const faces = textureInfo.faces; 413 if (!faces || !faces.px || !faces.nx || !faces.py || !faces.ny || !faces.pz || !faces.nz) return; 414 415 new THREE.CubeTextureLoader().load( 416 [faces.px, faces.nx, faces.py, faces.ny, faces.pz, faces.nz], 417 (texture) => { 418 texture.minFilter = THREE.LinearFilter; 419 texture.magFilter = THREE.LinearFilter; 420 const uniform = material.uniforms[textureInfo.uniformName]; 421 if (!uniform) return; 422 disposeShaderTextureValue(uniform.value); 423 uniform.value = texture; 424 material.needsUpdate = true; 425 } 426 ); 427 return; 428 } 429 430 if (textureInfo.type === 'volume' && THREE.Data3DTexture) { 431 fetch(textureInfo.url) 432 .then(response => { 433 if (!response.ok) throw new Error('Failed to load volume texture'); 434 return response.arrayBuffer(); 435 }) 436 .then(buffer => { 437 const size = 32;
438 const texture = new THREE.Data3DTexture(new Uint8Array(buffer), size, size, size); 439 texture.format = THREE.RedFormat; 440 texture.type = THREE.UnsignedByteType; 441 texture.minFilter = THREE.LinearFilter; 442 texture.magFilter = THREE.LinearFilter; 443 texture.wrapS = THREE.RepeatWrapping; 444 texture.wrapT = THREE.RepeatWrapping; 445 texture.wrapR = THREE.RepeatWrapping; 446 texture.needsUpdate = true; 447 const uniform = material.uniforms[textureInfo.uniformName]; 448 if (!uniform) return; 449 disposeShaderTextureValue(uniform.value); 450 uniform.value = texture; 451 material.needsUpdate = true; 452 }) 453 .catch(() => {}); 454 return; 455 } 456 457 new THREE.TextureLoader().load( 458 textureInfo.url, 459 (texture) => { 460 texture.wrapS = THREE.RepeatWrapping; 461 texture.wrapT = THREE.RepeatWrapping; 462 texture.minFilter = THREE.LinearFilter; 463 texture.magFilter = THREE.LinearFilter; 464 const uniform = material.uniforms[textureInfo.uniformName]; 465 if (!uniform) return; 466 disposeShaderTextureValue(uniform.value); 467 uniform.value = texture; 468 material.needsUpdate = true; 469 } 470 ); 471 }); 472} 473 474function disposeShaderTextures(material) { 475 if (!material) return; 476 shaderTextures.forEach(textureInfo => { 477 const uniform = material.uniforms[textureInfo.uniformName]; 478 if (uniform) disposeShaderTextureValue(uniform.value); 479 }); 480} 481 482/* ---------- Colour helper (module scope: the uniform block and the 483 timeline runtime both call it) ---------- */ 484// Shader colours are consumed as raw 0..1 sRGB components, exactly as 485// hexToVec3 bakes them into the shader's own literals. new THREE.Color(hex) 486// would convert sRGB -> linear and hand the shader much darker, hue-shifted 487// values than the editor preview showed, so build the colour by hand. 488function __spcColor(hex, target) { 489 var c = target && target.isColor ? target : new THREE.Color(); 490 if (typeof hex !== 'string' || hex.charAt(0) !== '#') return c; 491 var h = hex.slice(1); 492 if (h.length === 3) h = h[0] + h[0] + h[1] + h[1] + h[2] + h[2]; 493 var n = parseInt(h.slice(0, 6), 16); 494 if (!isFinite(n)) return c; 495 c.r = ((n >> 16) & 255) / 255; 496 c.g = ((n >> 8) & 255) / 255; 497 c.b = (n & 255) / 255; 498 return c; 499} 500 501 502 503onMounted(() => { 504 let renderer, scene, camera, material, animationFrameId; 505 let timelinePlayer = null; 506 const canvas = canvasEl_mu7j1h.value; 507 if (!canvas) return; 508 509 const uniforms = { 510 u_time: { value: 0.0 }, 511 u_resolution: { value: new THREE.Vector2() }, 512 u_mouse: { value: new THREE.Vector2(0.5, 0.5) }, 513 u_mouseVelocity: { value: new THREE.Vector2() }, 514 u_mouseStrength: { value: 0 }, 515 u_mouseTrail: { value: Array.from({ length: 8 }, () => new THREE.Vector2(0.5, 0.5)) }, 516 u_scrollVelocity: { value: 0 }, 517 u_gyro: { value: new THREE.Vector3() }, 518 u_clickPosition: { value: new THREE.Vector2(0.5, 0.5) }, 519 u_lastClickTime: { value: -10 }, 520 // User-defined uniforms: 521 u_timeSpeed: { value: 0.36 }, 522 u_layer_0_opacity: { value: 1 }, 523 u_layer_0_tint1: { value: __spcColor("#0ea5e9") }, 524 u_layer_0_tint2: { value: __spcColor("#a855f7") }, 525 u_layer_0_tint3: { value: __spcColor("#22d3ee") }, 526 u_layer_0_scale: { value: 3 }, 527 u_layer_0_speed: { value: 0.6 }, 528 u_layer_0_viscosity: { value: 0.85 }, 529 u_layer_0_intensity: { value: 1 } 530 }; 531 532 addShaderTexturePlaceholders(uniforms); 533 534 535 536// Pointer state (mirrors app/lib/shaders/pointer-state.ts) 537const __spcPointer = (() => { 538 const TRAIL = 8; 539 const SAMPLE = 0.04; 540 const clamp = (v, a, b) => (v < a ? a : v > b ? b : v); 541 let tx = 0.5, ty = 0.5, x = 0.5, y = 0.5, vx = 0, vy = 0; 542 let strength = 0, active = false, trailClock = 0; 543 let clickX = 0.5, clickY = 0.5, lastClickTime = -10; 544 const trail = new Array(TRAIL * 2).fill(0.5); 545 const follow = 0.18, decay = 2.5; 546 return { 547 trail, 548 get x() { return x; }, get y() { return y; }, 549 get vx() { return vx; }, get vy() { return vy; }, 550 get strength() { return strength; }, 551 get clickX() { return clickX; }, get clickY() { return clickY; }, 552 get lastClickTime() { return lastClickTime; }, 553 setTarget(nx, ny) { tx = nx; ty = ny; active = true; }, 554 setAbsent() { active = false; }, 555 setClick(nx, ny, elapsed) { clickX = nx; clickY = ny; lastClickTime = elapsed; }, 556 update(dt) { 557 const step = Math.min(Math.max(dt, 0), 0.1);
558 const k = follow >= 1 ? 1 : follow <= 0 ? 0 : 1 - Math.pow(1 - follow, step * 60); 559 const nx = x + (tx - x) * k, ny = y + (ty - y) * k; 560 if (step > 0) { 561 vx += (clamp((nx - x) / step, -1, 1) - vx) * k; 562 vy += (clamp((ny - y) / step, -1, 1) - vy) * k; 563 } 564 x = nx; y = ny; 565 strength += ((active ? 1 : 0) - strength) * Math.min(1, decay * step); 566 trailClock += step; 567 if (trailClock >= SAMPLE) { 568 trailClock = 0; 569 trail.pop(); trail.pop(); trail.unshift(x, y); 570 } 571 }, 572 /** Attach pointer listeners to an element; returns a teardown function. */ 573 attach(el) { 574 const move = (e) => { 575 const r = el.getBoundingClientRect(); 576 if (!r.width || !r.height) return; 577 __spcPointer.setTarget((e.clientX - r.left) / r.width, 1 - (e.clientY - r.top) / r.height); 578 }; 579 const leave = () => __spcPointer.setAbsent(); 580 const down = (e) => { 581 const r = el.getBoundingClientRect(); 582 if (!r.width || !r.height) return; 583 __spcPointer.setClick((e.clientX - r.left) / r.width, 1 - (e.clientY - r.top) / r.height, performance.now() / 1000); 584 }; 585 el.addEventListener('pointermove', move, { passive: true }); 586 el.addEventListener('pointerleave', leave, { passive: true }); 587 el.addEventListener('pointerdown', down, { passive: true }); 588 return () => { 589 el.removeEventListener('pointermove', move); 590 el.removeEventListener('pointerleave', leave); 591 el.removeEventListener('pointerdown', down); 592 }; 593 }, 594 }; 595})(); 596 const detachPointer = __spcPointer.attach(canvas); 597 598 // Scene 599 scene = new THREE.Scene(); 600 601 // Camera 602 camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1); 603 604 // Renderer 605 // alpha + premultipliedAlpha:false so a shader authored with a transparent 606 // background composites over whatever sits behind the canvas. A shader with 607 // an opaque background writes alpha 1 everywhere and is unaffected. 608 renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: true, premultipliedAlpha: false }); 609 renderer.setClearColor(0x000000, 0); 610 611 // Geometry 612 const geometry = new THREE.PlaneGeometry(2, 2); 613 614 // Material 615 // NoBlending: the fragment shader has already composited every layer and emits 616 // straight (un-premultiplied) alpha, so THREE must write it through untouched. 617 // Blending it again against the cleared buffer would multiply colour by alpha a 618 // second time and darken every semi-transparent pixel. 619 material = new THREE.ShaderMaterial({ 620 vertexShader, 621 fragmentShader, 622 uniforms, 623 blending: THREE.NoBlending, 624 }); 625 loadShaderTextures(material); 626 627 // Mesh 628 const mesh = new THREE.Mesh(geometry, material); 629 scene.add(mesh); 630 631 // Resize handler 632 const resizeObserver = new ResizeObserver(entries => { 633 for (let entry of entries) { 634 const { width, height } = entry.contentRect; 635 renderer.setSize(width, height, false); 636 uniforms.u_resolution.value.set(width, height); 637 } 638 }); 639 resizeObserver.observe(canvas); 640 641 // Animation loop 642 let lastTime = performance.now(); 643 644 function animate() { 645 animationFrameId = requestAnimationFrame(animate); 646 const now = performance.now(); 647 const delta = (now - lastTime) / 1000; 648 lastTime = now; 649 650 uniforms.u_time.value += delta; 651 syncCssVars(); 652 uniforms.u_scrollVelocity.value *= 0.92; // decay scroll velocity toward 0 653 __spcPointer.update(delta); 654 if (uniforms.u_mouse) uniforms.u_mouse.value.set(__spcPointer.x, __spcPointer.y); 655 if (uniforms.u_mouseVelocity) uniforms.u_mouseVelocity.value.set(__spcPointer.vx, __spcPointer.vy); 656 if (uniforms.u_mouseStrength) uniforms.u_mouseStrength.value = __spcPointer.strength; 657 if (uniforms.u_mouseTrail) { 658 const t = uniforms.u_mouseTrail.value; 659 for (let i = 0; i < t.length; i++) t[i].set(__spcPointer.trail[i * 2], __spcPointer.trail[i * 2 + 1]); 660 } 661 if (uniforms.u_clickPosition) uniforms.u_clickPosition.value.set(__spcPointer.clickX, __spcPointer.clickY); 662 if (uniforms.u_lastClickTime) uniforms.u_lastClickTime.value = __spcPointer.last
662ClickTime; 663 renderer.render(scene, camera); 664 } 665 animate(); 666 667 // --- Frontend integration: CSS variable binding, scroll velocity, gyroscope, click ripple --- 668 function syncCssVars() { 669 if (!props.cssVars) return; 670 const el = canvas.parentElement || canvas; 671 const styles = getComputedStyle(el); 672 for (const uniformName in props.cssVars) { 673 const cssVar = props.cssVars[uniformName]; 674 const raw = styles.getPropertyValue(cssVar).trim(); 675 if (!raw || !uniforms[uniformName]) continue; 676 if (raw[0] === '#') { 677 uniforms[uniformName].value = __spcColor(raw, uniforms[uniformName].value); 678 } else { 679 const num = Number(raw); 680 if (Number.isFinite(num)) uniforms[uniformName].value = num; 681 } 682 } 683 } 684 syncCssVars(); 685 686 let lastScrollY = window.scrollY; 687 let lastScrollTime = performance.now(); 688 const handleScrollVelocity = () => { 689 const now = performance.now(); 690 const v = (window.scrollY - lastScrollY) / Math.max(now - lastScrollTime, 1.0); 691 lastScrollY = window.scrollY; 692 lastScrollTime = now; 693 uniforms.u_scrollVelocity.value = Math.max(-1.0, Math.min(1.0, v * 0.2)); 694 }; 695 window.addEventListener('scroll', handleScrollVelocity, { passive: true }); 696 697 const handleDeviceOrientation = (event) => { 698 const gamma = Number(event.gamma) || 0; 699 const beta = Number(event.beta) || 0; 700 const alpha = Number(event.alpha) || 0; 701 uniforms.u_gyro.value.set( 702 Math.max(-1.0, Math.min(1.0, gamma / 45.0)), 703 Math.max(-1.0, Math.min(1.0, beta / 45.0)), 704 Math.max(-1.0, Math.min(1.0, (alpha - 180.0) / 180.0)) 705 ); 706 }; 707 window.addEventListener('deviceorientation', handleDeviceOrientation); 708 709 const handleClick = (event) => { 710 const rect = canvas.getBoundingClientRect(); 711 uniforms.u_clickPosition.value.set( 712 (event.clientX - rect.left) / Math.max(rect.width, 1), 713 1.0 - (event.clientY - rect.top) / Math.max(rect.height, 1) 714 ); 715 uniforms.u_lastClickTime.value = uniforms.u_time.value; 716 }; 717 canvas.addEventListener('click', handleClick); 718 719 const visibilityObserver = new IntersectionObserver((entries) => { 720 const entry = entries[0]; 721 if (!entry) return; 722 if (entry.isIntersecting && animationFrameId === 0) { 723 lastTime = performance.now(); 724 animate(); 725 } else if (!entry.isIntersecting) { 726 cancelAnimationFrame(animationFrameId); 727 animationFrameId = 0; 728 } 729 }, { threshold: 0 }); 730 visibilityObserver.observe(canvas); 731 732 onUnmounted(() => { 733 cancelAnimationFrame(animationFrameId); 734 detachPointer(); 735 window.removeEventListener('scroll', handleScrollVelocity); 736 window.removeEventListener('deviceorientation', handleDeviceOrientation); 737 canvas.removeEventListener('click', handleClick); 738 if (visibilityObserver) visibilityObserver.disconnect(); 739 resizeObserver.disconnect(); 740 disposeShaderTextures(material); 741 if (renderer) renderer.dispose(); 742 if (geometry) geometry.dispose(); 743 if (material) material.dispose(); 744 }); 745}); 746 747/* 748 TIMELINE ANIMATION DATA (JSON) 749 ============================== 750 No timeline data 751 752 To use this timeline with GSAP: 753
754 import gsap from 'gsap'; 755 756 const timeline = gsap.timeline({ 757 repeat: 0, 758 timeScale: 1 759 }); 760 761 // Example: Animate uniform values 762 // No tracks defined 763*/ 764<\/script> 765 766<style scoped> 767.shader-canvas-mu7j1h { 768 width: 100%; 769 height: 100%; 770 display: block; 771} 772</style>`;export{e as default};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.