{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "shader-field",
  "type": "registry:component",
  "title": "ShaderField",
  "description": "Full-screen domain-warped GLSL noise refracted through a color spectrum. Pointer-reactive, resolution-independent, zero geometry.",
  "dependencies": [
    "three",
    "@react-three/fiber"
  ],
  "registryDependencies": [
    "https://prism.icglabs.co/r/canvas-perf.json"
  ],
  "tier": "free",
  "files": [
    {
      "path": "components/prism/ShaderField.tsx",
      "content": "\"use client\";\n\nimport { useMemo, useRef } from \"react\";\nimport { Canvas, useFrame, useThree } from \"@react-three/fiber\";\nimport * as THREE from \"three\";\nimport { useDeviceTier } from \"@/lib/useDeviceTier\";\nimport { AutoDpr, PauseWhenOffscreen } from \"@/components/showcase/CanvasPerf\";\n\n/**\n * ShaderField — a full-screen fragment shader: domain-warped fractal noise\n * refracted through a configurable color spectrum. Flows on its own, drifts\n * toward the pointer. Zero geometry beyond a clip-space quad, so it's cheap and\n * resolution-independent. Custom GLSL — the kind of \"signature surface\" that\n * defines award-winning sites.\n */\nexport type ShaderFieldProps = {\n  /** Exactly 5 hex stops sampled across the noise field. */\n  palette?: [string, string, string, string, string];\n  speed?: number;\n  /** Higher = more turbulent warping. */\n  warp?: number;\n  className?: string;\n};\n\nconst VERT = /* glsl */ `\nvarying vec2 vUv;\nvoid main() {\n  vUv = uv;\n  gl_Position = vec4(position.xy, 0.0, 1.0);\n}\n`;\n\nconst FRAG = /* glsl */ `\nprecision highp float;\nvarying vec2 vUv;\nuniform float uTime;\nuniform float uWarp;\nuniform vec2 uMouse;\nuniform float uAspect;\nuniform vec3 uPalette[5];\n\n// hash / value-noise / fbm\nfloat hash(vec2 p) {\n  p = fract(p * vec2(123.34, 345.45));\n  p += dot(p, p + 34.345);\n  return fract(p.x * p.y);\n}\nfloat noise(vec2 p) {\n  vec2 i = floor(p);\n  vec2 f = fract(p);\n  vec2 u = f * f * (3.0 - 2.0 * f);\n  float a = hash(i);\n  float b = hash(i + vec2(1.0, 0.0));\n  float c = hash(i + vec2(0.0, 1.0));\n  float d = hash(i + vec2(1.0, 1.0));\n  return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);\n}\nfloat fbm(vec2 p) {\n  float v = 0.0;\n  float a = 0.5;\n  for (int i = 0; i < 4; i++) {\n    v += a * noise(p);\n    p *= 2.0;\n    a *= 0.5;\n  }\n  return v;\n}\n\nvec3 prism(float t) {\n  vec3 c = mix(uPalette[0], uPalette[1], smoothstep(0.0, 0.25, t));\n  c = mix(c, uPalette[2], smoothstep(0.25, 0.5, t));\n  c = mix(c, uPalette[3], smoothstep(0.5, 0.75, t));\n  c = mix(c, uPalette[4], smoothstep(0.75, 1.0, t));\n  return c;\n}\n\nvoid main() {\n  vec2 uv = vUv;\n  uv.x *= uAspect;\n  float t = uTime * 0.08;\n\n  // pointer pulls the field\n  vec2 m = (uMouse - 0.5) * 0.6;\n  uv += m;\n\n  // single-pass domain warp (Inigo Quilez style) — cheap but rich\n  vec2 q = vec2(fbm(uv + t), fbm(uv + vec2(5.2, 1.3) - t));\n  float f = fbm(uv + uWarp * q + 0.12 * t);\n\n  float shade = clamp(f * f * 2.4 + 0.15, 0.0, 1.0);\n  vec3 col = prism(clamp(f + 0.25 * q.x, 0.0, 1.0));\n  col *= 0.35 + 0.9 * shade;\n\n  // bright refraction filaments where the warp folds\n  float fil = smoothstep(0.78, 0.92, f) * 0.6;\n  col += fil * mix(uPalette[2], uPalette[3], q.y);\n\n  // vignette toward obsidian\n  vec2 vc = vUv - 0.5;\n  float vig = smoothstep(0.95, 0.25, length(vc));\n  col *= mix(0.25, 1.0, vig);\n\n  // subtle dithered grain to kill banding\n  float g = hash(vUv * 850.0 + t) * 0.025 - 0.0125;\n  col += g;\n\n  gl_FragColor = vec4(col, 1.0);\n}\n`;\n\nfunction paletteToFloats(palette: string[]): Float32Array {\n  const arr = new Float32Array(15);\n  for (let i = 0; i < 5; i++) {\n    const c = new THREE.Color(palette[Math.min(i, palette.length - 1)]);\n    arr[i * 3] = c.r;\n    arr[i * 3 + 1] = c.g;\n    arr[i * 3 + 2] = c.b;\n  }\n  return arr;\n}\n\nfunction FieldMesh({\n  palette,\n  speed,\n  warp,\n}: {\n  palette: string[];\n  speed: number;\n  warp: number;\n}) {\n  const mat = useRef<THREE.ShaderMaterial>(null);\n  const mouse = useRef(new THREE.Vector2(0.5, 0.5));\n  const size = useThree((s) => s.size);\n  const reduced =\n    typeof window !== \"undefined\" &&\n    window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches;\n\n  const uniforms = useMemo(\n    () => ({\n      uTime: { value: 0 },\n      uWarp: { value: warp },\n      uMouse: { value: new THREE.Vector2(0.5, 0.5) },\n      uAspect: { value: 1 },\n      uPalette: { value: paletteToFloats(palette) },\n    }),\n    [palette, warp]\n  );\n\n  useFrame((state, delta) => {\n    const m = mat.current;\n    if (!m) return;\n    if (!reduced) m.uniforms.uTime.value += delta * speed;\n    m.uniforms.uAspect.value = size.width / size.height;\n    // ease toward pointer (-1..1 → 0..1)\n    const target = mouse.current;\n    target.set((state.pointer.x + 1) / 2, (state.pointer.y + 1) / 2);\n    m.uniforms.uMouse.value.lerp(target, 0.04);\n  });\n\n  return (\n    <mesh>\n      <planeGeometry args={[2, 2]} />\n      <shaderMaterial ref={mat} vertexShader={VERT} fragmentShader={FRAG} uniforms={uniforms} />\n    </mesh>\n  );\n}\n\nexport function ShaderField({\n  palette = [\"#0a0a14\", \"#5b7cff\", \"#1fd4e6\", \"#8b5cff\", \"#ff3d81\"],\n  speed = 1,\n  warp = 4.0,\n  className,\n}: ShaderFieldProps) {\n  const q = useDeviceTier();\n  return (\n    <div className={className} style={{ width: \"100%\", height: \"100%\" }}>\n      {/* dpr is a range; AdaptiveDpr scales it by measured FPS and the loop pauses\n          off-screen — a full-bleed fragment shader stays cheap even on weak GPUs. */}\n      <Canvas dpr={q.dpr} gl={{ antialias: false, powerPreference: \"high-performance\" }}>\n        <FieldMesh palette={palette} speed={speed} warp={warp} />\n        <AutoDpr />\n        <PauseWhenOffscreen />\n      </Canvas>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/prism/ShaderField.tsx"
    }
  ]
}