{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "block-text",
  "title": "Block Text",
  "description": "A colored 3D block that keeps rolling forward, bringing up the next word as it tips.",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "registry/clover-ui/block-text.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport {\n  animate,\n  motion,\n  useMotionValue,\n  useReducedMotion,\n  useTransform,\n  type MotionValue,\n} from \"motion/react\";\n\nimport { cn } from \"@/lib/utils\";\n\nexport const BLOCK_TEXT_THEMES = {\n  halloween: { colors: [\"#ea580c\", \"#6d28d9\", \"#1c1917\"], textColor: \"#fff7ed\" },\n  classic: { colors: [\"#16a34a\", \"#ff5a1f\", \"#0029ff\"], textColor: \"#f5f5f5\" },\n  ocean: { colors: [\"#0369a1\", \"#0d9488\", \"#1e3a8a\"], textColor: \"#f0f9ff\" },\n  candy: { colors: [\"#db2777\", \"#9333ea\", \"#e11d48\"], textColor: \"#fdf2f8\" },\n  sunset: { colors: [\"#dc2626\", \"#ea580c\", \"#b45309\"], textColor: \"#fff7ed\" },\n} as const;\n\nexport type BlockTextTheme = keyof typeof BLOCK_TEXT_THEMES;\n\nexport interface BlockTextProps extends Omit<React.ComponentProps<\"span\">, \"children\" | \"color\"> {\n  words?: string[];\n  theme?: BlockTextTheme;\n  colors?: string[];\n  textColor?: string;\n  interval?: number;\n  duration?: number;\n  paused?: boolean;\n  shimmer?: boolean;\n}\n\nconst DEFAULT_WORDS = [\"Trick\", \"Treat\", \"Boo!\"];\nconst FACES = [0, 1, 2, 3];\n\nfunction faceStep(face: number, step: number) {\n  const ahead = step + 1;\n  return ahead - ((((ahead - face) % 4) + 4) % 4);\n}\n\nfunction Face({\n  face,\n  angle,\n  word,\n  color,\n  shimmer,\n}: {\n  face: number;\n  angle: MotionValue<number>;\n  word: string;\n  color: string;\n  shimmer: boolean;\n}) {\n  const shade = useTransform(angle, (value) => {\n    const turn = ((value - face * 90) * Math.PI) / 180;\n    return Math.min(0.55, Math.max(0, (1 - Math.cos(turn)) * 0.55));\n  });\n\n  return (\n    <span\n      className=\"absolute inset-0 flex items-center justify-center overflow-hidden px-[var(--block-px,0.55em)] backface-hidden\"\n      style={{\n        backgroundImage: `radial-gradient(120% 140% at 50% 0%, color-mix(in oklab, ${color} 62%, white) 0%, ${color} 42%, color-mix(in oklab, ${color} 66%, black) 100%)`,\n        transform: `rotateX(${face * -90}deg) translateZ(calc(var(--block-h) / 2))`,\n      }}\n    >\n      <span\n        aria-hidden\n        className=\"pointer-events-none absolute inset-px bg-[linear-gradient(180deg,rgba(255,255,255,.28),transparent_42%)] opacity-80\"\n      />\n      <span\n        aria-hidden\n        className=\"pointer-events-none absolute -inset-x-1/3 top-0 h-px bg-white/70 blur-sm\"\n      />\n      {shimmer ? (\n        <motion.span\n          className=\"relative whitespace-nowrap [filter:drop-shadow(0_1px_0_rgb(0_0_0/0.18))]\"\n          style={{\n            WebkitTextFillColor: \"transparent\",\n            backgroundColor: \"currentColor\",\n            backgroundImage: `linear-gradient(to right, currentColor 0%, color-mix(in oklab, ${color} 60%, white) 40%, color-mix(in oklab, ${color} 60%, white) 60%, currentColor 100%)`,\n            WebkitBackgroundClip: \"text\",\n            backgroundClip: \"text\",\n            backgroundRepeat: \"no-repeat\",\n            backgroundSize: \"50% 200%\",\n          }}\n          initial={{ backgroundPositionX: \"250%\" }}\n          animate={{ backgroundPositionX: [\"-100%\", \"250%\"] }}\n          transition={{ duration: 1.5, repeat: Infinity, repeatDelay: 1.5, ease: \"linear\" }}\n        >\n          {word}\n        </motion.span>\n      ) : (\n        <span className=\"relative whitespace-nowrap [text-shadow:0_1px_0_rgb(0_0_0/0.18)]\">\n          {word}\n        </span>\n      )}\n      <motion.span className=\"absolute inset-0 bg-black\" style={{ opacity: shade }} />\n    </span>\n  );\n}\n\nfunction BlockText({\n  words = DEFAULT_WORDS,\n  theme = \"halloween\",\n  colors,\n  textColor,\n  interval = 1.4,\n  duration = 1.2,\n  paused = false,\n  shimmer = true,\n  className,\n  style,\n  ...props\n}: BlockTextProps) {\n  const reduceMotion = useReducedMotion();\n  const angle = useMotionValue(0);\n  const [step, setStep] = React.useState(0);\n  const stepRef = React.useRef(0);\n  const list = words.length > 0 ? words : [\"\"];\n  const preset = BLOCK_TEXT_THEMES[theme] ?? BLOCK_TEXT_THEMES.halloween;\n  const palette: readonly string[] = colors && colors.length > 0 ? colors : preset.colors;\n  const count = list.length;\n\n  const transform = useTransform(\n    angle,\n    (value) => `translateZ(calc(var(--block-h) / -2)) rotateX(${value}deg)`,\n  );\n\n  React.useEffect(() => {\n    if (paused || count < 2) return;\n\n    let timer: ReturnType<typeof setTimeout> | undefined;\n    let controls: ReturnType<typeof animate> | undefined;\n    let cancelled = false;\n\n    const roll = () => {\n      timer = setTimeout(() => {\n        const next = stepRef.current + 1;\n        controls = animate(angle, next * 90, {\n          duration: reduceMotion ? 0 : duration,\n          ease: [0.65, 0, 0.25, 1],\n        });\n        controls.then(() => {\n          if (cancelled) return;\n          stepRef.current = next;\n          setStep(next);\n          roll();\n        });\n      }, interval * 1000);\n    };\n\n    roll();\n\n    return () => {\n      cancelled = true;\n      if (timer) clearTimeout(timer);\n      controls?.stop();\n      angle.set(stepRef.current * 90);\n    };\n  }, [angle, count, duration, interval, paused, reduceMotion]);\n\n  return (\n    <span\n      className={cn(\n        \"relative inline-block align-middle text-4xl leading-none font-bold tracking-tight [--block-h:1.9em] perspective-[14em]\",\n        className,\n      )}\n      style={{ color: textColor ?? preset.textColor, ...style }}\n      {...props}\n    >\n      <span className=\"sr-only\">{list.join(\", \")}</span>\n\n      <span aria-hidden className=\"invisible grid\">\n        {list.map((word, index) => (\n          <span\n            key={`${word}-${index}`}\n            className=\"col-start-1 row-start-1 flex h-(--block-h) items-center px-[var(--block-px,0.55em)] whitespace-nowrap\"\n          >\n            {word}\n          </span>\n        ))}\n      </span>\n\n      <motion.span\n        aria-hidden\n        className=\"absolute inset-0 will-change-transform transform-3d\"\n        style={{ transform }}\n      >\n        {FACES.map((face) => {\n          const at = faceStep(face, step);\n          const index = ((at % count) + count) % count;\n          return (\n            <Face\n              key={face}\n              face={face}\n              angle={angle}\n              word={list[index]}\n              color={palette[index % palette.length]}\n              shimmer={shimmer && !reduceMotion}\n            />\n          );\n        })}\n      </motion.span>\n    </span>\n  );\n}\n\nexport { BlockText };\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:component"
}