{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "DepthText-TS-TW",
	"title": "DepthText",
	"description": "Layered extruded type with parallax that shifts against the pointer.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:component",
			"path": "DepthText/DepthText.tsx",
			"content": "import { useEffect, useMemo, useRef, type CSSProperties } from 'react';\n\nexport interface DepthTextProps {\n  text?: string;\n  layers?: number;\n  depth?: number;\n  faceColor?: string;\n  depthColor?: string;\n  tilt?: number;\n  pointerTracking?: boolean;\n  smoothing?: number;\n  perspective?: number;\n  autoOrbit?: boolean;\n  orbitSpeed?: number;\n  fontSize?: string;\n  fontWeight?: number | string;\n  shadow?: boolean;\n  className?: string;\n  style?: CSSProperties;\n}\n\ninterface DepthLayer {\n  index: number;\n  color: string;\n  transform: string;\n}\n\nconst MAX_LAYERS = 64;\n\nconst clamp = (value: number, min: number, max: number): number => Math.min(Math.max(value, min), max);\n\nconst getLayerColor = (faceColor: string, depthColor: string, index: number, total: number): string => {\n  const progress = total <= 1 ? 1 : index / total;\n  const eased = progress * progress;\n  const faceMix = Math.round((1 - eased) * 72 + 4);\n  return `color-mix(in srgb, ${faceColor} ${faceMix}%, ${depthColor})`;\n};\n\nconst getTransform = (rotateX: number, rotateY: number): string =>\n  `rotateX(${rotateX.toFixed(3)}deg) rotateY(${rotateY.toFixed(3)}deg)`;\n\nconst DepthText = ({\n  text = 'Elevate',\n  layers = 34,\n  depth = 2.4,\n  faceColor = '#f8fafc',\n  depthColor = '#7c3aed',\n  tilt = 7.5,\n  pointerTracking = true,\n  smoothing = 0.14,\n  perspective = 900,\n  autoOrbit = true,\n  orbitSpeed = 0.35,\n  fontSize = 'clamp(3rem, 12vw, 7rem)',\n  fontWeight = 900,\n  shadow = true,\n  className = '',\n  style = {}\n}: DepthTextProps) => {\n  const rootRef = useRef<HTMLSpanElement | null>(null);\n  const stageRef = useRef<HTMLSpanElement | null>(null);\n\n  const safeLayers = clamp(Math.round(Number(layers) || 1), 2, MAX_LAYERS);\n  const safeDepth = clamp(Number(depth) || 0, 0, 12);\n  const safeTilt = clamp(Number(tilt) || 0, 0, 12);\n  const safeSmoothing = clamp(Number(smoothing) || 0.14, 0.02, 0.35);\n  const safePerspective = clamp(Number(perspective) || 900, 300, 2000);\n  const safeOrbitSpeed = clamp(Number(orbitSpeed) || 0, 0, 2);\n\n  const baseRotation = useMemo(() => ({ x: -safeTilt * 0.32, y: safeTilt * 0.42 }), [safeTilt]);\n\n  const depthLayers = useMemo<DepthLayer[]>(\n    () =>\n      Array.from({ length: safeLayers }, (_, layerIndex) => {\n        const index = safeLayers - layerIndex;\n        return {\n          index,\n          color: getLayerColor(faceColor, depthColor, index, safeLayers),\n          transform: `translateZ(${-index * safeDepth}px)`\n        };\n      }),\n    [safeLayers, safeDepth, faceColor, depthColor]\n  );\n\n  useEffect(() => {\n    const root = rootRef.current;\n    const stage = stageRef.current;\n    if (!root || !stage || typeof window === 'undefined') return undefined;\n\n    const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n    const finePointer = window.matchMedia('(hover: hover) and (pointer: fine)').matches;\n    const canTrackPointer = pointerTracking && finePointer && !reducedMotion;\n\n    let frameId = 0;\n    let activePointer = false;\n    let startTime = performance.now();\n    const current = { ...baseRotation };\n    const target = { ...baseRotation };\n\n    const applyTransform = () => {\n      stage.style.transform = getTransform(current.x, current.y);\n    };\n\n    if (reducedMotion) {\n      stage.style.transform = getTransform(baseRotation.x, baseRotation.y);\n      return undefined;\n    }\n\n    const handlePointerMove = (event: PointerEvent) => {\n      const rect = root.getBoundingClientRect();\n      if (!rect.width || !rect.height) return;\n\n      activePointer = true;\n      const x = clamp((event.clientX - (rect.left + rect.width / 2)) / (rect.width * 0.8), -1, 1);\n      const y = clamp((event.clientY - (rect.top + rect.height / 2)) / (rect.height * 0.8), -1, 1);\n\n      target.x = baseRotation.x - y * safeTilt;\n      target.y = baseRotation.y + x * safeTilt;\n    };\n\n    const handlePointerLeave = () => {\n      activePointer = false;\n      target.x = baseRotation.x;\n      target.y = baseRotation.y;\n    };\n\n    if (canTrackPointer) {\n      window.addEventListener('pointermove', handlePointerMove);\n      window.addEventListener('pointerleave', handlePointerLeave);\n      window.addEventListener('blur', handlePointerLeave);\n    }\n\n    const tick = (now: number) => {\n      if ((!canTrackPointer || !activePointer) && autoOrbit) {\n        const elapsed = (now - startTime) / 1000;\n        const orbit = elapsed * safeOrbitSpeed * Math.PI * 2;\n        const fallbackAmount = canTrackPointer ? 0.18 : 0.55;\n        target.x = baseRotation.x + Math.sin(orbit) * safeTilt * fallbackAmount;\n        target.y = baseRotation.y + Math.cos(orbit * 0.85) * safeTilt * fallbackAmount;\n      }\n\n      current.x += (target.x - current.x) * safeSmoothing;\n      current.y += (target.y - current.y) * safeSmoothing;\n      applyTransform();\n      frameId = requestAnimationFrame(tick);\n    };\n\n    applyTransform();\n    frameId = requestAnimationFrame(tick);\n\n    return () => {\n      if (canTrackPointer) {\n        window.removeEventListener('pointermove', handlePointerMove);\n        window.removeEventListener('pointerleave', handlePointerLeave);\n        window.removeEventListener('blur', handlePointerLeave);\n      }\n      cancelAnimationFrame(frameId);\n      startTime = 0;\n    };\n  }, [autoOrbit, baseRotation, pointerTracking, safeOrbitSpeed, safeSmoothing, safeTilt]);\n\n  const rootStyle: CSSProperties = {\n    ...style,\n    perspective: `${safePerspective}px`,\n    perspectiveOrigin: '50% 48%',\n    contain: 'layout paint',\n    isolation: 'isolate'\n  };\n\n  const stageStyle: CSSProperties = {\n    transformStyle: 'preserve-3d',\n    transform: getTransform(baseRotation.x, baseRotation.y),\n    transformOrigin: '50% 50%',\n    willChange: 'transform'\n  };\n\n  const textStyle: CSSProperties = {\n    fontSize,\n    fontWeight,\n    lineHeight: 0.86,\n    letterSpacing: '-0.065em',\n    whiteSpace: 'nowrap',\n    userSelect: 'none',\n    transformStyle: 'preserve-3d',\n    backfaceVisibility: 'hidden',\n    fontKerning: 'normal',\n    textRendering: 'geometricPrecision'\n  };\n\n  return (\n    <span ref={rootRef} className={`inline-block ${className}`.trim()} style={rootStyle}>\n      <span ref={stageRef} className=\"relative inline-grid place-items-center\" style={stageStyle}>\n        {depthLayers.map(layer => (\n          <span\n            aria-hidden=\"true\"\n            className=\"pointer-events-none absolute inset-0 z-0 inline-block brightness-95 saturate-95\"\n            key={layer.index}\n            style={{ ...textStyle, color: layer.color, transform: layer.transform }}\n          >\n            {text}\n          </span>\n        ))}\n        <span\n          className=\"relative z-10 inline-block\"\n          style={{\n            ...textStyle,\n            color: faceColor,\n            textShadow: shadow\n              ? `0 22px 34px color-mix(in srgb, ${depthColor} 36%, transparent), 0 4px 8px rgba(0, 0, 0, 0.28)`\n              : 'none',\n            transform: 'translateZ(0.6px)'\n          }}\n        >\n          {text}\n        </span>\n      </span>\n    </span>\n  );\n};\n\nexport default DepthText;\n"
		}
	],
	"registryDependencies": [],
	"dependencies": []
}