{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "shader-strata",
  "title": "Shader Strata",
  "description": "Fine horizontal sediment layers displaced by fbm noise, each drifting at its own depth.",
  "dependencies": ["remotion"],
  "files": [
    {
      "path": "registry/remocn/shader-strata/index.tsx",
      "content": "\"use client\";\n\nimport { useEffect, useRef, useState } from \"react\";\nimport {\n  continueRender,\n  delayRender,\n  useCurrentFrame,\n  useVideoConfig,\n} from \"remotion\";\n\n// A field of fine horizontal sediment layers — strata. Each layer's edge is\n// displaced by fbm noise and drifts at its own depth speed, so the stack reads\n// as a living foundation rather than a gradient. Written for the remocn\n// registry as a custom WebGL shader (not a paper-design wrapper): frame-driven,\n// deterministic, no wall-clock time anywhere.\n//\n// `accent` / `accentAmount` tint the DEEP strata (the bottom of the frame) —\n// the layers everything else stands on — which is what makes it a natural\n// carrier for \"base\" stories: the undertone can arrive mid-video by animating\n// `accentAmount` with the master timeline.\n\nexport interface ShaderStrataProps {\n  /** Palette from the deepest layer to the surface layer. Exactly 4 stops. */\n  colors?: [string, string, string, string];\n  /** Tint mixed into the deep strata. */\n  accent?: string;\n  /** 0..1 — how much of the accent the deep strata take. */\n  accentAmount?: number;\n  /** Number of strata bands across the frame height. */\n  layers?: number;\n  /** Edge waviness of each layer, in fractions of the frame height. */\n  amplitude?: number;\n  /** Global time multiplier. */\n  speed?: number;\n  className?: string;\n}\n\nconst VERT = `\nattribute vec2 a_pos;\nvoid main() {\n  gl_Position = vec4(a_pos, 0.0, 1.0);\n}\n`;\n\nconst FRAG = `\nprecision highp float;\n\nuniform vec2 u_res;\nuniform float u_time;\nuniform float u_layers;\nuniform float u_amp;\nuniform vec3 u_c0;\nuniform vec3 u_c1;\nuniform vec3 u_c2;\nuniform vec3 u_c3;\nuniform vec3 u_accent;\nuniform float u_accentAmt;\n\nfloat hash(vec2 p) {\n  return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123);\n}\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  return mix(\n    mix(hash(i), hash(i + vec2(1.0, 0.0)), u.x),\n    mix(hash(i + vec2(0.0, 1.0)), hash(i + vec2(1.0, 1.0)), u.x),\n    u.y\n  );\n}\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.03;\n    a *= 0.5;\n  }\n  return v;\n}\n\nvoid main() {\n  vec2 uv = gl_FragCoord.xy / u_res;\n  float aspect = u_res.x / u_res.y;\n\n  // Deeper layers drift faster — parallax without per-band recursion.\n  float drift = u_time * (0.012 + 0.05 * (1.0 - uv.y));\n  float w = fbm(vec2(uv.x * 2.6 * aspect + drift, uv.y * 2.2 - u_time * 0.01));\n\n  float yd = uv.y + (w - 0.5) * u_amp;\n  float band = yd * u_layers;\n  float idx = floor(band);\n  float f = fract(band);\n\n  // Per-band value jitter + a soft sediment shadow at each band's lower edge.\n  float j = hash(vec2(idx, 7.0));\n  float edge = smoothstep(0.0, 0.4, f) * (1.0 - 0.3 * smoothstep(0.8, 1.0, f));\n\n  float depth = clamp(idx / u_layers, 0.0, 1.0);\n  vec3 col = mix(u_c0, u_c1, smoothstep(0.0, 0.4, depth));\n  col = mix(col, u_c2, smoothstep(0.35, 0.75, depth));\n  col = mix(col, u_c3, smoothstep(0.7, 1.0, depth));\n  col *= 0.84 + 0.16 * j;\n  col *= 0.92 + 0.08 * edge;\n\n  // The accent rises from the deep strata and thins toward the surface.\n  float mask = (1.0 - uv.y);\n  mask *= mask;\n  col = mix(col, u_accent, clamp(u_accentAmt, 0.0, 1.0) * mask * 0.38);\n\n  // A whisper of living grain, time-derived so it stays deterministic.\n  col += (hash(gl_FragCoord.xy + floor(u_time * 9.0)) - 0.5) * 0.012;\n\n  gl_FragColor = vec4(col, 1.0);\n}\n`;\n\nconst hexToRgb = (hex: string): [number, number, number] => {\n  const h = hex.replace(\"#\", \"\");\n  const v =\n    h.length === 3\n      ? h\n          .split(\"\")\n          .map((c) => c + c)\n          .join(\"\")\n      : h;\n  const n = parseInt(v, 16);\n  return [((n >> 16) & 255) / 255, ((n >> 8) & 255) / 255, (n & 255) / 255];\n};\n\ntype GlState = {\n  gl: WebGLRenderingContext;\n  uniforms: Record<string, WebGLUniformLocation | null>;\n};\n\nexport function ShaderStrata({\n  colors = [\"#050507\", \"#0a0a0d\", \"#111014\", \"#17161b\"],\n  accent = \"#6733FF\",\n  accentAmount = 0,\n  layers = 14,\n  amplitude = 0.16,\n  speed = 1,\n  className,\n}: ShaderStrataProps) {\n  const frame = useCurrentFrame();\n  const { fps, width, height } = useVideoConfig();\n  const canvasRef = useRef<HTMLCanvasElement>(null);\n  const stateRef = useRef<GlState | null>(null);\n  const [handle] = useState(() => delayRender(\"shader-strata\"));\n  const continuedRef = useRef(false);\n\n  useEffect(() => {\n    const canvas = canvasRef.current;\n    if (!canvas || stateRef.current) return;\n    const gl = canvas.getContext(\"webgl\", {\n      antialias: false,\n      preserveDrawingBuffer: true,\n    });\n    if (!gl) return;\n\n    const compile = (type: number, src: string) => {\n      const shader = gl.createShader(type);\n      if (!shader) throw new Error(\"Failed to create WebGL shader\");\n      gl.shaderSource(shader, src);\n      gl.compileShader(shader);\n      return shader;\n    };\n    const program = gl.createProgram();\n    if (!program) return;\n    gl.attachShader(program, compile(gl.VERTEX_SHADER, VERT));\n    gl.attachShader(program, compile(gl.FRAGMENT_SHADER, FRAG));\n    gl.linkProgram(program);\n    // biome-ignore lint/correctness/useHookAtTopLevel: gl.useProgram is a WebGL method, not a React hook\n    gl.useProgram(program);\n\n    const buf = gl.createBuffer();\n    gl.bindBuffer(gl.ARRAY_BUFFER, buf);\n    gl.bufferData(\n      gl.ARRAY_BUFFER,\n      new Float32Array([-1, -1, 3, -1, -1, 3]),\n      gl.STATIC_DRAW,\n    );\n    const loc = gl.getAttribLocation(program, \"a_pos\");\n    gl.enableVertexAttribArray(loc);\n    gl.vertexAttribPointer(loc, 2, gl.FLOAT, false, 0, 0);\n\n    const uniforms: GlState[\"uniforms\"] = {};\n    for (const name of [\n      \"u_res\",\n      \"u_time\",\n      \"u_layers\",\n      \"u_amp\",\n      \"u_c0\",\n      \"u_c1\",\n      \"u_c2\",\n      \"u_c3\",\n      \"u_accent\",\n      \"u_accentAmt\",\n    ]) {\n      uniforms[name] = gl.getUniformLocation(program, name);\n    }\n    stateRef.current = { gl, uniforms };\n  }, []);\n\n  useEffect(() => {\n    const state = stateRef.current;\n    const canvas = canvasRef.current;\n    if (!state || !canvas) return;\n    const { gl, uniforms } = state;\n\n    gl.viewport(0, 0, canvas.width, canvas.height);\n    gl.uniform2f(uniforms.u_res, canvas.width, canvas.height);\n    gl.uniform1f(uniforms.u_time, (frame / fps) * speed);\n    gl.uniform1f(uniforms.u_layers, layers);\n    gl.uniform1f(uniforms.u_amp, amplitude);\n    gl.uniform3fv(uniforms.u_c0, hexToRgb(colors[0]));\n    gl.uniform3fv(uniforms.u_c1, hexToRgb(colors[1]));\n    gl.uniform3fv(uniforms.u_c2, hexToRgb(colors[2]));\n    gl.uniform3fv(uniforms.u_c3, hexToRgb(colors[3]));\n    gl.uniform3fv(uniforms.u_accent, hexToRgb(accent));\n    gl.uniform1f(uniforms.u_accentAmt, accentAmount);\n    gl.drawArrays(gl.TRIANGLES, 0, 3);\n\n    if (!continuedRef.current) {\n      continuedRef.current = true;\n      requestAnimationFrame(() =>\n        requestAnimationFrame(() => continueRender(handle)),\n      );\n    }\n  }, [\n    frame,\n    fps,\n    speed,\n    layers,\n    amplitude,\n    colors,\n    accent,\n    accentAmount,\n    handle,\n  ]);\n\n  return (\n    <div className={className} style={{ position: \"absolute\", inset: 0 }}>\n      <canvas\n        ref={canvasRef}\n        width={width}\n        height={height}\n        style={{ width: \"100%\", height: \"100%\", display: \"block\" }}\n      />\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/shader-strata.tsx"
    }
  ],
  "type": "registry:component"
}
