{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"static-type","type":"registry:ui","title":"Static Type","description":"Canvas-drawn lettering whose pixel rows jitter sideways like a weak signal, getting noisier when you hover it.","dependencies":[],"registryDependencies":[],"files":[{"path":"registry/ui/static-type.tsx","type":"registry:ui","content":"\"use client\";\n\nimport { useEffect, useRef } from \"react\";\n\nexport interface StaticTypeProps {\n  text?: string;\n  /** Any CSS font-size; resolved to px for the canvas. */\n  fontSize?: string;\n  fontWeight?: number;\n  color?: string;\n  /** Jitter at rest, 0-1. */\n  baseIntensity?: number;\n  /** Jitter while the pointer is over the text, 0-1. */\n  hoverIntensity?: number;\n  /** Maximum horizontal displacement of a row, in px. */\n  fuzzRange?: number;\n  /** Ramp up the jitter on hover. */\n  enableHover?: boolean;\n  className?: string;\n}\n\nexport function StaticType({\n  text = \"FUZZY\",\n  fontSize = \"clamp(4rem, 15vw, 10rem)\",\n  fontWeight = 900,\n  color = \"#fafafa\",\n  baseIntensity = 0.18,\n  hoverIntensity = 0.55,\n  fuzzRange = 30,\n  enableHover = true,\n  className,\n}: StaticTypeProps) {\n  const wrap = useRef<HTMLSpanElement>(null);\n  const ref = useRef<HTMLCanvasElement>(null);\n\n  useEffect(() => {\n    const root = wrap.current!;\n    const canvas = ref.current!;\n    const ctx = canvas.getContext(\"2d\")!;\n    const off = document.createElement(\"canvas\");\n    const octx = off.getContext(\"2d\")!;\n    const reduced = window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches;\n    let dpr = 1;\n    let tw = 0;\n    let th = 0;\n    let pad = 0;\n    let intensity = baseIntensity;\n    let target = baseIntensity;\n    let raf = 0;\n    let visible = true;\n\n    const layout = () => {\n      const cs = getComputedStyle(root);\n      const px = parseFloat(cs.fontSize) || 96;\n      dpr = Math.min(window.devicePixelRatio || 1, 2);\n      const font = `${fontWeight} ${px}px ${cs.fontFamily}`;\n      octx.font = font;\n      const m = octx.measureText(text);\n      const left = m.actualBoundingBoxLeft;\n      tw = Math.ceil(left + m.actualBoundingBoxRight) + 4;\n      const asc = m.actualBoundingBoxAscent;\n      th = Math.ceil(asc + m.actualBoundingBoxDescent) + 8;\n      pad = fuzzRange;\n      off.width = tw * dpr;\n      off.height = th * dpr;\n      octx.setTransform(dpr, 0, 0, dpr, 0, 0);\n      octx.font = font;\n      octx.fillStyle = color;\n      octx.textBaseline = \"alphabetic\";\n      octx.fillText(text, left + 2, asc + 4);\n      canvas.width = (tw + pad * 2) * dpr;\n      canvas.height = th * dpr;\n      canvas.style.width = `${tw + pad * 2}px`;\n      canvas.style.height = `${th}px`;\n      canvas.style.margin = `0 ${-pad}px`;\n      draw();\n    };\n\n    const draw = () => {\n      ctx.clearRect(0, 0, canvas.width, canvas.height);\n      const range = reduced ? 0 : intensity * pad * dpr;\n      const w = off.width;\n      // One device-pixel row at a time, each shoved sideways by its own random amount.\n      for (let y = 0; y < off.height; y++) {\n        const dx = (Math.random() - 0.5) * 2 * range;\n        ctx.drawImage(off, 0, y, w, 1, pad * dpr + dx, y, w, 1);\n      }\n    };\n\n    const loop = () => {\n      intensity += (target - intensity) * 0.12;\n      draw();\n      raf = visible && !reduced ? requestAnimationFrame(loop) : 0;\n    };\n\n    const move = (e: PointerEvent) => {\n      const r = canvas.getBoundingClientRect();\n      const x = e.clientX - r.left;\n      const y = e.clientY - r.top;\n      const inside = x > pad && x < r.width - pad && y > 0 && y < r.height;\n      target = inside ? hoverIntensity : baseIntensity;\n    };\n    const leave = () => (target = baseIntensity);\n\n    layout();\n    document.fonts?.ready.then(layout);\n    window.addEventListener(\"resize\", layout);\n    const io = new IntersectionObserver(([entry]) => {\n      visible = entry.isIntersecting;\n      if (visible && !raf && !reduced) raf = requestAnimationFrame(loop);\n    });\n    io.observe(canvas);\n    if (enableHover) {\n      canvas.addEventListener(\"pointermove\", move, { passive: true });\n      canvas.addEventListener(\"pointerleave\", leave);\n    }\n    return () => {\n      cancelAnimationFrame(raf);\n      raf = -1;\n      io.disconnect();\n      window.removeEventListener(\"resize\", layout);\n      canvas.removeEventListener(\"pointermove\", move);\n      canvas.removeEventListener(\"pointerleave\", leave);\n    };\n  }, [text, fontSize, fontWeight, color, baseIntensity, hoverIntensity, fuzzRange, enableHover]);\n\n  return (\n    <span ref={wrap} className={className} style={{ display: \"inline-block\", fontSize, lineHeight: 0 }}>\n      <span className=\"sr-only\">{text}</span>\n      <canvas ref={ref} aria-hidden style={{ display: \"block\" }} />\n    </span>\n  );\n}\n"}]}