{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "ASCIIText-JS-CSS",
	"title": "ASCIIText",
	"description": "Renders text with an animated ASCII background for a retro feel.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:component",
			"path": "ASCIIText/ASCIIText.jsx",
			"content": "// Component ported and enhanced from https://codepen.io/JuanFuentes/pen/eYEeoyE\n\nimport { useEffect, useRef } from 'react';\nimport * as THREE from 'three';\n\nconst vertexShader = `\nvarying vec2 vUv;\nuniform float uTime;\nuniform float mouse;\nuniform float uEnableWaves;\n\nvoid main() {\n    vUv = uv;\n    float time = uTime * 5.;\n\n    float waveFactor = uEnableWaves;\n\n    vec3 transformed = position;\n\n    transformed.x += sin(time + position.y) * 0.5 * waveFactor;\n    transformed.y += cos(time + position.z) * 0.15 * waveFactor;\n    transformed.z += sin(time + position.x) * waveFactor;\n\n    gl_Position = projectionMatrix * modelViewMatrix * vec4(transformed, 1.0);\n}\n`;\n\nconst fragmentShader = `\nvarying vec2 vUv;\nuniform float mouse;\nuniform float uTime;\nuniform sampler2D uTexture;\n\nvoid main() {\n    float time = uTime;\n    vec2 pos = vUv;\n    \n    float move = sin(time + mouse) * 0.01;\n    float r = texture2D(uTexture, pos + cos(time * 2. - time + pos.x) * .01).r;\n    float g = texture2D(uTexture, pos + tan(time * .5 + pos.x - time) * .01).g;\n    float b = texture2D(uTexture, pos - cos(time * 2. + time + pos.y) * .01).b;\n    float a = texture2D(uTexture, pos).a;\n    gl_FragColor = vec4(r, g, b, a);\n}\n`;\n\nMath.map = function (n, start, stop, start2, stop2) {\n  return ((n - start) / (stop - start)) * (stop2 - start2) + start2;\n};\n\nconst PX_RATIO = typeof window !== 'undefined' ? window.devicePixelRatio : 1;\n\nclass AsciiFilter {\n  constructor(renderer, { fontSize, fontFamily, charset, invert } = {}) {\n    this.renderer = renderer;\n    this.domElement = document.createElement('div');\n    this.domElement.style.position = 'absolute';\n    this.domElement.style.top = '0';\n    this.domElement.style.left = '0';\n    this.domElement.style.width = '100%';\n    this.domElement.style.height = '100%';\n\n    this.pre = document.createElement('pre');\n    this.domElement.appendChild(this.pre);\n\n    this.canvas = document.createElement('canvas');\n    this.context = this.canvas.getContext('2d');\n    this.domElement.appendChild(this.canvas);\n\n    this.deg = 0;\n    this.invert = invert ?? true;\n    this.fontSize = fontSize ?? 12;\n    this.fontFamily = fontFamily ?? \"'Courier New', monospace\";\n    this.charset = charset ?? ' .\\'`^\",:;Il!i~+_-?][}{1)(|/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$';\n\n    this.context.webkitImageSmoothingEnabled = false;\n    this.context.mozImageSmoothingEnabled = false;\n    this.context.msImageSmoothingEnabled = false;\n    this.context.imageSmoothingEnabled = false;\n\n    this.onMouseMove = this.onMouseMove.bind(this);\n    document.addEventListener('mousemove', this.onMouseMove);\n  }\n\n  setSize(width, height) {\n    this.width = width;\n    this.height = height;\n    this.renderer.setSize(width, height);\n    this.reset();\n\n    this.center = { x: width / 2, y: height / 2 };\n    this.mouse = { x: this.center.x, y: this.center.y };\n  }\n\n  reset() {\n    this.context.font = `${this.fontSize}px ${this.fontFamily}`;\n    const charWidth = this.context.measureText('A').width;\n\n    this.cols = Math.floor(this.width / (this.fontSize * (charWidth / this.fontSize)));\n    this.rows = Math.floor(this.height / this.fontSize);\n\n    this.canvas.width = this.cols;\n    this.canvas.height = this.rows;\n    this.pre.style.fontFamily = this.fontFamily;\n    this.pre.style.fontSize = `${this.fontSize}px`;\n    this.pre.style.margin = '0';\n    this.pre.style.padding = '0';\n    this.pre.style.lineHeight = '1em';\n    this.pre.style.position = 'absolute';\n    this.pre.style.left = '0';\n    this.pre.style.top = '0';\n    this.pre.style.zIndex = '9';\n    this.pre.style.backgroundAttachment = 'fixed';\n    this.pre.style.mixBlendMode = 'difference';\n  }\n\n  render(scene, camera) {\n    this.renderer.render(scene, camera);\n\n    const w = this.canvas.width;\n    const h = this.canvas.height;\n    this.context.clearRect(0, 0, w, h);\n    if (this.context && w && h) {\n      this.context.drawImage(this.renderer.domElement, 0, 0, w, h);\n    }\n\n    this.asciify(this.context, w, h);\n    this.hue();\n  }\n\n  onMouseMove(e) {\n    this.mouse = { x: e.clientX * PX_RATIO, y: e.clientY * PX_RATIO };\n  }\n\n  get dx() {\n    return this.mouse.x - this.center.x;\n  }\n\n  get dy() {\n    return this.mouse.y - this.center.y;\n  }\n\n  hue() {\n    const deg = (Math.atan2(this.dy, this.dx) * 180) / Math.PI;\n    this.deg += (deg - this.deg) * 0.075;\n    this.domElement.style.filter = `hue-rotate(${this.deg.toFixed(1)}deg)`;\n  }\n\n  asciify(ctx, w, h) {\n    if (w && h) {\n      const imgData = ctx.getImageData(0, 0, w, h).data;\n      let str = '';\n      for (let y = 0; y < h; y++) {\n        for (let x = 0; x < w; x++) {\n          const i = x * 4 + y * 4 * w;\n          const [r, g, b, a] = [imgData[i], imgData[i + 1], imgData[i + 2], imgData[i + 3]];\n\n          if (a === 0) {\n            str += ' ';\n            continue;\n          }\n\n          let gray = (0.3 * r + 0.6 * g + 0.1 * b) / 255;\n          let idx = Math.floor((1 - gray) * (this.charset.length - 1));\n          if (this.invert) idx = this.charset.length - idx - 1;\n          str += this.charset[idx];\n        }\n        str += '\\n';\n      }\n      this.pre.innerHTML = str;\n    }\n  }\n\n  dispose() {\n    document.removeEventListener('mousemove', this.onMouseMove);\n  }\n}\n\nclass CanvasTxt {\n  constructor(txt, { fontSize = 200, fontFamily = 'Arial', color = '#fdf9f3' } = {}) {\n    this.canvas = document.createElement('canvas');\n    this.context = this.canvas.getContext('2d');\n    this.txt = txt;\n    this.fontSize = fontSize;\n    this.fontFamily = fontFamily;\n    this.color = color;\n\n    this.font = `600 ${this.fontSize}px ${this.fontFamily}`;\n  }\n\n  resize() {\n    this.context.font = this.font;\n    const metrics = this.context.measureText(this.txt);\n\n    const textWidth = Math.ceil(metrics.width) + 20;\n    const textHeight = Math.ceil(metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent) + 20;\n\n    this.canvas.width = textWidth;\n    this.canvas.height = textHeight;\n  }\n\n  render() {\n    this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);\n    this.context.fillStyle = this.color;\n    this.context.font = this.font;\n\n    const metrics = this.context.measureText(this.txt);\n    const yPos = 10 + metrics.actualBoundingBoxAscent;\n\n    this.context.fillText(this.txt, 10, yPos);\n  }\n\n  get width() {\n    return this.canvas.width;\n  }\n\n  get height() {\n    return this.canvas.height;\n  }\n\n  get texture() {\n    return this.canvas;\n  }\n}\n\nclass CanvAscii {\n  constructor(\n    { text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves },\n    containerElem,\n    width,\n    height\n  ) {\n    this.textString = text;\n    this.asciiFontSize = asciiFontSize;\n    this.textFontSize = textFontSize;\n    this.textColor = textColor;\n    this.planeBaseHeight = planeBaseHeight;\n    this.container = containerElem;\n    this.width = width;\n    this.height = height;\n    this.enableWaves = enableWaves;\n\n    this.camera = new THREE.PerspectiveCamera(45, this.width / this.height, 1, 1000);\n    this.camera.position.z = 30;\n\n    this.scene = new THREE.Scene();\n    this.mouse = { x: this.width / 2, y: this.height / 2 };\n\n    this.onMouseMove = this.onMouseMove.bind(this);\n  }\n\n  async init() {\n    try {\n      await document.fonts.load('600 200px \"IBM Plex Mono\"');\n      await document.fonts.load('500 12px \"IBM Plex Mono\"');\n    } catch (e) {\n      // Font loading failed, continue with fallback\n    }\n    await document.fonts.ready;\n\n    this.setMesh();\n    this.setRenderer();\n  }\n\n  setMesh() {\n    this.textCanvas = new CanvasTxt(this.textString, {\n      fontSize: this.textFontSize,\n      fontFamily: 'IBM Plex Mono',\n      color: this.textColor\n    });\n    this.textCanvas.resize();\n    this.textCanvas.render();\n\n    this.texture = new THREE.CanvasTexture(this.textCanvas.texture);\n    this.texture.minFilter = THREE.NearestFilter;\n\n    const textAspect = this.textCanvas.width / this.textCanvas.height;\n    const baseH = this.planeBaseHeight;\n    const planeW = baseH * textAspect;\n    const planeH = baseH;\n\n    this.geometry = new THREE.PlaneGeometry(planeW, planeH, 36, 36);\n    this.material = new THREE.ShaderMaterial({\n      vertexShader,\n      fragmentShader,\n      transparent: true,\n      uniforms: {\n        uTime: { value: 0 },\n        mouse: { value: 1.0 },\n        uTexture: { value: this.texture },\n        uEnableWaves: { value: this.enableWaves ? 1.0 : 0.0 }\n      }\n    });\n\n    this.mesh = new THREE.Mesh(this.geometry, this.material);\n    this.scene.add(this.mesh);\n  }\n\n  setRenderer() {\n    this.renderer = new THREE.WebGLRenderer({ antialias: false, alpha: true });\n    this.renderer.setPixelRatio(1);\n    this.renderer.setClearColor(0x000000, 0);\n\n    this.filter = new AsciiFilter(this.renderer, {\n      fontFamily: 'IBM Plex Mono',\n      fontSize: this.asciiFontSize,\n      invert: true\n    });\n\n    this.container.appendChild(this.filter.domElement);\n    this.setSize(this.width, this.height);\n\n    this.container.addEventListener('mousemove', this.onMouseMove);\n    this.container.addEventListener('touchmove', this.onMouseMove);\n  }\n\n  setSize(w, h) {\n    this.width = w;\n    this.height = h;\n\n    this.camera.aspect = w / h;\n    this.camera.updateProjectionMatrix();\n\n    this.filter.setSize(w, h);\n\n    this.center = { x: w / 2, y: h / 2 };\n  }\n\n  load() {\n    this.animate();\n  }\n\n  onMouseMove(evt) {\n    const e = evt.touches ? evt.touches[0] : evt;\n    const bounds = this.container.getBoundingClientRect();\n    const x = e.clientX - bounds.left;\n    const y = e.clientY - bounds.top;\n    this.mouse = { x, y };\n  }\n\n  animate() {\n    const animateFrame = () => {\n      this.animationFrameId = requestAnimationFrame(animateFrame);\n      this.render();\n    };\n    animateFrame();\n  }\n\n  render() {\n    const time = new Date().getTime() * 0.001;\n\n    this.textCanvas.render();\n    this.texture.needsUpdate = true;\n\n    this.mesh.material.uniforms.uTime.value = Math.sin(time);\n\n    this.updateRotation();\n    this.filter.render(this.scene, this.camera);\n  }\n\n  updateRotation() {\n    const x = Math.map(this.mouse.y, 0, this.height, 0.5, -0.5);\n    const y = Math.map(this.mouse.x, 0, this.width, -0.5, 0.5);\n\n    this.mesh.rotation.x += (x - this.mesh.rotation.x) * 0.05;\n    this.mesh.rotation.y += (y - this.mesh.rotation.y) * 0.05;\n  }\n\n  clear() {\n    this.scene.traverse(obj => {\n      if (obj.isMesh && typeof obj.material === 'object' && obj.material !== null) {\n        Object.keys(obj.material).forEach(key => {\n          const matProp = obj.material[key];\n          if (matProp !== null && typeof matProp === 'object' && typeof matProp.dispose === 'function') {\n            matProp.dispose();\n          }\n        });\n        obj.material.dispose();\n        obj.geometry.dispose();\n      }\n    });\n    this.scene.clear();\n  }\n\n  dispose() {\n    cancelAnimationFrame(this.animationFrameId);\n    if (this.filter) {\n      this.filter.dispose();\n      if (this.filter.domElement.parentNode) {\n        this.container.removeChild(this.filter.domElement);\n      }\n    }\n    this.container.removeEventListener('mousemove', this.onMouseMove);\n    this.container.removeEventListener('touchmove', this.onMouseMove);\n    this.clear();\n    if (this.renderer) {\n      this.renderer.dispose();\n      this.renderer.forceContextLoss();\n    }\n  }\n}\n\nexport default function ASCIIText({\n  text = 'David!',\n  asciiFontSize = 8,\n  textFontSize = 200,\n  textColor = '#fdf9f3',\n  planeBaseHeight = 8,\n  enableWaves = true\n}) {\n  const containerRef = useRef(null);\n  const asciiRef = useRef(null);\n\n  useEffect(() => {\n    if (!containerRef.current) return;\n\n    let cancelled = false;\n    let observer = null;\n    let ro = null;\n\n    const createAndInit = async (container, w, h) => {\n      const instance = new CanvAscii(\n        { text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves },\n        container,\n        w,\n        h\n      );\n      await instance.init();\n      return instance;\n    };\n\n    const setup = async () => {\n      const { width, height } = containerRef.current.getBoundingClientRect();\n\n      if (width === 0 || height === 0) {\n        observer = new IntersectionObserver(\n          async ([entry]) => {\n            if (cancelled) return;\n            if (entry.isIntersecting && entry.boundingClientRect.width > 0 && entry.boundingClientRect.height > 0) {\n              const { width: w, height: h } = entry.boundingClientRect;\n              observer.disconnect();\n              observer = null;\n\n              if (!cancelled) {\n                asciiRef.current = await createAndInit(containerRef.current, w, h);\n                if (!cancelled && asciiRef.current) {\n                  asciiRef.current.load();\n                }\n              }\n            }\n          },\n          { threshold: 0.1 }\n        );\n        observer.observe(containerRef.current);\n        return;\n      }\n\n      asciiRef.current = await createAndInit(containerRef.current, width, height);\n      if (!cancelled && asciiRef.current) {\n        asciiRef.current.load();\n\n        ro = new ResizeObserver(entries => {\n          if (!entries[0] || !asciiRef.current) return;\n          const { width: w, height: h } = entries[0].contentRect;\n          if (w > 0 && h > 0) {\n            asciiRef.current.setSize(w, h);\n          }\n        });\n        ro.observe(containerRef.current);\n      }\n    };\n\n    setup();\n\n    return () => {\n      cancelled = true;\n      if (observer) observer.disconnect();\n      if (ro) ro.disconnect();\n      if (asciiRef.current) {\n        asciiRef.current.dispose();\n        asciiRef.current = null;\n      }\n    };\n  }, [text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves]);\n\n  return (\n    <div\n      ref={containerRef}\n      className=\"ascii-text-container\"\n      style={{\n        position: 'absolute',\n        width: '100%',\n        height: '100%'\n      }}\n    >\n      <style>{`\n        @import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@500;600&display=swap');\n\n        .ascii-text-container canvas {\n          position: absolute;\n          left: 0;\n          top: 0;\n          width: 100%;\n          height: 100%;\n          image-rendering: optimizeSpeed;\n          image-rendering: -moz-crisp-edges;\n          image-rendering: -o-crisp-edges;\n          image-rendering: -webkit-optimize-contrast;\n          image-rendering: optimize-contrast;\n          image-rendering: crisp-edges;\n          image-rendering: pixelated;\n        }\n\n        .ascii-text-container pre {\n          margin: 0;\n          user-select: none;\n          padding: 0;\n          line-height: 1em;\n          text-align: left;\n          position: absolute;\n          left: 0;\n          top: 0;\n          background-image: radial-gradient(circle, #ff6188 0%, #fc9867 50%, #ffd866 100%);\n          background-attachment: fixed;\n          -webkit-text-fill-color: transparent;\n          -webkit-background-clip: text;\n          z-index: 9;\n          mix-blend-mode: difference;\n        }\n      `}</style>\n    </div>\n  );\n}\n"
		}
	],
	"registryDependencies": [],
	"dependencies": [
		"three@^0.180.0"
	]
}