{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "ShapeWaves-JS-CSS",
	"title": "ShapeWaves",
	"description": "A field of triangles, circles and squares that brighten and grow along rolling waves.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:file",
			"path": "ShapeWaves.css",
			"target": "@components/ShapeWaves.css",
			"content": ".shape-waves {\n  position: relative;\n  width: 100%;\n  height: 100%;\n  overflow: hidden;\n  pointer-events: none;\n  isolation: isolate;\n}\n\n.shape-waves__canvas {\n  position: absolute;\n  inset: 0;\n  display: block;\n  width: 100%;\n  height: 100%;\n  opacity: 0;\n  transition: opacity 600ms cubic-bezier(0.22, 1, 0.36, 1);\n  pointer-events: none;\n}\n\n.shape-waves[data-ready='true'] .shape-waves__canvas {\n  opacity: 1;\n}\n\n@media (prefers-reduced-motion: reduce) {\n  .shape-waves__canvas {\n    transition: none;\n  }\n}\n"
		},
		{
			"type": "registry:component",
			"path": "ShapeWaves.jsx",
			"content": "'use client';\n\n// Inspired by https://vercel.com/labs\nimport { useEffect, useRef, useState } from 'react';\nimport { effect, frame, init, sampler, storage, surface, target, uniforms } from 'vgpu';\n\nimport './ShapeWaves.css';\n\nconst SHAPE_MODES = { mixed: 0, squares: 1, circles: 2, triangles: 3 };\nconst MAX_DPR = 2;\nconst MAX_MASK_SIZE = 1024;\nconst NOISE_CELLS = 32;\nconst TIME_RATE = 0.1;\nconst SIMULATION_STEP = 1 / 60;\nconst WAVE_SPEED = 0.42;\nconst WAVE_FRICTION = 0.94;\nconst WAVE_DECAY = 0.972;\nconst SETTLED_THRESHOLD = 0.01;\nconst INTRO_BAND = 0.2;\nconst INTRO_WARP = 0.3;\nconst INTRO_JITTER = 0.16;\nconst INTRO_END = 1 + INTRO_WARP + INTRO_JITTER + INTRO_BAND;\n\nconst NOISE_WGSL = `\nfn mod289v3(x: vec3f) -> vec3f { return x - floor(x * (1.0 / 289.0)) * 289.0; }\nfn mod289v4(x: vec4f) -> vec4f { return x - floor(x * (1.0 / 289.0)) * 289.0; }\nfn permute(x: vec4f) -> vec4f { return mod289v4(((x * 34.0) + 10.0) * x); }\nfn taylorInvSqrt(r: vec4f) -> vec4f { return 1.79284291400159 - 0.85373472095314 * r; }\nfn fadeCurve(t: vec3f) -> vec3f { return t * t * t * (t * (t * 6.0 - 15.0) + 10.0); }\n\nfn cnoise(P: vec3f) -> f32 {\n  var Pi0 = floor(P);\n  var Pi1 = Pi0 + vec3f(1.0);\n  Pi0 = mod289v3(Pi0);\n  Pi1 = mod289v3(Pi1);\n  let Pf0 = fract(P);\n  let Pf1 = Pf0 - vec3f(1.0);\n  let ix = vec4f(Pi0.x, Pi1.x, Pi0.x, Pi1.x);\n  let iy = vec4f(Pi0.yy, Pi1.yy);\n  let iz0 = Pi0.zzzz;\n  let iz1 = Pi1.zzzz;\n\n  let ixy = permute(permute(ix) + iy);\n  let ixy0 = permute(ixy + iz0);\n  let ixy1 = permute(ixy + iz1);\n\n  var gx0 = ixy0 * (1.0 / 7.0);\n  var gy0 = fract(floor(gx0) * (1.0 / 7.0)) - 0.5;\n  gx0 = fract(gx0);\n  let gz0 = vec4f(0.5) - abs(gx0) - abs(gy0);\n  let sz0 = step(gz0, vec4f(0.0));\n  gx0 -= sz0 * (step(vec4f(0.0), gx0) - 0.5);\n  gy0 -= sz0 * (step(vec4f(0.0), gy0) - 0.5);\n\n  var gx1 = ixy1 * (1.0 / 7.0);\n  var gy1 = fract(floor(gx1) * (1.0 / 7.0)) - 0.5;\n  gx1 = fract(gx1);\n  let gz1 = vec4f(0.5) - abs(gx1) - abs(gy1);\n  let sz1 = step(gz1, vec4f(0.0));\n  gx1 -= sz1 * (step(vec4f(0.0), gx1) - 0.5);\n  gy1 -= sz1 * (step(vec4f(0.0), gy1) - 0.5);\n\n  var g000 = vec3f(gx0.x, gy0.x, gz0.x);\n  var g100 = vec3f(gx0.y, gy0.y, gz0.y);\n  var g010 = vec3f(gx0.z, gy0.z, gz0.z);\n  var g110 = vec3f(gx0.w, gy0.w, gz0.w);\n  var g001 = vec3f(gx1.x, gy1.x, gz1.x);\n  var g101 = vec3f(gx1.y, gy1.y, gz1.y);\n  var g011 = vec3f(gx1.z, gy1.z, gz1.z);\n  var g111 = vec3f(gx1.w, gy1.w, gz1.w);\n\n  let norm0 = taylorInvSqrt(vec4f(dot(g000, g000), dot(g010, g010), dot(g100, g100), dot(g110, g110)));\n  g000 *= norm0.x;\n  g010 *= norm0.y;\n  g100 *= norm0.z;\n  g110 *= norm0.w;\n  let norm1 = taylorInvSqrt(vec4f(dot(g001, g001), dot(g011, g011), dot(g101, g101), dot(g111, g111)));\n  g001 *= norm1.x;\n  g011 *= norm1.y;\n  g101 *= norm1.z;\n  g111 *= norm1.w;\n\n  let n000 = dot(g000, Pf0);\n  let n100 = dot(g100, vec3f(Pf1.x, Pf0.yz));\n  let n010 = dot(g010, vec3f(Pf0.x, Pf1.y, Pf0.z));\n  let n110 = dot(g110, vec3f(Pf1.xy, Pf0.z));\n  let n001 = dot(g001, vec3f(Pf0.xy, Pf1.z));\n  let n101 = dot(g101, vec3f(Pf1.x, Pf0.y, Pf1.z));\n  let n011 = dot(g011, vec3f(Pf0.x, Pf1.yz));\n  let n111 = dot(g111, Pf1);\n\n  let f = fadeCurve(Pf0);\n  let nz = mix(vec4f(n000, n100, n010, n110), vec4f(n001, n101, n011, n111), f.z);\n  let ny = mix(nz.xy, nz.zw, f.y);\n  return 2.2 * mix(ny.x, ny.y, f.x);\n}\n\nfn fbm(p: vec3f) -> f32 {\n  var total = 0.0;\n  var amplitude = 1.0;\n  var weight = 0.0;\n  var frequency = 1.0;\n  for (var i = 0; i < 2; i++) {\n    total += amplitude * cnoise(p * frequency);\n    weight += amplitude;\n    amplitude *= 0.5;\n    frequency *= 2.0;\n  }\n  return total / weight;\n}\n`;\n\nconst SCENE_SHADER = `\nstruct Params {\n  resolution: vec4f,\n  placement: vec4f,\n  grid: vec4f,\n  field: vec4f,\n  motion: vec4f,\n  color: vec4f,\n  hover: vec4f,\n  background: vec4f,\n}\n@group(0) @binding(0) var<uniform> params: Params;\n@group(0) @binding(1) var maskTexture: texture_2d<f32>;\n@group(0) @binding(2) var maskSampler: sampler;\n@group(0) @binding(3) var<storage, read> charges: array<f32>;\n\nconst SEED = vec2f(12.9898, 78.233);\nconst GLOW_THRESHOLD = 0.6;\n${NOISE_WGSL}\n\nfn sdIsoscelesTriangle(point: vec2f, q: vec2f) -> f32 {\n  let p = vec2f(abs(point.x), point.y);\n  let a = p - q * clamp(dot(p, q) / dot(q, q), 0.0, 1.0);\n  let b = p - q * vec2f(clamp(p.x / q.x, 0.0, 1.0), 1.0);\n  let s = -sign(q.y);\n  let d = min(vec2f(dot(a, a), s * (p.x * q.y - p.y * q.x)), vec2f(dot(b, b), s * (p.y - q.y)));\n  return -sqrt(d.x) * sign(d.y);\n}\n\nfn shapeDistance(p: vec2f, shape: i32, c: f32) -> f32 {\n  if (shape == 0) { return max(abs(p.x), abs(p.y)) - c; }\n  if (shape == 1) { return length(p) - c; }\n  return sdIsoscelesTriangle(vec2f(p.x, p.y + c), vec2f(c, 2.0 * c));\n}\n\nfn hash21(p: vec2f) -> f32 {\n  return fract(sin(dot(p, vec2f(127.1, 311.7))) * 43758.5453);\n}\n\n@fragment fn fs_main(@location(0) uv: vec2f) -> @location(0) vec4f {\n  let resolution = params.resolution.xy;\n  let cellPx = params.grid.x;\n  let dotSize = params.grid.y;\n  let mode = i32(params.grid.z + 0.5);\n  let cols = i32(params.grid.w + 0.5);\n  let background = params.background.rgb;\n  let toSurface = params.hover.w < 0.5;\n\n  let pixel = uv * resolution;\n  let origin = params.placement.xy;\n  let rows = i32(params.placement.z + 0.5);\n  let cell = floor((pixel - origin) / cellPx);\n  if (cell.y < 0.0 || i32(cell.y) >= rows || cell.x < 0.0 || i32(cell.x) >= cols) {\n    return vec4f(background, select(0.0, 1.0, toSurface));\n  }\n  let center = origin + (cell + 0.5) * cellPx;\n  let local = (pixel - center) / (cellPx * 0.5);\n  let cellUv = center / resolution;\n\n  if (params.motion.z > 0.5 && textureSampleLevel(maskTexture, maskSampler, cellUv, 0.0).r > 0.5) {\n    return vec4f(background, select(0.0, 1.0, toSurface));\n  }\n\n  var level = 1.0;\n  let fade = params.motion.w;\n  if (fade > 0.0) {\n    let q = abs(uv * 2.0 - 1.0);\n    let radius = pow(pow(q.x, 2.5) + pow(q.y, 2.5), 1.0 / 2.5) / pow(2.0, 1.0 / 2.5);\n    level = 1.0 - smoothstep(max(0.0, 1.0 - fade * 2.2), 1.0, radius);\n  }\n  let noise = fbm(vec3f((center + params.motion.xy) / params.field.x + SEED, params.field.w));\n  let tone = clamp((noise * 0.5 + 0.5 - params.field.y) * params.field.z + 0.5, 0.0, 1.0);\n  let band = i32(min(tone, 0.999999) * 3.0);\n\n  var charge = 0.0;\n  let index = i32(cell.y) * cols + i32(cell.x);\n  if (index >= 0 && index < i32(arrayLength(&charges))) { charge = charges[index]; }\n  let stepped = (band + i32(clamp(charge, 0.0, 0.999) * 3.0)) % 3;\n\n  var shape = 2 - stepped;\n  var size = dotSize;\n  if (mode != 0) {\n    shape = mode - 1;\n    size = dotSize * mix(0.45, 1.0, f32(stepped) / 2.0);\n  }\n  let introProgress = params.placement.w;\n  var front = 0.0;\n  if (introProgress < ${INTRO_END.toFixed(2)}) {\n    let radial = length((center - resolution * 0.5) / (resolution * 0.5)) * 0.70710678;\n    let warp = cnoise(vec3f(cellUv * vec2f(3.2, 2.4) + SEED, 4.7)) * ${INTRO_WARP.toFixed(2)};\n    let jitter = hash21(cell) * ${INTRO_JITTER.toFixed(2)};\n    let spread = radial + warp + jitter + ${INTRO_WARP.toFixed(2)};\n    let band = ${INTRO_BAND.toFixed(2)} * (0.6 + 0.8 * hash21(cell + vec2f(17.0, 9.0)));\n    let t = clamp((introProgress - spread) / band, 0.0, 1.0);\n    if (t <= 0.0) {\n      return vec4f(background, select(0.0, 1.0, toSurface));\n    }\n    let back = t - 1.0;\n    size = max(size * (1.0 + 2.70158 * back * back * back + 1.70158 * back * back), 0.02);\n    front = 1.0 - smoothstep(0.0, 1.0, abs(introProgress - spread) / band);\n  }\n  let aa = 2.0 / cellPx;\n  let coverage = smoothstep(aa, -aa, shapeDistance(local, shape, size));\n\n  let tint = mix(params.color.rgb, params.hover.rgb, max(smoothstep(0.15, 0.85, charge), front * 0.35));\n\n  let rgb = mix(background, tint, coverage * level);\n  if (toSurface) { return vec4f(rgb, 1.0); }\n  let luminance = dot(tint * level, vec3f(0.2126, 0.7152, 0.0722));\n  let glow = max(0.0, (luminance - GLOW_THRESHOLD) / (1.0 - GLOW_THRESHOLD)) * coverage;\n  return vec4f(rgb, glow);\n}\n`;\n\nconst BLUR_SHADER = `\nstruct Blur { direction: vec4f }\n@group(0) @binding(0) var<uniform> blur: Blur;\n@group(0) @binding(1) var sourceTexture: texture_2d<f32>;\n@group(0) @binding(2) var sourceSampler: sampler;\n\n@fragment fn fs_main(@location(0) uv: vec2f) -> @location(0) vec4f {\n  let sigma = blur.direction.z;\n  let radius = i32(ceil(3.0 * sigma));\n  var sum = vec3f(0.0);\n  var weight = 0.0;\n  for (var i = -radius; i <= radius; i++) {\n    let offset = f32(i);\n    let w = exp(-(offset * offset) / (2.0 * sigma * sigma));\n    let sample = textureSampleLevel(sourceTexture, sourceSampler, uv + offset * blur.direction.xy, 0.0);\n    sum += select(sample.rgb, sample.rgb * sample.a, blur.direction.w > 0.5) * w;\n    weight += w;\n  }\n  return vec4f(sum / weight, 1.0);\n}\n`;\n\nconst COMPOSITE_SHADER = `\nstruct Composite { strength: vec4f }\n@group(0) @binding(0) var<uniform> composite: Composite;\n@group(0) @binding(1) var sceneTexture: texture_2d<f32>;\n@group(0) @binding(2) var glowTexture: texture_2d<f32>;\n@group(0) @binding(3) var linearSampler: sampler;\n\n@fragment fn fs_main(@location(0) uv: vec2f) -> @location(0) vec4f {\n  let scene = textureSampleLevel(sceneTexture, linearSampler, uv, 0.0).rgb;\n  let glow = textureSampleLevel(glowTexture, linearSampler, uv, 0.0).rgb;\n  return vec4f(scene + glow * composite.strength.x, 1.0);\n}\n`;\n\nconst parseColor = (value, fallback) => {\n  const source = typeof value === 'string' ? value.trim() : '';\n  const match = /^#?([\\da-f]{3}|[\\da-f]{6})$/i.exec(source) || /^#?([\\da-f]{6})$/i.exec(fallback);\n  let hex = match[1];\n  if (hex.length === 3) hex = hex.replace(/./g, char => char + char);\n  return [0, 2, 4].map(offset => parseInt(hex.slice(offset, offset + 2), 16) / 255);\n};\n\nexport default function ShapeWaves({\n  text = '',\n  fontFamily = 'Geist, \"Geist Sans\", system-ui, sans-serif',\n  fontWeight = 500,\n  textSize = 0.6,\n  shapes = 'mixed',\n  cellSize = 10,\n  dotSize = 0.75,\n  color = '#929292',\n  hoverColor = '#ffffff',\n  backgroundColor = '#000000',\n  speed = 1,\n  scale = 1,\n  contrast = 1,\n  brightness = 0.4,\n  flow = 0,\n  direction = 0,\n  fade = 0.25,\n  interactive = true,\n  splashRadius = 40,\n  splashStrength = 0.4,\n  glow = 0.35,\n  intro = true,\n  introDuration = 1.6,\n  introKey = 0,\n  paused = false,\n  onError,\n  className = ''\n}) {\n  const rootRef = useRef(null);\n  const canvasRef = useRef(null);\n  const [ready, setReady] = useState(false);\n  const settingsRef = useRef(null);\n  const applySettingsRef = useRef(() => {});\n  const applyMaskRef = useRef(() => {});\n  const onErrorRef = useRef(onError);\n\n  settingsRef.current = {\n    text: String(text ?? ''),\n    fontFamily,\n    fontWeight,\n    textSize,\n    shapes,\n    cellSize: Math.max(2, cellSize),\n    dotSize,\n    color,\n    hoverColor,\n    backgroundColor,\n    speed,\n    scale: Math.max(0.05, scale),\n    contrast,\n    brightness,\n    flow,\n    direction,\n    fade,\n    interactive,\n    splashRadius,\n    splashStrength,\n    glow,\n    intro,\n    introDuration: Math.max(0.1, introDuration),\n    introKey,\n    paused\n  };\n  onErrorRef.current = onError;\n\n  const settingsSignature = [\n    shapes,\n    cellSize,\n    dotSize,\n    color,\n    hoverColor,\n    backgroundColor,\n    speed,\n    scale,\n    contrast,\n    brightness,\n    flow,\n    direction,\n    fade,\n    interactive,\n    splashRadius,\n    splashStrength,\n    glow,\n    intro,\n    introDuration,\n    paused\n  ].join('|');\n  const maskSignature = [text, fontFamily, fontWeight, textSize].join('|');\n  const replayRef = useRef(() => {});\n\n  useEffect(() => {\n    applySettingsRef.current();\n  }, [settingsSignature]);\n\n  useEffect(() => {\n    applyMaskRef.current();\n  }, [maskSignature]);\n\n  useEffect(() => {\n    if (introKey) replayRef.current();\n  }, [introKey]);\n\n  useEffect(() => {\n    const root = rootRef.current;\n    const canvas = canvasRef.current;\n    if (!root || !canvas) return undefined;\n\n    let disposed = false;\n    let failed = false;\n    let gpu;\n    let maskTexture;\n    let chargeBuffer;\n    let frameId = 0;\n    let lastFrameTime = 0;\n    let time = 0;\n    let drift = [0, 0];\n    let dpr = 1;\n    let visible = true;\n    let presented = false;\n    let cols = 1;\n    let rows = 1;\n    let cellPx = 10;\n    let gridOrigin = [0, 0];\n    let charges = new Float32Array(1);\n    let heights = new Float32Array(1);\n    let previousHeights = new Float32Array(1);\n    let simulationBacklog = 0;\n    let introStart = 0;\n    let introProgress = INTRO_END;\n    let introArmed = false;\n    let chargesActive = false;\n    let bounds = null;\n    let unsubscribeGpuError;\n    let resizeObserver;\n    let visibilityObserver;\n    let wakeRenderer = () => {};\n    const pointer = { x: 0, y: 0, at: 0, inside: false };\n    const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)');\n    const maskCanvas = document.createElement('canvas');\n    const maskContext = maskCanvas.getContext('2d');\n\n    const handleWake = () => wakeRenderer();\n    const invalidateBounds = () => {\n      bounds = null;\n    };\n    const splash = (x, y, strength) => {\n      const settings = settingsRef.current;\n      const sigma = Math.max(0.5, ((settings.splashRadius * dpr) / cellPx) * 0.5);\n      const reach = Math.ceil(sigma * 2.5);\n      const centerCol = (x * dpr - gridOrigin[0]) / cellPx - 0.5;\n      const centerRow = (y * dpr - gridOrigin[1]) / cellPx - 0.5;\n      const minRow = Math.max(0, Math.floor(centerRow - reach));\n      const maxRow = Math.min(rows - 1, Math.ceil(centerRow + reach));\n      const minCol = Math.max(0, Math.floor(centerCol - reach));\n      const maxCol = Math.min(cols - 1, Math.ceil(centerCol + reach));\n      for (let row = minRow; row <= maxRow; row++) {\n        const dy = row - centerRow;\n        for (let col = minCol; col <= maxCol; col++) {\n          const dx = col - centerCol;\n          const bump = strength * Math.exp(-(dx * dx + dy * dy) / (2 * sigma * sigma));\n          const index = row * cols + col;\n          heights[index] = Math.min(1.2, heights[index] + bump);\n        }\n      }\n      chargesActive = true;\n    };\n\n    const handlePointerMove = event => {\n      if (!settingsRef.current.interactive) return;\n      if (!bounds) bounds = root.getBoundingClientRect();\n      const now = performance.now();\n      const x = event.clientX - bounds.left;\n      const y = event.clientY - bounds.top;\n      const inside = x >= 0 && y >= 0 && x <= bounds.width && y <= bounds.height;\n      if (inside) {\n        const elapsed = pointer.inside ? Math.max(8, now - pointer.at) : 16;\n        const travelled = pointer.inside ? Math.hypot(x - pointer.x, y - pointer.y) : 0;\n        const speed = (travelled / elapsed) * 1000;\n        splash(x, y, Math.min(1, 0.22 + speed * 0.0006) * settingsRef.current.splashStrength);\n        wakeRenderer();\n      }\n      pointer.x = x;\n      pointer.y = y;\n      pointer.at = now;\n      pointer.inside = inside;\n    };\n\n    const reportFailure = error => {\n      if (disposed || failed) return;\n      failed = true;\n      if (frameId) cancelAnimationFrame(frameId);\n      frameId = 0;\n      resizeObserver?.disconnect();\n      visibilityObserver?.disconnect();\n      unsubscribeGpuError?.();\n      applySettingsRef.current = () => {};\n      applyMaskRef.current = () => {};\n      const failedGpu = gpu;\n      gpu = undefined;\n      failedGpu?.dispose();\n      setReady(false);\n      onErrorRef.current?.(error instanceof Error ? error : new Error(String(error)));\n    };\n\n    const measureSurface = () => {\n      dpr = Math.min(window.devicePixelRatio || 1, MAX_DPR);\n      return [Math.max(1, Math.round(canvas.clientWidth * dpr)), Math.max(1, Math.round(canvas.clientHeight * dpr))];\n    };\n\n    void (async () => {\n      try {\n        gpu = await init({ powerPreference: 'low-power' });\n        if (disposed) return gpu.dispose();\n        unsubscribeGpuError = gpu.onError(reportFailure);\n\n        const initialSize = measureSurface();\n        const outputFormat = navigator.gpu.getPreferredCanvasFormat();\n        const output = surface(gpu, canvas, { dpr, size: initialSize, autoResize: false, format: outputFormat });\n\n        const params = uniforms(gpu, {\n          resolution: [initialSize[0], initialSize[1], 1 / initialSize[0], 1 / initialSize[1]],\n          placement: [0, 0, 1, 0],\n          grid: [10, 0.75, 0, 1],\n          field: [320, 0.5, 2.8, 0],\n          motion: [0, 0, 0, 0.25],\n          color: [0.573, 0.573, 0.573, 1],\n          hover: [1, 1, 1, 0],\n          background: [0, 0, 0, 1]\n        });\n        const linearSampler = sampler(gpu, {\n          minFilter: 'linear',\n          magFilter: 'linear',\n          addressModeU: 'clamp-to-edge',\n          addressModeV: 'clamp-to-edge'\n        });\n        const createMaskTexture = (maskWidth, maskHeight) =>\n          gpu.device.createTexture({\n            size: [maskWidth, maskHeight],\n            format: 'rgba8unorm',\n            usage: ['texture_binding', 'copy_dst', 'render_attachment'],\n            label: 'shape-waves-mask'\n          });\n        maskTexture = createMaskTexture(1, 1);\n        chargeBuffer = storage(gpu, 4, 'read');\n        chargeBuffer.write(charges);\n\n        const scene = effect(gpu, SCENE_SHADER, {\n          label: 'shape-waves-scene',\n          set: { params, maskTexture, maskSampler: linearSampler, charges: chargeBuffer }\n        });\n        const sceneTarget = target(gpu, { size: initialSize, format: 'rgba8unorm', label: 'shape-waves-scene' });\n        const glowSize = size => [Math.max(1, Math.ceil(size[0] / 2)), Math.max(1, Math.ceil(size[1] / 2))];\n        const glowA = target(gpu, { size: glowSize(initialSize), format: 'rgba8unorm', label: 'shape-waves-glow-a' });\n        const glowB = target(gpu, { size: glowSize(initialSize), format: 'rgba8unorm', label: 'shape-waves-glow-b' });\n        const blurParamsX = uniforms(gpu, { direction: [0, 0, 4, 1] });\n        const blurParamsY = uniforms(gpu, { direction: [0, 0, 4, 0] });\n        const blurX = effect(gpu, BLUR_SHADER, {\n          label: 'shape-waves-glow-x',\n          set: { blur: blurParamsX, sourceTexture: sceneTarget, sourceSampler: linearSampler }\n        });\n        const blurY = effect(gpu, BLUR_SHADER, {\n          label: 'shape-waves-glow-y',\n          set: { blur: blurParamsY, sourceTexture: glowA, sourceSampler: linearSampler }\n        });\n        const compositeParams = uniforms(gpu, { strength: [2, 0, 0, 0] });\n        const composite = effect(gpu, COMPOSITE_SHADER, {\n          label: 'shape-waves-composite',\n          set: { composite: compositeParams, sceneTexture: sceneTarget, glowTexture: glowB, linearSampler }\n        });\n        await Promise.all([\n          scene.compile({ colors: [outputFormat] }),\n          scene.compile(sceneTarget),\n          blurX.compile(glowA),\n          blurY.compile(glowB),\n          composite.compile({ colors: [outputFormat] })\n        ]);\n        if (disposed) return;\n\n        const glowEnabled = () => settingsRef.current.glow > 0;\n        let lastIntro = false;\n\n        const configureGrid = () => {\n          const settings = settingsRef.current;\n          const [width, height] = output.size;\n          const nextCols = Math.max(1, Math.round(width / (settings.cellSize * dpr)));\n          cellPx = width / nextCols;\n          const nextRows = Math.max(1, Math.floor(height / cellPx));\n          gridOrigin = [0, (height - nextRows * cellPx) / 2];\n          if (nextCols === cols && nextRows === rows && charges.length === cols * rows) return;\n          cols = nextCols;\n          rows = nextRows;\n          charges = new Float32Array(cols * rows);\n          heights = new Float32Array(cols * rows);\n          previousHeights = new Float32Array(cols * rows);\n          chargesActive = false;\n          const nextBuffer = storage(gpu, charges.byteLength, 'read');\n          nextBuffer.write(charges);\n          scene.set({ charges: nextBuffer });\n          chargeBuffer.destroy();\n          chargeBuffer = nextBuffer;\n        };\n\n        const stepRipples = () => {\n          const lastCol = cols - 1;\n          const lastRow = rows - 1;\n          let peak = 0;\n          for (let row = 0; row < rows; row++) {\n            const up = (row === 0 ? row : row - 1) * cols;\n            const down = (row === lastRow ? row : row + 1) * cols;\n            const base = row * cols;\n            for (let col = 0; col < cols; col++) {\n              const index = base + col;\n              const left = base + (col === 0 ? col : col - 1);\n              const right = base + (col === lastCol ? col : col + 1);\n              const height = heights[index];\n              const laplacian = heights[left] + heights[right] + heights[up + col] + heights[down + col] - 4 * height;\n              const velocity = (height - previousHeights[index]) * WAVE_FRICTION;\n              const next = (height + velocity + WAVE_SPEED * laplacian) * WAVE_DECAY;\n              previousHeights[index] = next;\n              const charge = Math.min(1, Math.max(0, next));\n              charges[index] = charge;\n              if (charge > peak) peak = charge;\n            }\n          }\n          const swap = heights;\n          heights = previousHeights;\n          previousHeights = swap;\n          return peak;\n        };\n\n        const updateCharges = deltaSeconds => {\n          if (!chargesActive) return false;\n          simulationBacklog = Math.min(simulationBacklog + deltaSeconds, SIMULATION_STEP * 4);\n          let peak = 1;\n          while (simulationBacklog >= SIMULATION_STEP) {\n            simulationBacklog -= SIMULATION_STEP;\n            peak = stepRipples();\n          }\n          if (peak < SETTLED_THRESHOLD) {\n            heights.fill(0);\n            previousHeights.fill(0);\n            charges.fill(0);\n            chargesActive = false;\n          }\n          chargeBuffer.write(charges);\n          return chargesActive;\n        };\n\n        const isAnimating = () => {\n          const settings = settingsRef.current;\n          return visible && !document.hidden && !settings.paused && settings.speed > 0 && !reduceMotion.matches;\n        };\n\n        const render = now => {\n          frameId = 0;\n          if (disposed || failed) return;\n          const settings = settingsRef.current;\n          const deltaSeconds = lastFrameTime ? Math.min(0.1, (now - lastFrameTime) / 1000) : 0;\n          lastFrameTime = now;\n          const animating = isAnimating();\n          if (animating) {\n            time += deltaSeconds * TIME_RATE * settings.speed;\n            const angle = (settings.direction * Math.PI) / 180;\n            const distance = settings.flow * cellPx * deltaSeconds;\n            drift = [drift[0] + Math.cos(angle) * distance, drift[1] + Math.sin(angle) * distance];\n          }\n          const hovering = visible && !document.hidden && updateCharges(deltaSeconds);\n          if (introArmed) {\n            introArmed = false;\n            introStart = now;\n            introProgress = 0;\n          }\n          const introPlaying = introProgress < INTRO_END;\n          if (introPlaying) {\n            introProgress = Math.min(INTRO_END, ((now - introStart) / 1000 / settings.introDuration) * INTRO_END);\n          }\n          params.set({ placement: [gridOrigin[0], gridOrigin[1], rows, introProgress] });\n          params.set({\n            field: [\n              NOISE_CELLS * cellPx * settings.scale,\n              0.5 - (settings.brightness - 0.5) * 0.4,\n              2.8 * settings.contrast,\n              time\n            ]\n          });\n          params.set({ motion: [drift[0], drift[1], settings.text.trim() ? 1 : 0, settings.fade] });\n          try {\n            frame(gpu, currentFrame => {\n              if (!glowEnabled()) {\n                currentFrame.pass(output, scene);\n                return;\n              }\n              currentFrame.pass(sceneTarget, scene);\n              currentFrame.pass(glowA, blurX);\n              currentFrame.pass(glowB, blurY);\n              currentFrame.pass(output, composite);\n            });\n          } catch (error) {\n            reportFailure(error);\n            return;\n          }\n          if (!presented) {\n            presented = true;\n            setReady(true);\n          }\n          if (animating || hovering || introPlaying) frameId = requestAnimationFrame(render);\n          else lastFrameTime = 0;\n        };\n\n        wakeRenderer = () => {\n          if (disposed || failed || frameId) return;\n          frameId = requestAnimationFrame(render);\n        };\n\n        const drawMask = () => {\n          const settings = settingsRef.current;\n          const content = settings.text.trim();\n          const [surfaceWidth, surfaceHeight] = output.size;\n          const hasText = content.length > 0;\n          const maskScale = hasText ? Math.min(1, MAX_MASK_SIZE / Math.max(surfaceWidth, surfaceHeight)) : 0;\n          const maskWidth = hasText ? Math.max(1, Math.round(surfaceWidth * maskScale)) : 1;\n          const maskHeight = hasText ? Math.max(1, Math.round(surfaceHeight * maskScale)) : 1;\n\n          maskCanvas.width = maskWidth;\n          maskCanvas.height = maskHeight;\n          maskContext.fillStyle = '#000';\n          maskContext.fillRect(0, 0, maskWidth, maskHeight);\n\n          if (hasText) {\n            let fontPx = Math.max(1, settings.textSize * maskHeight);\n            maskContext.font = `${settings.fontWeight} ${fontPx}px ${settings.fontFamily}`;\n            const measured = maskContext.measureText(content).width;\n            const maxWidth = maskWidth * 0.9;\n            if (measured > maxWidth) {\n              fontPx = Math.max(1, (fontPx * maxWidth) / measured);\n              maskContext.font = `${settings.fontWeight} ${fontPx}px ${settings.fontFamily}`;\n            }\n            maskContext.textAlign = 'center';\n            maskContext.textBaseline = 'alphabetic';\n            const metrics = maskContext.measureText(content);\n            const x = (maskWidth + metrics.actualBoundingBoxLeft - metrics.actualBoundingBoxRight) / 2;\n            const y = (maskHeight + metrics.actualBoundingBoxAscent - metrics.actualBoundingBoxDescent) / 2;\n            maskContext.fillStyle = '#fff';\n            maskContext.fillText(content, x, y);\n          }\n\n          const nextTexture = createMaskTexture(maskWidth, maskHeight);\n          gpu.gpu.queue.copyExternalImageToTexture({ source: maskCanvas }, { texture: nextTexture.gpu }, [\n            maskWidth,\n            maskHeight\n          ]);\n          scene.set({ maskTexture: nextTexture });\n          maskTexture.destroy();\n          maskTexture = nextTexture;\n        };\n\n        const applySettings = () => {\n          if (disposed || failed) return;\n          const settings = settingsRef.current;\n          configureGrid();\n          params.set({\n            placement: [gridOrigin[0], gridOrigin[1], rows, introProgress],\n            grid: [cellPx, Math.min(1, Math.max(0.1, settings.dotSize)), SHAPE_MODES[settings.shapes] ?? 0, cols],\n            color: [...parseColor(settings.color, '#929292'), 1],\n            hover: [...parseColor(settings.hoverColor, '#ffffff'), glowEnabled() ? 1 : 0],\n            background: [...parseColor(settings.backgroundColor, '#000000'), 1]\n          });\n          compositeParams.set({ strength: [2 * settings.glow, 0, 0, 0] });\n          if (settings.intro !== lastIntro) {\n            lastIntro = settings.intro;\n            if (settings.intro && !reduceMotion.matches) introArmed = true;\n          }\n          if (!settings.interactive && chargesActive) {\n            heights.fill(0);\n            previousHeights.fill(0);\n            charges.fill(0);\n            chargesActive = false;\n            chargeBuffer.write(charges);\n          }\n          wakeRenderer();\n        };\n\n        const applyMask = () => {\n          if (disposed || failed) return;\n          drawMask();\n          applySettings();\n          const settings = settingsRef.current;\n          if (!settings.text.trim() || !document.fonts?.load) return;\n          document.fonts\n            .load(`${settings.fontWeight} 32px ${settings.fontFamily}`)\n            .then(() => {\n              if (disposed || failed) return;\n              drawMask();\n              wakeRenderer();\n            })\n            .catch(() => {});\n        };\n\n        const resize = () => {\n          if (disposed || failed) return;\n          invalidateBounds();\n          const nextSize = measureSurface();\n          if (nextSize[0] !== output.size[0] || nextSize[1] !== output.size[1]) output.resize(nextSize);\n          const [width, height] = output.size;\n          sceneTarget.resize([width, height]);\n          const half = glowSize([width, height]);\n          glowA.resize(half);\n          glowB.resize(half);\n          blurParamsX.set({ direction: [1 / half[0], 0, 4, 1] });\n          blurParamsY.set({ direction: [0, 1 / half[1], 4, 0] });\n          params.set({ resolution: [width, height, 1 / width, 1 / height] });\n          drawMask();\n          applySettings();\n        };\n\n        applySettingsRef.current = applySettings;\n        applyMaskRef.current = applyMask;\n        replayRef.current = () => {\n          if (disposed || failed || !settingsRef.current.intro || reduceMotion.matches) return;\n          introArmed = true;\n          wakeRenderer();\n        };\n\n        resizeObserver = new ResizeObserver(resize);\n        resizeObserver.observe(root);\n        visibilityObserver = new IntersectionObserver(\n          entries => {\n            visible = entries.some(entry => entry.isIntersecting);\n            if (visible) wakeRenderer();\n          },\n          { threshold: 0 }\n        );\n        visibilityObserver.observe(root);\n        document.addEventListener('visibilitychange', handleWake);\n        reduceMotion.addEventListener('change', handleWake);\n        window.addEventListener('pointermove', handlePointerMove, { passive: true });\n        window.addEventListener('scroll', invalidateBounds, { capture: true, passive: true });\n\n        resize();\n        applyMask();\n      } catch (error) {\n        reportFailure(error);\n      }\n    })();\n\n    return () => {\n      disposed = true;\n      wakeRenderer = () => {};\n      applySettingsRef.current = () => {};\n      applyMaskRef.current = () => {};\n      replayRef.current = () => {};\n      document.removeEventListener('visibilitychange', handleWake);\n      reduceMotion.removeEventListener('change', handleWake);\n      window.removeEventListener('pointermove', handlePointerMove);\n      window.removeEventListener('scroll', invalidateBounds, { capture: true });\n      resizeObserver?.disconnect();\n      visibilityObserver?.disconnect();\n      unsubscribeGpuError?.();\n      if (frameId) cancelAnimationFrame(frameId);\n      maskTexture?.destroy();\n      chargeBuffer?.destroy();\n      gpu?.dispose();\n    };\n  }, []);\n\n  return (\n    <div\n      ref={rootRef}\n      className={`shape-waves ${className}`}\n      data-ready={ready}\n      style={{ backgroundColor }}\n      aria-hidden=\"true\"\n    >\n      <canvas ref={canvasRef} className=\"shape-waves__canvas\" />\n    </div>\n  );\n}\n"
		}
	],
	"registryDependencies": [],
	"dependencies": [
		"vgpu@^0.3.1"
	]
}