{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "shader-text-reveal",
  "title": "Shader Text Reveal",
  "description": "Reveal words through the original OpenShaders hero shader, then settle into white type.",
  "dependencies": ["remotion"],
  "files": [
    {
      "path": "registry/remocn/shader-text-reveal/index.tsx",
      "content": "\"use client\";\n\nimport { useLayoutEffect, useRef } from \"react\";\nimport { useCurrentFrame, useDelayRender, useVideoConfig } from \"remotion\";\n\nexport type ShaderTextRevealProps = {\n  /** Words appear one at a time. Separate phrases with newlines to keep them together. */\n  text?: string;\n  fontSize?: number;\n  fontFamily?: string;\n  fontWeight?: number;\n  /** Frames per word, including its entrance and exit. The final word stays visible. */\n  wordDuration?: number;\n  /** Strength of the original hero shader inside the letters, from 0 to 1. */\n  intensity?: number;\n};\n\nconst clamp = (n: number, min = 0, max = 1) => Math.min(max, Math.max(min, n));\n\nexport function getShaderTextState(\n  frame: number,\n  wordCount: number,\n  wordDuration: number,\n) {\n  const duration = Math.max(12, Math.round(wordDuration));\n  const count = Math.max(1, wordCount);\n  const f = Math.max(0, frame);\n  const index = Math.min(count - 1, Math.floor(f / duration));\n  const local = f - index * duration;\n  const enter = clamp((local / duration + 0.16) / 0.76);\n  const exit =\n    index === count - 1 ? 0 : clamp((local / duration - 0.65) / 0.35);\n  return {\n    index,\n    enter,\n    exit: exit * exit * (3 - 2 * exit),\n    scale: 1 + 0.12 * (1 - enter) ** 3,\n  };\n}\n\nconst VERTEX = `#version 300 es\nvoid main() {\n  vec2 p = vec2(float((gl_VertexID << 1) & 2), float(gl_VertexID & 2));\n  gl_Position = vec4(p * 2.0 - 1.0, 0.0, 1.0);\n}`;\n\n/* @remocn · OpenShaders — https://openshaders.com/@remocn\n * Original hero field and halftone GLSL. Only its clock is driven by Remotion.\n */\nconst HERO_FIELD = `#version 300 es\nprecision highp float;\n\nuniform vec2 iResolution;\nuniform float iTime;\nuniform float uLightMode;\nuniform vec3 uDarkBackground;\nuniform vec3 uLightBackground;\nout vec4 fragColor;\n\nconst float HUE = 0.663466752;\nconst float HUE_SPREAD = 0.314346701;\nconst float HUE_TRAVEL = 1.65112376;\nconst float CHROMA = 0.160295546;\nconst float LIGHTNESS = 0.537409723;\nconst float COLOUR_CYCLE = 0.124862425;\nconst float THETA = 2.12986207;\nconst float SHEAR = 0.968176901;\nconst float SHRINK = 0.955401778;\nconst float LAYERS = 72.0;\nconst float WARP_FREQ_X = 0.439311415;\nconst float WARP_FREQ_Y = 2.87923527;\nconst float WARP_AMP_X = 0.156202048;\nconst float WARP_AMP_Y = 0.0256033838;\nconst float ASPECT_X = 2.23183894;\nconst float ASPECT_Y = 0.15731883;\nconst float OFFSET_X = 0.375628948;\nconst float OFFSET_Y = -0.017801486;\nconst float TILT = 1.63293767;\nconst float ZOOM = 0.93135649;\nconst float CENTRE_X = -0.0956316143;\nconst float CENTRE_Y = -0.520057321;\nconst float GLOW_SIZE = 0.00201825937;\nconst float FALLOFF = 0.352821857;\nconst float VIGNETTE = 0.136085436;\nconst float FLOW_SPEED = 0.391012281;\nconst float FLOW_DIRECTION = 1.0;\nconst float BREATH_RATE = 0.382053256;\nconst float BREATH_AMOUNT = 0.0873704553;\nconst float PHASE = 56.3862724;\nconst float ECHO = 0.0;\nconst float ECHO_SHIFT = 0.178754866;\nconst float SOFTNESS = 0.00164903817;\nconst float LIGHT_SWING = 0.149726808;\n\nconst float TAU = 6.28318530718;\n\nvec3 oklchToLinear(float L, float C, float h) {\n  float a = C * cos(h), b = C * sin(h);\n  float l_ = L + 0.3963377774 * a + 0.2158037573 * b;\n  float m_ = L - 0.1055613458 * a - 0.0638541728 * b;\n  float s_ = L - 0.0894841775 * a - 1.2914855480 * b;\n  vec3 lms = vec3(l_, m_, s_);\n  lms = lms * lms * lms;\n  return mat3(4.0767416621, -1.2684380046, -0.0041960863,\n              -3.3077115913, 2.6097574011, -0.7034186147,\n              0.2309699292, -0.3413193965, 1.7076147010) * lms;\n}\n\nfloat blueNoise(vec2 p, float frame) {\n  p += 5.588238 * mod(frame, 64.0);\n  return fract(52.9829189 * fract(0.06711056 * p.x + 0.00583715 * p.y));\n}\n\nvoid main() {\n  vec2 R = iResolution.xy;\n  vec2 pos = (gl_FragCoord.xy - 0.5 * R) / R.y;\n  float t = iTime * FLOW_SPEED * FLOW_DIRECTION + PHASE;\n  float breath = (-sin(iTime * BREATH_RATE * 1.5) + sin(iTime * BREATH_RATE + 1.0)) * 0.25 + 0.5;\n\n  vec2 u = (pos - vec2(CENTRE_X, CENTRE_Y)) * (ZOOM - breath * BREATH_AMOUNT);\n  float ct = cos(TILT), st = sin(TILT);\n  u = mat2(ct, st, -st, ct) * u;\n\n  mat2 fold = mat2(cos(THETA), sin(THETA), -SHEAR, cos(THETA));\n\n  float hue0 = HUE * TAU;\n  float hue1 = hue0 + HUE_SPREAD * TAU;\n  vec3 color = vec3(0.0);\n\n  for (float i = 1.0; i <= 96.0; i += 1.0) {\n    if (i > LAYERS) break;\n    u.x += -sin(u.y * WARP_FREQ_X + t + i * 0.007) * WARP_AMP_X;\n    u.y += -sin(u.x * WARP_FREQ_Y - t + i * 0.02) * WARP_AMP_Y;\n    u = fold * u * SHRINK;\n\n    vec2 q = u - vec2(OFFSET_X + breath * 0.1, OFFSET_Y);\n    vec2 s = vec2(q.x * ASPECT_X, q.y * ASPECT_Y);\n    float glow = GLOW_SIZE / (dot(s, s) + SOFTNESS);\n#ifndef SKIP_ECHO\n    vec2 e = vec2((q.x - ECHO_SHIFT) * ASPECT_X, s.y);\n    glow += ECHO * GLOW_SIZE / (dot(e, e) + SOFTNESS);\n#endif\n    glow *= 0.25 + breath * 0.4;\n\n    float r = length(u);\n    float k = sin(i * COLOUR_CYCLE + t * 1.2 + r * HUE_TRAVEL) * 0.5 + 0.5;\n    vec3 tint = clamp(oklchToLinear(LIGHTNESS + LIGHT_SWING * k, CHROMA * (0.75 + 0.35 * k), mix(hue0, hue1, k)), 0.0, 1.0);\n    color += glow * tint * exp2(-r * FALLOFF);\n  }\n\n  vec3 x = max(color, 0.0);\n  color = (x * (2.51 * x + 0.03)) / (x * (2.43 * x + 0.59) + 0.14);\n  color = pow(clamp(color, 0.0, 1.0), vec3(0.85, 0.92, 0.98));\n\n  float edge = smoothstep(0.5, 1.6, length(pos));\n  color *= 1.0 - edge * VIGNETTE;\n\n  vec3 dark = uDarkBackground + color * (1.0 - uDarkBackground);\n  float strength = max(color.r, max(color.g, color.b));\n  vec3 light = uLightBackground * (1.0 - strength) + color * 0.96;\n  color = mix(dark, light, uLightMode);\n\n  color += (blueNoise(gl_FragCoord.xy, floor(iTime * 24.0)) - 0.5) / 255.0;\n  fragColor = vec4(clamp(color, 0.0, 1.0), 1.0);\n}\n`;\n\nconst HERO_HALFTONE = `#version 300 es\nprecision highp float;\n\nuniform sampler2D tScene;\nuniform vec2 iResolution;\nuniform float iTime;\nuniform float uLightMode;\nuniform vec3 uDarkBackground;\nuniform vec3 uLightBackground;\nuniform float uPixelRatio;\nout vec4 fragColor;\n\nconst float uStrength = 0.797335207;\nconst float uScale = 0.985214949;\nconst float uSeed = 0.435611069;\n\nconst float TAU = 6.28318530718;\nconst vec3 LUMA = vec3(0.2126, 0.7152, 0.0722);\n\nvec3 toInk(vec3 c) { return mix(c - uDarkBackground, uLightBackground - c, uLightMode); }\nvec3 fromInk(vec3 ink) { return mix(uDarkBackground + ink, uLightBackground - ink, uLightMode); }\nvec3 sceneInk(vec2 uv) { return toInk(texture(tScene, clamp(uv, 0.0, 1.0)).rgb); }\n\nfloat blueNoise(vec2 p, float frame) {\n  p += 5.588238 * mod(frame, 64.0);\n  return fract(52.9829189 * fract(0.06711056 * p.x + 0.00583715 * p.y));\n}\n\nvec3 halftone(vec2 frag) {\n  float cell = max(3.0, uScale * 3.6 * uPixelRatio);\n  float angle = 0.26 + uSeed * 0.3;\n  mat2 turn = mat2(cos(angle), -sin(angle), sin(angle), cos(angle));\n  vec2 rotated = turn * frag;\n  vec2 grid = floor(rotated / cell);\n  vec2 centre = (grid + 0.5) * cell;\n  vec2 source = transpose(turn) * centre;\n  vec3 soft = sceneInk(frag / iResolution);\n  vec3 ink = sceneInk(source / iResolution);\n  float level = clamp(dot(ink, LUMA), 0.0, 1.0);\n  float radius = cell * sqrt(pow(level, 0.9) / 3.14159265);\n  float dist = length(rotated - centre);\n  float aa = 0.7 * uPixelRatio;\n  float dot_ = 1.0 - smoothstep(radius - aa, radius + aa, dist);\n  vec3 dots = ink * min(0.8 / max(level, 1e-3), 2.2) * dot_;\n  float presence = smoothstep(0.03, 0.16, level) * (0.3 + 0.14 * uStrength);\n  return mix(soft, dots, presence);\n}\n\nvoid main() {\n  vec2 frag = gl_FragCoord.xy;\n  vec3 ink = halftone(frag);\n  vec3 color = fromInk(clamp(ink, 0.0, 1.0));\n  color += (blueNoise(frag, floor(iTime * 24.0)) - 0.5) / 255.0;\n  fragColor = vec4(clamp(color, 0.0, 1.0), 1.0);\n}\n`;\n\nconst FRAGMENT = `#version 300 es\nprecision highp float;\nuniform sampler2D uText;\nuniform sampler2D uHero;\nuniform vec2 uResolution;\nuniform vec4 uBounds;\nuniform float uEnter;\nuniform float uExit;\nuniform float uScale;\nuniform float uTime;\nuniform float uIntensity;\nout vec4 fragColor;\n\nvoid main() {\n  vec2 uv = (gl_FragCoord.xy / uResolution - 0.5) / uScale + 0.5;\n  vec2 q = (uv - uBounds.xy) / uBounds.zw;\n  float flow = sin(q.x * 7.0 + q.y * 5.0 - uTime * 3.2) * 0.065\n    + sin(q.x * 13.0 - q.y * 8.0 + uTime * 2.1) * 0.025;\n  float rank = q.x * 0.64 + (1.0 - q.y) * 0.36 + flow;\n  float d = rank - mix(-0.15, 1.65, uEnter);\n  float edge = exp(-pow((d + 0.1) * 6.0, 2.0));\n  // Refraction affects the glyph itself, concentrated inside the wet leading edge.\n  vec3 field = texture(uHero, uv).rgb;\n  vec2 warp = vec2(field.r - field.b, field.g - field.r);\n  vec2 sampleUV = uv + warp * edge * uIntensity * 2.5 / uResolution;\n  float glyph = texture(uText, sampleUV).a;\n  float appear = 1.0 - smoothstep(-0.025, 0.09, d);\n  float exitRank = q.x * 0.65 + (1.0 - q.y) * 0.5 + flow;\n  float leave = smoothstep(-0.12, 0.06, exitRank - mix(-0.25, 1.45, uExit));\n\n  vec3 liquid = texture(uHero, sampleUV).rgb;\n  vec3 silver = vec3(0.96, 0.925, 1.0);\n  float settled = 1.0 - smoothstep(-0.58, -0.16, d);\n  vec3 color = mix(liquid, silver, settled);\n  color = mix(silver, color, uIntensity);\n  float alpha = glyph * appear * leave;\n  fragColor = vec4(color * alpha, alpha);\n}`;\n\nfunction createRenderer(canvas: HTMLCanvasElement) {\n  const gl = canvas.getContext(\"webgl2\", {\n    alpha: true,\n    premultipliedAlpha: true,\n    antialias: false,\n    depth: false,\n  });\n  if (!gl) throw new Error(\"Shader Text Reveal requires WebGL2.\");\n  const shaders: WebGLShader[] = [];\n  const programs: WebGLProgram[] = [];\n  const textures: WebGLTexture[] = [];\n  const buffers: WebGLFramebuffer[] = [];\n  const dispose = () => {\n    for (const shader of shaders) gl.deleteShader(shader);\n    for (const program of programs) gl.deleteProgram(program);\n    for (const texture of textures) gl.deleteTexture(texture);\n    for (const buffer of buffers) gl.deleteFramebuffer(buffer);\n  };\n  const compile = (fragment: string) => {\n    const program = gl.createProgram();\n    if (!program) throw new Error(\"Shader Text Reveal: GPU allocation failed.\");\n    programs.push(program);\n    for (const [type, source] of [\n      [gl.VERTEX_SHADER, VERTEX],\n      [gl.FRAGMENT_SHADER, fragment],\n    ] as const) {\n      const shader = gl.createShader(type);\n      if (!shader)\n        throw new Error(\"Shader Text Reveal: shader allocation failed.\");\n      shaders.push(shader);\n      gl.shaderSource(shader, source);\n      gl.compileShader(shader);\n      if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS))\n        throw new Error(\n          gl.getShaderInfoLog(shader) ?? \"Shader compilation failed.\",\n        );\n      gl.attachShader(program, shader);\n    }\n    gl.linkProgram(program);\n    if (!gl.getProgramParameter(program, gl.LINK_STATUS))\n      throw new Error(gl.getProgramInfoLog(program) ?? \"Shader link failed.\");\n    const locations = new Map<string, WebGLUniformLocation | null>();\n    const uniform = (name: string) => {\n      if (!locations.has(name))\n        locations.set(name, gl.getUniformLocation(program, name));\n      return locations.get(name) ?? null;\n    };\n    return {\n      uniform,\n      begin: (time: number) => {\n        gl.useProgram(program);\n        gl.viewport(0, 0, canvas.width, canvas.height);\n        gl.uniform2f(uniform(\"iResolution\"), canvas.width, canvas.height);\n        gl.uniform1f(uniform(\"iTime\"), time);\n        gl.uniform1f(uniform(\"uLightMode\"), 0);\n        gl.uniform3f(uniform(\"uDarkBackground\"), 9 / 255, 9 / 255, 9 / 255);\n        gl.uniform3f(uniform(\"uLightBackground\"), 1, 1, 1);\n        gl.uniform1f(uniform(\"uPixelRatio\"), 1);\n      },\n    };\n  };\n  const texture = () => {\n    const value = gl.createTexture();\n    if (!value)\n      throw new Error(\"Shader Text Reveal: texture allocation failed.\");\n    textures.push(value);\n    gl.activeTexture(gl.TEXTURE0);\n    gl.bindTexture(gl.TEXTURE_2D, value);\n    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);\n    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);\n    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n    return value;\n  };\n  const target = () => {\n    const value = texture();\n    const buffer = gl.createFramebuffer();\n    if (!buffer)\n      throw new Error(\"Shader Text Reveal: framebuffer allocation failed.\");\n    buffers.push(buffer);\n    gl.bindFramebuffer(gl.FRAMEBUFFER, buffer);\n    gl.texImage2D(\n      gl.TEXTURE_2D,\n      0,\n      gl.RGBA,\n      canvas.width,\n      canvas.height,\n      0,\n      gl.RGBA,\n      gl.UNSIGNED_BYTE,\n      null,\n    );\n    gl.framebufferTexture2D(\n      gl.FRAMEBUFFER,\n      gl.COLOR_ATTACHMENT0,\n      gl.TEXTURE_2D,\n      value,\n      0,\n    );\n    if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) !== gl.FRAMEBUFFER_COMPLETE)\n      throw new Error(\"Shader Text Reveal: incomplete framebuffer.\");\n    return { value, buffer };\n  };\n  const bind = (value: WebGLTexture, unit: number) => {\n    gl.activeTexture(gl.TEXTURE0 + unit);\n    gl.bindTexture(gl.TEXTURE_2D, value);\n  };\n  try {\n    const field = compile(HERO_FIELD);\n    const halftone = compile(HERO_HALFTONE);\n    const composite = compile(FRAGMENT);\n    const text = texture();\n    const raw = target();\n    const hero = target();\n    gl.bindFramebuffer(gl.FRAMEBUFFER, null);\n    gl.clearColor(0, 0, 0, 0);\n    gl.clear(gl.COLOR_BUFFER_BIT);\n    let previous: HTMLCanvasElement | null = null;\n    return {\n      dispose,\n      draw: (\n        word: TextTexture,\n        state: ReturnType<typeof getShaderTextState>,\n        time: number,\n        intensity: number,\n      ) => {\n        if (gl.isContextLost())\n          throw new Error(\"Shader Text Reveal: WebGL context lost.\");\n        // Render the hero's original field, then its original halftone pass.\n        gl.bindFramebuffer(gl.FRAMEBUFFER, raw.buffer);\n        field.begin(time);\n        gl.drawArrays(gl.TRIANGLES, 0, 3);\n        gl.bindFramebuffer(gl.FRAMEBUFFER, hero.buffer);\n        halftone.begin(time);\n        bind(raw.value, 0);\n        gl.uniform1i(halftone.uniform(\"tScene\"), 0);\n        gl.drawArrays(gl.TRIANGLES, 0, 3);\n\n        gl.bindFramebuffer(gl.FRAMEBUFFER, null);\n        composite.begin(time);\n        bind(text, 0);\n        if (previous !== word.canvas) {\n          gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);\n          gl.texImage2D(\n            gl.TEXTURE_2D,\n            0,\n            gl.RGBA,\n            gl.RGBA,\n            gl.UNSIGNED_BYTE,\n            word.canvas,\n          );\n          gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);\n          previous = word.canvas;\n        }\n        bind(hero.value, 1);\n        gl.uniform1i(composite.uniform(\"uText\"), 0);\n        gl.uniform1i(composite.uniform(\"uHero\"), 1);\n        gl.uniform2f(\n          composite.uniform(\"uResolution\"),\n          canvas.width,\n          canvas.height,\n        );\n        gl.uniform4f(composite.uniform(\"uBounds\"), ...word.bounds);\n        gl.uniform1f(composite.uniform(\"uEnter\"), state.enter);\n        gl.uniform1f(composite.uniform(\"uExit\"), state.exit);\n        gl.uniform1f(composite.uniform(\"uScale\"), state.scale);\n        gl.uniform1f(composite.uniform(\"uTime\"), time);\n        gl.uniform1f(composite.uniform(\"uIntensity\"), clamp(intensity));\n        gl.drawArrays(gl.TRIANGLES, 0, 3);\n      },\n    };\n  } catch (error) {\n    dispose();\n    throw error;\n  }\n}\n\ntype TextTexture = {\n  canvas: HTMLCanvasElement;\n  bounds: [number, number, number, number];\n};\n\nfunction textTexture(\n  text: string,\n  width: number,\n  height: number,\n  fontSize: number,\n  fontWeight: number,\n  fontFamily: string,\n): TextTexture {\n  const canvas = document.createElement(\"canvas\");\n  canvas.width = width;\n  canvas.height = height;\n  const ctx = canvas.getContext(\"2d\");\n  if (!ctx) throw new Error(\"Shader Text Reveal: cannot rasterize text.\");\n  let size = Math.min(Math.max(1, fontSize), height * 0.58);\n  ctx.font = `${fontWeight} ${size}px ${fontFamily}`;\n  size *= Math.min(\n    1,\n    (width * 0.72) / Math.max(1, ctx.measureText(text).width),\n  );\n  ctx.font = `${fontWeight} ${size}px ${fontFamily}`;\n  const metrics = ctx.measureText(text);\n  const left = (width - metrics.width) / 2;\n  const ascent = metrics.actualBoundingBoxAscent;\n  const descent = metrics.actualBoundingBoxDescent;\n  const baseline = (height + ascent - descent) / 2;\n  ctx.fillStyle = \"white\";\n  ctx.fillText(text, left, baseline);\n  return {\n    canvas,\n    bounds: [\n      left / width,\n      (height - baseline - descent) / height,\n      Math.max(1, metrics.width) / width,\n      Math.max(1, ascent + descent) / height,\n    ],\n  };\n}\n\nexport function ShaderTextReveal({\n  text = \"Your product\",\n  fontSize = 400,\n  fontFamily = \"Arial, sans-serif\",\n  fontWeight = 500,\n  wordDuration = 30,\n  intensity = 1,\n}: ShaderTextRevealProps) {\n  const frame = useCurrentFrame();\n  const { width, height, fps } = useVideoConfig();\n  const { delayRender, continueRender, cancelRender } = useDelayRender();\n  const canvas = useRef<HTMLCanvasElement>(null);\n  const resources = useRef<{\n    renderer: ReturnType<typeof createRenderer>;\n    words: TextTexture[];\n  } | null>(null);\n  const current = useRef({ frame, fps, wordDuration, intensity });\n  current.current = { frame, fps, wordDuration, intensity };\n\n  useLayoutEffect(() => {\n    const element = canvas.current;\n    if (!element) return;\n    let cancelled = false;\n    const handle = delayRender(\"Load Shader Text Reveal font\");\n    const phrases = text.includes(\"\\n\") ? text.split(\"\\n\") : text.split(/\\s+/);\n    const words = phrases.map((word) => word.trim()).filter(Boolean);\n    const draw = () => {\n      const resource = resources.current;\n      if (!resource || resource.words.length === 0) return;\n      const clock = current.current;\n      const state = getShaderTextState(\n        clock.frame,\n        resource.words.length,\n        clock.wordDuration,\n      );\n      resource.renderer.draw(\n        resource.words[state.index],\n        state,\n        clock.frame / clock.fps,\n        clock.intensity,\n      );\n    };\n    void document.fonts\n      .load(`${fontWeight} ${fontSize}px ${fontFamily}`, text || \" \")\n      .then(() => {\n        if (cancelled) return;\n        const textures = words.map((word) =>\n          textTexture(word, width, height, fontSize, fontWeight, fontFamily),\n        );\n        const renderer = createRenderer(element);\n        resources.current = { renderer, words: textures };\n        draw();\n        continueRender(handle);\n      })\n      .catch((error: unknown) => {\n        if (!cancelled)\n          cancelRender(\n            error instanceof Error ? error : new Error(String(error)),\n          );\n      });\n    return () => {\n      cancelled = true;\n      continueRender(handle);\n      resources.current?.renderer.dispose();\n      resources.current = null;\n    };\n  }, [\n    text,\n    width,\n    height,\n    fontSize,\n    fontWeight,\n    fontFamily,\n    delayRender,\n    continueRender,\n    cancelRender,\n  ]);\n\n  useLayoutEffect(() => {\n    const resource = resources.current;\n    if (!resource || resource.words.length === 0) return;\n    try {\n      const state = getShaderTextState(\n        frame,\n        resource.words.length,\n        wordDuration,\n      );\n      resource.renderer.draw(\n        resource.words[state.index],\n        state,\n        frame / fps,\n        intensity,\n      );\n    } catch (error) {\n      cancelRender(error instanceof Error ? error : new Error(String(error)));\n    }\n  }, [frame, fps, wordDuration, intensity, cancelRender]);\n\n  return (\n    <canvas\n      ref={canvas}\n      width={width}\n      height={height}\n      role=\"img\"\n      aria-label={text}\n      style={{\n        position: \"absolute\",\n        inset: 0,\n        width: \"100%\",\n        height: \"100%\",\n        pointerEvents: \"none\",\n      }}\n    />\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/shader-text-reveal.tsx"
    }
  ],
  "type": "registry:component"
}
