{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "MicroSlats-TS-CSS",
	"title": "MicroSlats",
	"description": "A wall of tiny slats that becomes a rolling sea in perspective, with glinting crests, four presets, a real fluid the cursor stirs and an intro that rolls in from the horizon.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:file",
			"path": "MicroSlats.css",
			"target": "@components/MicroSlats.css",
			"content": ".micro-slats {\n  position: relative;\n  width: 100%;\n  height: 100%;\n  overflow: hidden;\n}\n"
		},
		{
			"type": "registry:component",
			"path": "MicroSlats.tsx",
			"content": "'use client';\n\nimport { useEffect, useMemo, useRef } from 'react';\nimport type { CSSProperties } from 'react';\nimport { Renderer, Program, Mesh, Triangle, RenderTarget, Texture } from 'ogl';\n\nimport './MicroSlats.css';\n\ntype SwellPreset = 'swell' | 'tide' | 'storm' | 'signal';\ntype Rgba = [number, number, number, number];\n\ninterface WaveValues {\n  scale: number;\n  speed: number;\n  direction: number;\n  chop: number;\n  stretch: number;\n  glint: number;\n  contrast: number;\n  perspective: number;\n  fog: number;\n}\n\nexport interface MicroSlatsProps extends Partial<WaveValues> {\n  preset?: SwellPreset;\n  color?: string;\n  glintColor?: string;\n  backgroundColor?: string;\n  slatWidth?: number;\n  slatHeight?: number;\n  gap?: number;\n  roundness?: number;\n  interactive?: boolean;\n  cursorStrength?: number;\n  cursorSize?: number;\n  swirl?: number;\n  trail?: number;\n  lean?: number;\n  intro?: boolean;\n  introDuration?: number;\n  paused?: boolean;\n  className?: string;\n  style?: CSSProperties;\n}\n\ninterface Settings extends WaveValues {\n  color: Rgba;\n  glintColor: Rgba;\n  backgroundColor: Rgba;\n  slatWidth: number;\n  slatHeight: number;\n  gap: number;\n  roundness: number;\n  interactive: boolean;\n  cursorStrength: number;\n  cursorSize: number;\n  swirl: number;\n  trail: number;\n  lean: number;\n  intro: boolean;\n  introDuration: number;\n  paused: boolean;\n}\n\ninterface GridLayout {\n  slatW: number;\n  slatH: number;\n  tilt: number;\n  grid: number[];\n  origin: number[];\n}\n\ninterface Pair {\n  read: RenderTarget;\n  write: RenderTarget;\n  swap: () => void;\n}\n\ninterface Fluid {\n  width: number;\n  height: number;\n  velocity: Pair;\n  ink: Pair;\n  pressure: Pair;\n  divergence: RenderTarget;\n  curl: RenderTarget;\n}\n\ntype Splat = [number, number, number, number, number | null];\n\nconst SWELL_PRESETS: Record<SwellPreset, WaveValues> = {\n  swell: {\n    scale: 1.5,\n    speed: 0.6,\n    direction: 250,\n    chop: 0.55,\n    stretch: 0,\n    glint: 0.7,\n    contrast: 1.25,\n    perspective: 0.55,\n    fog: 0.55\n  },\n  tide: {\n    scale: 1.3,\n    speed: 0.5,\n    direction: 262,\n    chop: 0.2,\n    stretch: 0.12,\n    glint: 0.45,\n    contrast: 1.1,\n    perspective: 0.5,\n    fog: 0.4\n  },\n  storm: {\n    scale: 0.55,\n    speed: 1.6,\n    direction: 236,\n    chop: 1,\n    stretch: 0.3,\n    glint: 1.3,\n    contrast: 1.8,\n    perspective: 0.8,\n    fog: 0.35\n  },\n  signal: {\n    scale: 0.85,\n    speed: 1.2,\n    direction: 180,\n    chop: 0.6,\n    stretch: 0.85,\n    glint: 0.25,\n    contrast: 1.2,\n    perspective: 0,\n    fog: 0\n  }\n};\n\nconst FLUID_SIZE = 96;\nconst PRESSURE_STEPS = 16;\nconst SPLAT_FORCE = 6900;\nconst SPLASH_JETS = 3;\nconst PIXEL_BUDGET = 4.5e6;\n\nconst clamp = (value: number, min: number, max: number) => Math.min(Math.max(value, min), max);\n\nconst parseColor = (value: string, fallback: Rgba): Rgba => {\n  try {\n    const ctx = document.createElement('canvas').getContext('2d');\n    if (!ctx) return fallback;\n    ctx.fillStyle = '#000000';\n    ctx.fillStyle = value;\n    const resolved = ctx.fillStyle;\n    if (resolved.startsWith('#')) {\n      const n = parseInt(resolved.slice(1), 16);\n      return [((n >> 16) & 255) / 255, ((n >> 8) & 255) / 255, (n & 255) / 255, 1];\n    }\n    const parts = resolved.match(/[\\d.]+/g);\n    if (!parts || parts.length < 3) return fallback;\n    return [Number(parts[0]) / 255, Number(parts[1]) / 255, Number(parts[2]) / 255, parts[3] ? Number(parts[3]) : 1];\n  } catch {\n    return fallback;\n  }\n};\n\nconst passVertex = `#version 300 es\nin vec2 position;\nvoid main() {\n  gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst fluidVertex = `#version 300 es\nin vec2 position;\nuniform vec2 uTexel;\nout vec2 vUv;\nout vec2 vL;\nout vec2 vR;\nout vec2 vT;\nout vec2 vB;\nvoid main() {\n  vUv = position * 0.5 + 0.5;\n  vL = vUv - vec2(uTexel.x, 0.0);\n  vR = vUv + vec2(uTexel.x, 0.0);\n  vT = vUv + vec2(0.0, uTexel.y);\n  vB = vUv - vec2(0.0, uTexel.y);\n  gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst fluidHead = `#version 300 es\nprecision highp float;\nin vec2 vUv;\nin vec2 vL;\nin vec2 vR;\nin vec2 vT;\nin vec2 vB;\nout vec4 fragColor;\n`;\n\nconst splatFragment = `${fluidHead}\nuniform sampler2D uTarget;\nuniform float uAspect;\nuniform vec2 uPoint;\nuniform vec3 uValue;\nuniform float uRadius;\nvoid main() {\n  vec2 offset = vUv - uPoint;\n  offset.x *= uAspect;\n  float falloff = exp(-dot(offset, offset) / uRadius);\n  fragColor = vec4(texture(uTarget, vUv).xyz + uValue * falloff, 1.0);\n}\n`;\n\nconst curlFragment = `${fluidHead}\nuniform sampler2D uVelocity;\nvoid main() {\n  float left = texture(uVelocity, vL).y;\n  float right = texture(uVelocity, vR).y;\n  float top = texture(uVelocity, vT).x;\n  float bottom = texture(uVelocity, vB).x;\n  fragColor = vec4(0.5 * (right - left - top + bottom), 0.0, 0.0, 1.0);\n}\n`;\n\nconst vorticityFragment = `${fluidHead}\nuniform sampler2D uVelocity;\nuniform sampler2D uCurl;\nuniform float uSwirl;\nuniform float uDt;\nvoid main() {\n  float left = texture(uCurl, vL).x;\n  float right = texture(uCurl, vR).x;\n  float top = texture(uCurl, vT).x;\n  float bottom = texture(uCurl, vB).x;\n  float middle = texture(uCurl, vUv).x;\n  vec2 force = 0.5 * vec2(abs(top) - abs(bottom), abs(right) - abs(left));\n  force = force / (length(force) + 0.0001) * uSwirl * middle;\n  force.y = -force.y;\n  vec2 velocity = texture(uVelocity, vUv).xy + force * uDt;\n  fragColor = vec4(clamp(velocity, -1000.0, 1000.0), 0.0, 1.0);\n}\n`;\n\nconst divergenceFragment = `${fluidHead}\nuniform sampler2D uVelocity;\nvoid main() {\n  vec2 middle = texture(uVelocity, vUv).xy;\n  float left = vL.x < 0.0 ? -middle.x : texture(uVelocity, vL).x;\n  float right = vR.x > 1.0 ? -middle.x : texture(uVelocity, vR).x;\n  float top = vT.y > 1.0 ? -middle.y : texture(uVelocity, vT).y;\n  float bottom = vB.y < 0.0 ? -middle.y : texture(uVelocity, vB).y;\n  fragColor = vec4(0.5 * (right - left + top - bottom), 0.0, 0.0, 1.0);\n}\n`;\n\nconst pressureFragment = `${fluidHead}\nuniform sampler2D uPressure;\nuniform sampler2D uDivergence;\nvoid main() {\n  float left = texture(uPressure, vL).x;\n  float right = texture(uPressure, vR).x;\n  float top = texture(uPressure, vT).x;\n  float bottom = texture(uPressure, vB).x;\n  float divergence = texture(uDivergence, vUv).x;\n  fragColor = vec4((left + right + top + bottom - divergence) * 0.25, 0.0, 0.0, 1.0);\n}\n`;\n\nconst projectFragment = `${fluidHead}\nuniform sampler2D uPressure;\nuniform sampler2D uVelocity;\nvoid main() {\n  float left = texture(uPressure, vL).x;\n  float right = texture(uPressure, vR).x;\n  float top = texture(uPressure, vT).x;\n  float bottom = texture(uPressure, vB).x;\n  vec2 velocity = texture(uVelocity, vUv).xy - vec2(right - left, top - bottom);\n  fragColor = vec4(velocity, 0.0, 1.0);\n}\n`;\n\nconst advectFragment = `${fluidHead}\nuniform sampler2D uVelocity;\nuniform sampler2D uSource;\nuniform vec2 uTexel;\nuniform float uDt;\nuniform float uFade;\nvoid main() {\n  vec2 from = vUv - uDt * texture(uVelocity, vUv).xy * uTexel;\n  fragColor = texture(uSource, from) / (1.0 + uFade * uDt);\n}\n`;\n\nconst scaleFragment = `${fluidHead}\nuniform sampler2D uSource;\nuniform float uValue;\nvoid main() {\n  fragColor = texture(uSource, vUv) * uValue;\n}\n`;\n\nconst fieldFragment = `#version 300 es\nprecision highp float;\nuniform vec2 uSize;\nuniform vec4 uGrid;\nuniform vec2 uOrigin;\nuniform vec2 uSlat;\nuniform float uTime;\nuniform float uScale;\nuniform float uDirection;\nuniform float uChop;\nuniform float uStretch;\nuniform float uGlint;\nuniform float uContrast;\nuniform float uPerspective;\nuniform float uFog;\nuniform float uIntro;\nuniform sampler2D tVelocity;\nuniform sampler2D tInk;\nuniform vec2 uFluidTexel;\nuniform float uFluid;\nuniform float uInk;\nuniform float uLean;\nout vec4 fragColor;\n\nconst float TURN[4] = float[4](0.0, -0.95, 0.78, 1.62);\nconst float LENGTH[4] = float[4](2.6, 1.7, 1.12, 0.76);\nconst float HEIGHT[4] = float[4](1.0, 0.75, 0.5, 0.3);\nconst float SHIFT[4] = float[4](0.0, 2.3, 4.1, 1.1);\n\nvoid main() {\n  vec2 cell = floor(gl_FragCoord.xy);\n  float row = uGrid.y - 1.0 - cell.y;\n  vec2 center = uOrigin + vec2(cell.x, row) * uGrid.zw + uSlat * 0.5;\n  float v = clamp(center.y / uSize.y, 0.0, 1.0);\n\n  vec2 flow = vec2(0.0);\n  float ink = 0.0;\n  if (uFluid > 0.5) {\n    vec2 fluidUv = clamp(vec2(center.x / uSize.x, 1.0 - center.y / uSize.y), 0.0, 1.0);\n    flow = texture(tVelocity, fluidUv).xy * uFluidTexel * uSize;\n    ink = texture(tInk, fluidUv).x;\n  }\n\n  float horizon = mix(8.0, 0.5, uPerspective);\n  float depth = (1.0 + horizon) / (v + horizon);\n  vec2 world = vec2((center.x - uSize.x * 0.5) / uSize.y * depth, (depth - 1.0) * horizon * 2.2);\n  world -= flow * 0.00018 * depth;\n  world /= max(uScale, 0.05);\n\n  float meander = 0.55 * sin(dot(world, vec2(0.23, 0.41)) * 0.9 + uTime * 0.13)\n    + 0.3 * sin(dot(world, vec2(-0.37, 0.19)) * 1.4 - uTime * 0.09);\n\n  float steep = uChop * 0.45;\n  float height = 0.0;\n  float total = 0.0;\n  for (int i = 0; i < 4; i++) {\n    float angle = uDirection + TURN[i];\n    vec2 heading = vec2(cos(angle), sin(angle));\n    float k = 6.2831853 / LENGTH[i];\n    float omega = sqrt(9.81 * k) * 0.35;\n    float theta = k * dot(world, heading) - omega * uTime + SHIFT[i] + meander * (0.6 + 0.3 * float(i));\n    height += HEIGHT[i] * (cos(theta) + steep * cos(2.0 * theta));\n    total += HEIGHT[i] * (1.0 + steep);\n  }\n  float level = clamp(0.5 + 0.5 * height / (total * 0.85), 0.0, 1.0);\n  float crest = smoothstep(0.6, 1.0, level);\n  float glint = crest * crest * uGlint;\n\n  float haze = mix(1.0, 1.0 - uFog, pow(1.0 - v, 1.4));\n  float light = (pow(level, uContrast) + glint * 0.8) * haze;\n  float glow = 1.0 - exp(-max(ink, 0.0) * uInk * 1.6);\n  light = 1.0 - (1.0 - clamp(light, 0.0, 1.0)) * (1.0 - glow);\n\n  float front = uIntro * 1.35;\n  float reveal = 1.0 - smoothstep(front - 0.35, front, v);\n  float sweep = exp(-pow((v - front + 0.22) / 0.07, 2.0)) * (1.0 - uIntro);\n  light = (light + sweep * 0.8) * reveal;\n  light = max(light, 0.045 * reveal);\n  float span = mix(1.0 - uStretch, 1.0, level) + glow * 0.3;\n  span = clamp(span, 0.08, 1.0) * mix(0.1, 1.0, reveal);\n  float tint = clamp(max(glint * 1.4, glow * 0.9) + sweep, 0.0, 1.0);\n  float lean = clamp(flow.x / 1400.0, -1.0, 1.0) * uLean * reveal;\n\n  fragColor = vec4(clamp(light, 0.0, 1.0), span, tint, 0.5 + 0.5 * lean);\n}\n`;\n\nconst slatFragment = `#version 300 es\nprecision highp float;\nuniform sampler2D tField;\nuniform vec2 uSize;\nuniform float uDpr;\nuniform vec4 uGrid;\nuniform vec2 uOrigin;\nuniform vec2 uSlat;\nuniform float uRound;\nuniform float uTilt;\nuniform vec3 uColor;\nuniform vec3 uGlintColor;\nuniform vec4 uBackground;\nout vec4 fragColor;\n\nfloat pill(vec2 p, vec2 b, float r) {\n  vec2 q = abs(p) - b + r;\n  return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - r;\n}\n\nvoid main() {\n  vec2 p = vec2(gl_FragCoord.x, uSize.y * uDpr - gl_FragCoord.y) / uDpr;\n  vec2 local = p - uOrigin;\n  vec2 cell = floor(local / uGrid.zw);\n  vec4 background = vec4(uBackground.rgb * uBackground.a, uBackground.a);\n  vec4 slat = vec4(0.0);\n  int reach = uTilt > 0.0 ? 1 : 0;\n  for (int dx = -1; dx <= 1; dx++) {\n    if (abs(dx) > reach) continue;\n    vec2 neighbour = cell + vec2(float(dx), 0.0);\n    if (neighbour.x < 0.0 || neighbour.y < 0.0 || neighbour.x >= uGrid.x || neighbour.y >= uGrid.y) continue;\n    vec4 field = texelFetch(tField, ivec2(int(neighbour.x), int(uGrid.y - 1.0 - neighbour.y)), 0);\n    vec2 offset = local - neighbour * uGrid.zw - uSlat * 0.5;\n    float angle = (field.a - 0.5) * 2.0 * uTilt;\n    float c = cos(angle);\n    float s = sin(angle);\n    vec2 turned = vec2(c * offset.x + s * offset.y, c * offset.y - s * offset.x);\n    vec2 halfSize = vec2(uSlat.x * 0.5, uSlat.y * 0.5 * field.g);\n    float radius = uRound * min(halfSize.x, halfSize.y);\n    float alpha = clamp(0.5 - pill(turned, halfSize, radius) * uDpr, 0.0, 1.0) * field.r;\n    if (alpha > slat.a) slat = vec4(mix(uColor, uGlintColor, field.b) * alpha, alpha);\n  }\n  fragColor = slat + background * (1.0 - slat.a);\n}\n`;\n\nconst MicroSlats = ({\n  preset = 'swell',\n  color = '#A855F7',\n  glintColor = '#ffffff',\n  backgroundColor = '#000000',\n  slatWidth = 10,\n  slatHeight = 25,\n  gap = 3,\n  roundness = 0.75,\n  scale,\n  speed,\n  direction,\n  chop,\n  stretch,\n  glint,\n  contrast,\n  perspective,\n  fog,\n  interactive = true,\n  cursorStrength = 1,\n  cursorSize = 40,\n  swirl = 0,\n  trail = 1.4,\n  lean = 0,\n  intro = true,\n  introDuration = 1.5,\n  paused = false,\n  className = '',\n  style\n}: MicroSlatsProps) => {\n  const containerRef = useRef<HTMLDivElement>(null);\n  const settingsRef = useRef<Settings | null>(null);\n  const wakeRef = useRef<(() => void) | null>(null);\n\n  const base = SWELL_PRESETS[preset] || SWELL_PRESETS.swell;\n  const pick = <K extends keyof WaveValues>(value: WaveValues[K] | undefined, key: K): WaveValues[K] =>\n    value === undefined || value === null ? base[key] : value;\n\n  const colors = useMemo(\n    () => ({\n      color: parseColor(color, [0.66, 0.33, 0.97, 1]),\n      glintColor: parseColor(glintColor, [1, 1, 1, 1]),\n      backgroundColor: parseColor(backgroundColor, [0, 0, 0, 1])\n    }),\n    [color, glintColor, backgroundColor]\n  );\n\n  useEffect(() => {\n    settingsRef.current = {\n      ...colors,\n      slatWidth,\n      slatHeight,\n      gap,\n      roundness,\n      scale: pick(scale, 'scale'),\n      speed: pick(speed, 'speed'),\n      direction: pick(direction, 'direction'),\n      chop: pick(chop, 'chop'),\n      stretch: pick(stretch, 'stretch'),\n      glint: pick(glint, 'glint'),\n      contrast: pick(contrast, 'contrast'),\n      perspective: pick(perspective, 'perspective'),\n      fog: pick(fog, 'fog'),\n      interactive,\n      cursorStrength,\n      cursorSize,\n      swirl,\n      trail,\n      lean,\n      intro,\n      introDuration,\n      paused\n    };\n    wakeRef.current?.();\n  });\n\n  useEffect(() => {\n    const container = containerRef.current;\n    if (!container) return undefined;\n\n    const renderer = new Renderer({ alpha: true, premultipliedAlpha: true, antialias: false, depth: false });\n    const gl = renderer.gl;\n    if (!renderer.isWebgl2) {\n      gl.getExtension('WEBGL_lose_context')?.loseContext();\n      return undefined;\n    }\n    gl.clearColor(0, 0, 0, 0);\n    const canvas = gl.canvas;\n    canvas.style.display = 'block';\n    canvas.style.width = '100%';\n    canvas.style.height = '100%';\n    canvas.setAttribute('aria-hidden', 'true');\n    container.appendChild(canvas);\n\n    const geometry = new Triangle(gl);\n    const blank = new Texture(gl);\n    const floatTargets = !!(\n      gl.getExtension('EXT_color_buffer_float') || gl.getExtension('EXT_color_buffer_half_float')\n    );\n\n    const dispose = (target: RenderTarget) => {\n      gl.deleteFramebuffer(target.buffer);\n      gl.deleteTexture(target.texture.texture);\n    };\n\n    let fieldTarget = new RenderTarget(gl, {\n      width: 1,\n      height: 1,\n      depth: false,\n      minFilter: gl.NEAREST,\n      magFilter: gl.NEAREST\n    });\n\n    const fieldUniforms = {\n      uSize: { value: [1, 1] },\n      uGrid: { value: [1, 1, 1, 1] },\n      uOrigin: { value: [0, 0] },\n      uSlat: { value: [1, 1] },\n      uTime: { value: 0 },\n      uScale: { value: 1 },\n      uDirection: { value: 0 },\n      uChop: { value: 0.5 },\n      uStretch: { value: 0.3 },\n      uGlint: { value: 1 },\n      uContrast: { value: 1 },\n      uPerspective: { value: 0.7 },\n      uFog: { value: 0.5 },\n      uIntro: { value: 0 },\n      tVelocity: { value: blank },\n      tInk: { value: blank },\n      uFluidTexel: { value: [1, 1] },\n      uFluid: { value: 0 },\n      uInk: { value: 1 },\n      uLean: { value: 0.5 }\n    };\n    const fieldMesh = new Mesh(gl, {\n      geometry,\n      program: new Program(gl, {\n        vertex: passVertex,\n        fragment: fieldFragment,\n        uniforms: fieldUniforms,\n        depthTest: false,\n        depthWrite: false\n      })\n    });\n\n    const slatUniforms = {\n      tField: { value: fieldTarget.texture },\n      uSize: { value: [1, 1] },\n      uDpr: { value: 1 },\n      uGrid: { value: [1, 1, 1, 1] },\n      uOrigin: { value: [0, 0] },\n      uSlat: { value: [1, 1] },\n      uRound: { value: 1 },\n      uTilt: { value: 0 },\n      uColor: { value: [1, 1, 1] },\n      uGlintColor: { value: [1, 1, 1] },\n      uBackground: { value: [0, 0, 0, 1] }\n    };\n    const slatMesh = new Mesh(gl, {\n      geometry,\n      program: new Program(gl, {\n        vertex: passVertex,\n        fragment: slatFragment,\n        uniforms: slatUniforms,\n        depthTest: false,\n        depthWrite: false\n      })\n    });\n\n    const fluidTexel = { value: [1, 1] };\n    const fluidPass = (fragment: string, uniforms: Record<string, { value: unknown }>) =>\n      new Mesh(gl, {\n        geometry,\n        program: new Program(gl, {\n          vertex: fluidVertex,\n          fragment,\n          uniforms: { uTexel: fluidTexel, ...uniforms },\n          depthTest: false,\n          depthWrite: false\n        })\n      });\n    const splatPass = fluidPass(splatFragment, {\n      uTarget: { value: blank },\n      uAspect: { value: 1 },\n      uPoint: { value: [0, 0] },\n      uValue: { value: [0, 0, 0] },\n      uRadius: { value: 0.01 }\n    });\n    const curlPass = fluidPass(curlFragment, { uVelocity: { value: blank } });\n    const vorticityPass = fluidPass(vorticityFragment, {\n      uVelocity: { value: blank },\n      uCurl: { value: blank },\n      uSwirl: { value: 20 },\n      uDt: { value: 0.016 }\n    });\n    const divergencePass = fluidPass(divergenceFragment, { uVelocity: { value: blank } });\n    const pressurePass = fluidPass(pressureFragment, { uPressure: { value: blank }, uDivergence: { value: blank } });\n    const projectPass = fluidPass(projectFragment, { uPressure: { value: blank }, uVelocity: { value: blank } });\n    const advectPass = fluidPass(advectFragment, {\n      uVelocity: { value: blank },\n      uSource: { value: blank },\n      uDt: { value: 0.016 },\n      uFade: { value: 1 }\n    });\n    const scalePass = fluidPass(scaleFragment, { uSource: { value: blank }, uValue: { value: 0 } });\n\n    const reducedMotion = window.matchMedia?.('(prefers-reduced-motion: reduce)').matches ?? false;\n\n    let width = 1;\n    let height = 1;\n    let raf = 0;\n    let last = performance.now();\n    let time = 0;\n    let introClock = 0;\n    let visible = true;\n    let alive = true;\n    let fluid: Fluid | null = null;\n    let fluidUntil = 0;\n    let fluidDirty = false;\n    let splashTurn = 0;\n    const splats: Splat[] = [];\n    const pointer = { x: 0, y: 0, tracked: false };\n\n    const run = (mesh: Mesh, target?: RenderTarget) => renderer.render({ scene: mesh, target, clear: false });\n\n    const floatTarget = (w: number, h: number) =>\n      new RenderTarget(gl, {\n        width: w,\n        height: h,\n        depth: false,\n        type: (gl as unknown as WebGL2RenderingContext).HALF_FLOAT,\n        format: gl.RGBA,\n        internalFormat: (gl as unknown as WebGL2RenderingContext).RGBA16F,\n        minFilter: gl.LINEAR,\n        magFilter: gl.LINEAR\n      });\n\n    const swapPair = (w: number, h: number): Pair => ({\n      read: floatTarget(w, h),\n      write: floatTarget(w, h),\n      swap() {\n        const next = this.read;\n        this.read = this.write;\n        this.write = next;\n      }\n    });\n\n    const disposeFluid = () => {\n      if (!fluid) return;\n      [fluid.velocity, fluid.ink, fluid.pressure].forEach(pair => {\n        dispose(pair.read);\n        dispose(pair.write);\n      });\n      dispose(fluid.divergence);\n      dispose(fluid.curl);\n      fluid = null;\n    };\n\n    const buildFluid = () => {\n      if (!floatTargets) return;\n      const aspect = width / height;\n      const w = Math.max(8, Math.round(aspect >= 1 ? FLUID_SIZE * aspect : FLUID_SIZE));\n      const h = Math.max(8, Math.round(aspect >= 1 ? FLUID_SIZE : FLUID_SIZE / aspect));\n      if (fluid && fluid.width === w && fluid.height === h) return;\n      disposeFluid();\n      fluid = {\n        width: w,\n        height: h,\n        velocity: swapPair(w, h),\n        ink: swapPair(w, h),\n        pressure: swapPair(w, h),\n        divergence: floatTarget(w, h),\n        curl: floatTarget(w, h)\n      };\n      fluidTexel.value = [1 / w, 1 / h];\n    };\n\n    const resize = () => {\n      width = Math.max(1, container.clientWidth);\n      height = Math.max(1, container.clientHeight);\n      renderer.dpr = Math.min(window.devicePixelRatio || 1, 2, Math.sqrt(PIXEL_BUDGET / (width * height)));\n      renderer.setSize(width, height);\n      buildFluid();\n      start();\n    };\n\n    const layout = (s: Settings): GridLayout => {\n      const slatW = clamp(s.slatWidth, 1, 64);\n      const slatH = clamp(s.slatHeight, 2, 240);\n      const gapSize = clamp(s.gap, 0, 64);\n      const pitchX = slatW + gapSize;\n      const pitchY = slatH + gapSize;\n      const cols = Math.min(2048, Math.ceil((width + gapSize) / pitchX) + 1);\n      const rows = Math.min(2048, Math.ceil((height + gapSize) / pitchY) + 1);\n      const originX = (width - (cols * pitchX - gapSize)) / 2;\n      const originY = (height - (rows * pitchY - gapSize)) / 2;\n      if (fieldTarget.width !== cols || fieldTarget.height !== rows) {\n        dispose(fieldTarget);\n        fieldTarget = new RenderTarget(gl, {\n          width: cols,\n          height: rows,\n          depth: false,\n          minFilter: gl.NEAREST,\n          magFilter: gl.NEAREST\n        });\n        slatUniforms.tField.value = fieldTarget.texture;\n      }\n      const tilt = Math.min(0.42, Math.asin(clamp((pitchX * 1.5 - slatW * 0.5) / (slatH * 0.5), 0, 1)));\n      return { slatW, slatH, tilt, grid: [cols, rows, pitchX, pitchY], origin: [originX, originY] };\n    };\n\n    const splat = (pair: Pair, point: number[], value: number[], radius: number) => {\n      const u = splatPass.program.uniforms;\n      u.uTarget.value = pair.read.texture;\n      u.uAspect.value = width / height;\n      u.uPoint.value = point;\n      u.uValue.value = value;\n      u.uRadius.value = radius;\n      run(splatPass, pair.write);\n      pair.swap();\n    };\n\n    const stepFluid = (f: Fluid, dt: number, s: Settings) => {\n      if (s.swirl > 0) {\n        curlPass.program.uniforms.uVelocity.value = f.velocity.read.texture;\n        run(curlPass, f.curl);\n\n        const vorticity = vorticityPass.program.uniforms;\n        vorticity.uVelocity.value = f.velocity.read.texture;\n        vorticity.uCurl.value = f.curl.texture;\n        vorticity.uSwirl.value = clamp(s.swirl, 0, 2) * 40;\n        vorticity.uDt.value = dt;\n        run(vorticityPass, f.velocity.write);\n        f.velocity.swap();\n      }\n\n      divergencePass.program.uniforms.uVelocity.value = f.velocity.read.texture;\n      run(divergencePass, f.divergence);\n\n      scalePass.program.uniforms.uSource.value = f.pressure.read.texture;\n      scalePass.program.uniforms.uValue.value = 0.8;\n      run(scalePass, f.pressure.write);\n      f.pressure.swap();\n\n      pressurePass.program.uniforms.uDivergence.value = f.divergence.texture;\n      for (let i = 0; i < PRESSURE_STEPS; i++) {\n        pressurePass.program.uniforms.uPressure.value = f.pressure.read.texture;\n        run(pressurePass, f.pressure.write);\n        f.pressure.swap();\n      }\n\n      projectPass.program.uniforms.uPressure.value = f.pressure.read.texture;\n      projectPass.program.uniforms.uVelocity.value = f.velocity.read.texture;\n      run(projectPass, f.velocity.write);\n      f.velocity.swap();\n\n      const fade = 1 / clamp(s.trail, 0.1, 10);\n      const advect = advectPass.program.uniforms;\n      advect.uDt.value = dt;\n      advect.uVelocity.value = f.velocity.read.texture;\n      advect.uSource.value = f.velocity.read.texture;\n      advect.uFade.value = fade * 1.4;\n      run(advectPass, f.velocity.write);\n      f.velocity.swap();\n\n      advect.uVelocity.value = f.velocity.read.texture;\n      advect.uSource.value = f.ink.read.texture;\n      advect.uFade.value = fade;\n      run(advectPass, f.ink.write);\n      f.ink.swap();\n    };\n\n    const clearFluid = (f: Fluid) => {\n      scalePass.program.uniforms.uValue.value = 0;\n      [f.velocity, f.ink, f.pressure].forEach(pair => {\n        scalePass.program.uniforms.uSource.value = pair.read.texture;\n        run(scalePass, pair.write);\n        pair.swap();\n      });\n    };\n\n    const frame = (now: number) => {\n      raf = 0;\n      if (!alive) return;\n      const s = settingsRef.current;\n      const dt = Math.min(0.05, Math.max(0, (now - last) / 1000));\n      last = now;\n      if (!s) return;\n\n      if (!s.paused && !reducedMotion) time += dt * s.speed;\n      introClock = s.intro && !reducedMotion ? introClock + dt / Math.max(0.2, s.introDuration) : 1;\n      const introLinear = Math.min(introClock, 1);\n      const introEase =\n        introLinear < 0.5 ? 4 * introLinear * introLinear * introLinear : 1 - Math.pow(-2 * introLinear + 2, 3) / 2;\n\n      if (fluid && splats.length) {\n        const radius = Math.pow(clamp(s.cursorSize, 4, 600) / height, 2) * 0.35;\n        const aspect = width / height;\n        for (const [u, v, du, dv, turn] of splats) {\n          if (turn === null) {\n            splat(fluid.velocity, [u, v], [du * SPLAT_FORCE, dv * SPLAT_FORCE, 0], radius);\n            splat(fluid.ink, [u, v], [0.13, 0, 0], radius);\n            continue;\n          }\n          const jet = radius * 0.55;\n          const reach = Math.sqrt(jet * 0.5) * 1.9;\n          const push = SPLAT_FORCE * 0.08;\n          for (let i = 0; i < SPLASH_JETS; i++) {\n            const angle = turn + (i / SPLASH_JETS) * Math.PI * 2;\n            const dx = Math.cos(angle);\n            const dy = Math.sin(angle);\n            const point = [u + (dx * reach) / aspect, v + dy * reach];\n            const spin = [(dx - dy) * Math.SQRT1_2 * push, (dy + dx) * Math.SQRT1_2 * push, 0];\n            splat(fluid.velocity, point, spin, jet);\n            splat(fluid.ink, point, [0.75, 0, 0], jet);\n          }\n          splat(fluid.ink, [u, v], [0.45, 0, 0], radius);\n        }\n        splats.length = 0;\n        fluidDirty = true;\n      }\n      const fluidActive = fluid !== null && now < fluidUntil;\n      if (fluid) {\n        if (fluidActive) stepFluid(fluid, Math.min(Math.max(dt, 1 / 240), 1 / 30), s);\n        else if (fluidDirty) {\n          clearFluid(fluid);\n          fluidDirty = false;\n        }\n      }\n\n      const grid = layout(s);\n\n      fieldUniforms.uSize.value = [width, height];\n      fieldUniforms.uGrid.value = grid.grid;\n      fieldUniforms.uOrigin.value = grid.origin;\n      fieldUniforms.uSlat.value = [grid.slatW, grid.slatH];\n      fieldUniforms.uTime.value = time;\n      fieldUniforms.uScale.value = s.scale;\n      fieldUniforms.uDirection.value = (s.direction * Math.PI) / 180;\n      fieldUniforms.uChop.value = clamp(s.chop, 0, 1.5);\n      fieldUniforms.uStretch.value = clamp(s.stretch, 0, 0.95);\n      fieldUniforms.uGlint.value = Math.max(0, s.glint);\n      fieldUniforms.uContrast.value = clamp(s.contrast, 0.2, 4);\n      fieldUniforms.uPerspective.value = clamp(s.perspective, 0, 1);\n      fieldUniforms.uFog.value = clamp(s.fog, 0, 1);\n      fieldUniforms.uIntro.value = introEase;\n      fieldUniforms.uFluid.value = fluidActive ? 1 : 0;\n      fieldUniforms.tVelocity.value = fluid ? fluid.velocity.read.texture : blank;\n      fieldUniforms.tInk.value = fluid ? fluid.ink.read.texture : blank;\n      fieldUniforms.uFluidTexel.value = fluidTexel.value;\n      fieldUniforms.uInk.value = clamp(s.cursorStrength, 0, 3);\n      fieldUniforms.uLean.value = clamp(s.lean, 0, 1);\n      renderer.render({ scene: fieldMesh, target: fieldTarget });\n\n      slatUniforms.uSize.value = [width, height];\n      slatUniforms.uDpr.value = renderer.dpr;\n      slatUniforms.uGrid.value = grid.grid;\n      slatUniforms.uOrigin.value = grid.origin;\n      slatUniforms.uSlat.value = [grid.slatW, grid.slatH];\n      slatUniforms.uRound.value = clamp(s.roundness, 0, 1);\n      slatUniforms.uTilt.value = fluidActive && s.lean > 0 ? grid.tilt : 0;\n      slatUniforms.uColor.value = s.color.slice(0, 3);\n      slatUniforms.uGlintColor.value = s.glintColor.slice(0, 3);\n      slatUniforms.uBackground.value = s.backgroundColor;\n      renderer.render({ scene: slatMesh });\n\n      const moving = (!s.paused && !reducedMotion && s.speed !== 0) || introClock < 1;\n      if (visible && (moving || fluidActive || fluidDirty)) raf = requestAnimationFrame(frame);\n    };\n\n    const start = () => {\n      if (raf || !visible || !alive) return;\n      last = performance.now();\n      raf = requestAnimationFrame(frame);\n    };\n\n    const locate = (e: PointerEvent) => {\n      const rect = container.getBoundingClientRect();\n      const x = e.clientX - rect.left;\n      const y = e.clientY - rect.top;\n      const w = Math.max(1, rect.width);\n      const h = Math.max(1, rect.height);\n      return { x, y, u: x / w, v: 1 - y / h, h, inside: x >= 0 && y >= 0 && x <= w && y <= h };\n    };\n\n    const queue = (u: number, v: number, du: number, dv: number, turn: number | null) => {\n      if (splats.length > 32) splats.shift();\n      splats.push([u, v, du, dv, turn]);\n      const s = settingsRef.current;\n      fluidUntil = performance.now() + Math.max(1.5, (s ? s.trail : 1.4) * 5) * 1000;\n      start();\n    };\n\n    const onPointerMove = (e: PointerEvent) => {\n      const s = settingsRef.current;\n      const spot = locate(e);\n      if (!fluid || !s || !s.interactive || reducedMotion || !spot.inside) {\n        pointer.tracked = false;\n        return;\n      }\n      if (pointer.tracked && (spot.x !== pointer.x || spot.y !== pointer.y)) {\n        queue(spot.u, spot.v, (spot.x - pointer.x) / spot.h, (pointer.y - spot.y) / spot.h, null);\n      }\n      pointer.x = spot.x;\n      pointer.y = spot.y;\n      pointer.tracked = true;\n    };\n\n    const onPointerDown = (e: PointerEvent) => {\n      const s = settingsRef.current;\n      const spot = locate(e);\n      if (!fluid || !s || !s.interactive || reducedMotion || !spot.inside) return;\n      splashTurn += 2.39996;\n      queue(spot.u, spot.v, 0, 0, splashTurn);\n    };\n\n    const onPointerLeave = () => {\n      pointer.tracked = false;\n    };\n\n    const onPointerOut = (e: PointerEvent) => {\n      if (!e.relatedTarget) onPointerLeave();\n    };\n\n    const onVisibility = () => {\n      if (!document.hidden) start();\n    };\n\n    window.addEventListener('pointermove', onPointerMove, { passive: true });\n    window.addEventListener('pointerdown', onPointerDown, { passive: true });\n    window.addEventListener('pointerout', onPointerOut, { passive: true });\n    window.addEventListener('blur', onPointerLeave);\n    document.addEventListener('visibilitychange', onVisibility);\n\n    const resizeObserver = new ResizeObserver(resize);\n    resizeObserver.observe(container);\n    const intersectionObserver = new IntersectionObserver(([entry]) => {\n      visible = entry.isIntersecting;\n      start();\n    });\n    intersectionObserver.observe(container);\n\n    wakeRef.current = start;\n    resize();\n\n    return () => {\n      alive = false;\n      visible = false;\n      cancelAnimationFrame(raf);\n      resizeObserver.disconnect();\n      intersectionObserver.disconnect();\n      window.removeEventListener('pointermove', onPointerMove);\n      window.removeEventListener('pointerdown', onPointerDown);\n      window.removeEventListener('pointerout', onPointerOut);\n      window.removeEventListener('blur', onPointerLeave);\n      document.removeEventListener('visibilitychange', onVisibility);\n      wakeRef.current = null;\n      disposeFluid();\n      dispose(fieldTarget);\n      gl.getExtension('WEBGL_lose_context')?.loseContext();\n      if (canvas.parentNode) canvas.parentNode.removeChild(canvas);\n    };\n  }, []);\n\n  return <div ref={containerRef} className={`micro-slats ${className}`.trim()} style={style} />;\n};\n\nexport default MicroSlats;\n"
		}
	],
	"registryDependencies": [],
	"dependencies": [
		"ogl@^1.0.11"
	]
}