97db9ee8c7
Add cli/code/office/platform/pricing pages, new home sections (Ecosystem, FeatureGrid, Faq, WorkflowSteps, BottomCta, ProductEcosystem), ScrollReveal and PixelTextReveal animation components, brand assets, and expanded site-content. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
151 lines
4.1 KiB
TypeScript
151 lines
4.1 KiB
TypeScript
"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<HTMLCanvasElement>(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 (
|
|
<canvas
|
|
ref={canvasRef}
|
|
className={className}
|
|
style={{ width: "100%", height: "100%", display: "block" }}
|
|
/>
|
|
)
|
|
}
|