{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "hyper-text",
  "title": "Hyper Text",
  "author": "Ballmac <https://ui.ballmac.com>",
  "description": "Characters flip through random letters like a split-flap board and settle left to right, as plain text or on tiles, on load, on scroll or on hover and focus.",
  "dependencies": [
    "motion@^12"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/ballmac/components/hyper-text.tsx",
      "content": "// Ballmac UI: Hyper Text. https://ui.ballmac.com/components/hyper-text\n\"use client\"\n\nimport * as React from \"react\"\nimport { motion, useInView, useReducedMotion } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst ALPHABET = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\"\n\ntype HyperTextProps = Omit<React.ComponentProps<\"span\">, \"children\"> & {\n  /** The final text. */\n  children: string\n  /** When to play. \"hover\" also plays on keyboard focus. */\n  trigger?: \"mount\" | \"inView\" | \"hover\"\n  /** \"tiles\" draws each character on a split-flap tile. */\n  variant?: \"plain\" | \"tiles\"\n  /** Milliseconds between flips of one character. */\n  speed?: number\n  /** Extra flips each character makes, per position, so the word settles left to right. */\n  spread?: number\n  /** Called when every character has settled. */\n  onComplete?: () => void\n}\n\nfunction Flap({ target, run, steps, speed, tile }: { target: string; run: number; steps: number; speed: number; tile: boolean }) {\n  const [char, setChar] = React.useState(target)\n  const reduce = useReducedMotion()\n  React.useEffect(() => {\n    if (!run || reduce || target === \" \") {\n      setChar(target)\n      return\n    }\n    let n = 0\n    const id = setInterval(() => {\n      n++\n      if (n >= steps) {\n        setChar(target)\n        clearInterval(id)\n      } else {\n        setChar(ALPHABET[Math.floor(Math.random() * ALPHABET.length)]!)\n      }\n    }, speed)\n    return () => clearInterval(id)\n  }, [run, target, steps, speed, reduce])\n\n  if (target === \" \") return <span className={tile ? \"w-[0.5em]\" : \"whitespace-pre\"}>{tile ? \"\" : \" \"}</span>\n  return (\n    <span\n      className={cn(\n        \"relative inline-flex h-[1.35em] min-w-[0.75em] items-center justify-center overflow-hidden\",\n        tile && \"rounded-[0.18em] bg-foreground px-[0.12em] text-background shadow-[inset_0_-0.08em_0_0_rgb(0_0_0/0.25)] after:absolute after:inset-x-0 after:top-1/2 after:h-px after:bg-background/30 after:content-['']\"\n      )}\n    >\n      <motion.span\n        key={char}\n        className=\"inline-block\"\n        initial={reduce ? false : { y: \"-70%\", opacity: 0 }}\n        animate={{ y: 0, opacity: 1 }}\n        transition={{ duration: Math.min(speed / 1000, 0.09), ease: \"easeOut\" }}\n      >\n        {char}\n      </motion.span>\n    </span>\n  )\n}\n\nfunction HyperText({ children: text, trigger = \"mount\", variant = \"plain\", speed = 55, spread = 2, onComplete, className, onPointerEnter, onFocus, ...props }: HyperTextProps) {\n  const ref = React.useRef<HTMLSpanElement>(null)\n  const seen = useInView(ref, { once: true })\n  const [run, setRun] = React.useState(0)\n  const chars = Array.from(text)\n  const completeRef = React.useRef(onComplete)\n  React.useEffect(() => {\n    completeRef.current = onComplete\n  })\n\n  React.useEffect(() => {\n    if (trigger === \"mount\" || (trigger === \"inView\" && seen)) setRun((r) => r + 1)\n  }, [trigger, seen])\n\n  React.useEffect(() => {\n    if (!run) return\n    const total = (4 + chars.length * spread) * speed + 60\n    const id = setTimeout(() => completeRef.current?.(), total)\n    return () => clearTimeout(id)\n  }, [run, chars.length, spread, speed])\n\n  const hover = trigger === \"hover\"\n  return (\n    <span\n      ref={ref}\n      data-slot=\"hyper-text\"\n      tabIndex={hover ? 0 : undefined}\n      onPointerEnter={(e) => {\n        onPointerEnter?.(e)\n        if (hover) setRun((r) => r + 1)\n      }}\n      onFocus={(e) => {\n        onFocus?.(e)\n        if (hover) setRun((r) => r + 1)\n      }}\n      className={cn(\n        \"inline-flex flex-wrap items-center font-mono font-semibold tracking-tight\",\n        variant === \"tiles\" ? \"gap-[0.12em]\" : \"gap-0\",\n        hover && \"rounded-sm outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50\",\n        className\n      )}\n      {...props}\n    >\n      <span className=\"sr-only\">{text}</span>\n      <span aria-hidden=\"true\" className=\"inline-flex flex-wrap items-center gap-[inherit]\">\n        {chars.map((c, i) => (\n          <Flap key={i} target={c.toUpperCase()} run={run} steps={4 + i * spread} speed={speed} tile={variant === \"tiles\"} />\n        ))}\n      </span>\n    </span>\n  )\n}\n\nexport { HyperText, type HyperTextProps }\n",
      "type": "registry:ui",
      "target": "@components/ballmac/hyper-text.tsx"
    }
  ],
  "meta": {
    "schema": 1,
    "tier": "free",
    "tags": [
      "text",
      "flip",
      "split-flap",
      "hover",
      "headline"
    ],
    "version": "1.0.0",
    "updated": "2026-10-01",
    "ai": {
      "summary": "<HyperText trigger='mount|inView|hover' variant='plain|tiles' speed spread>TEXT</HyperText>. Text is uppercased. The hover trigger is focusable and replays on focus.",
      "whenToUse": [
        "Brand words, status boards, dates and counters",
        "Playful hover headings"
      ],
      "whenNotToUse": [
        "Anything that must stay readable mid-animation",
        "Long sentences"
      ],
      "composesWith": [
        "scramble-text",
        "number-ticker"
      ],
      "a11y": [
        {
          "keys": "Tab",
          "action": "Reaches the text when trigger is hover; focus replays the animation"
        },
        {
          "keys": "Screen readers",
          "action": "The real text is read once; the flipping characters are hidden"
        },
        {
          "keys": "Reduced motion",
          "action": "Characters appear settled"
        }
      ],
      "customization": [
        "variant",
        "trigger",
        "speed and spread",
        "onComplete"
      ]
    },
    "examples": [
      "hyper-text-demo",
      "hyper-text-hover"
    ],
    "url": "https://ui.ballmac.com/components/hyper-text"
  },
  "categories": [
    "text"
  ],
  "type": "registry:ui"
}