"use client" import { useRef, useEffect } from "react" interface PixelTextRevealProps { lines?: string[] className?: string } export default function PixelTextReveal({ lines = ["DAL", "Ecosystem"], className, }: PixelTextRevealProps) { const canvasRef = useRef(null) useEffect(() => { const canvas = canvasRef.current if (!canvas) return const ctx = canvas.getContext("2d") if (!ctx) return const drawCtx = ctx const dpr = window.devicePixelRatio || 1 const width = canvas.clientWidth const height = canvas.clientHeight canvas.width = width * dpr canvas.height = height * dpr ctx.scale(dpr, dpr) const cellSize = 5 const gap = 1.5 const step = cellSize + gap const cols = Math.floor(width / step) const rows = Math.floor(height / step) const scale = 6 const offW = cols * scale const offH = rows * scale const offscreen = document.createElement("canvas") offscreen.width = offW offscreen.height = offH const offCtx = offscreen.getContext("2d")! offCtx.fillStyle = "#000" offCtx.fillRect(0, 0, offW, offH) offCtx.fillStyle = "#fff" offCtx.textAlign = "center" offCtx.textBaseline = "middle" const primarySize = Math.max(48, Math.floor(offH * 0.38)) const secondarySize = Math.max(36, Math.floor(offH * 0.26)) const lineGap = Math.floor(offH * 0.06) const totalH = primarySize + (lines.length > 1 ? secondarySize + lineGap : 0) const baseY = (offH - totalH) / 2 lines.forEach((line, i) => { const size = i === 0 ? primarySize : secondarySize offCtx.font = `${i === 0 ? 900 : 800} ${size}px "Inter", "SF Pro Display", system-ui, sans-serif` offCtx.letterSpacing = i === 0 ? "0.05em" : "0.02em" const y = i === 0 ? baseY + primarySize / 2 : baseY + primarySize + lineGap + secondarySize / 2 offCtx.fillText(line, offW / 2, y) }) const imgData = offCtx.getImageData(0, 0, offW, offH) const textMap: boolean[][] = [] for (let gy = 0; gy < rows; gy++) { textMap[gy] = [] for (let gx = 0; gx < cols; gx++) { let sum = 0 for (let sy = 0; sy < scale; sy++) { for (let sx = 0; sx < scale; sx++) { sum += imgData.data[((gy * scale + sy) * offW + gx * scale + sx) * 4] } } textMap[gy][gx] = sum / (scale * scale) > 60 } } const centerX = cols / 2 const centerY = rows / 2 const cells = Array.from({ length: rows }, (_, y) => Array.from({ length: cols }, (_, x) => { const isText = textMap[y][x] const dist = Math.hypot(x - centerX, y - centerY) return { opacity: 0, target: isText ? 1.0 : 0.006 + Math.random() * 0.014, delay: isText ? dist * 12 + Math.random() * 200 : Math.random() * 600, isText, } }) ) let fg = document.documentElement.classList.contains("dark") ? "255,255,255" : "5,5,5" const observer = new MutationObserver(() => { fg = document.documentElement.classList.contains("dark") ? "255,255,255" : "5,5,5" }) observer.observe(document.documentElement, { attributes: true, attributeFilter: ["class"], }) const startTime = performance.now() let raf: number function draw(now: number) { const elapsed = now - startTime drawCtx.clearRect(0, 0, width, height) for (let y = 0; y < rows; y++) { for (let x = 0; x < cols; x++) { const c = cells[y][x] if (elapsed > c.delay) { c.opacity += (c.target - c.opacity) * 0.06 } if (c.opacity < 0.004) continue drawCtx.fillStyle = `rgba(${fg},${c.opacity})` drawCtx.fillRect(x * step, y * step, cellSize, cellSize) } } raf = requestAnimationFrame(draw) } raf = requestAnimationFrame(draw) return () => { cancelAnimationFrame(raf) observer.disconnect() } }, [lines]) return ( ) }