{
  "name": "teletype-clock-default",
  "type": "registry:block",
  "tier": "free",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "https://www.craftui.space/r/craftui-base.json"
  ],
  "files": [
    {
      "path": "components/craftui/blocks/teletype-clock/default.tsx",
      "content": "\"use client\";\n\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\";\nimport { useEffect, useRef, useState } from \"react\";\n\n// Split-flap physics: the top half falls first, hinged on the center seam;\n// only once it's fully down does the bottom half swing up to meet it and\n// bounce-settle — fast and snappy, like a real mechanical flap.\nconst FALL_DURATION = 0.14;\nconst LAND_DURATION = 0.16;\nconst FALL_EASE = [0.55, 0.055, 0.675, 0.19] as const; // ease-in-cubic (gravity)\n\ninterface TeletypeClockProps {\n  /** Advance the clock by one minute on an interval so flips happen constantly */\n  demo?: boolean;\n  /** Milliseconds between demo flips */\n  demoIntervalMs?: number;\n  /** 12-hour format instead of 24-hour */\n  hour12?: boolean;\n  className?: string;\n}\n\nfunction formatDigits(date: Date, hour12: boolean): string[] {\n  let hours = date.getHours();\n  if (hour12) {\n    hours = hours % 12 || 12;\n  }\n  const hh = String(hours).padStart(2, \"0\");\n  const mm = String(date.getMinutes()).padStart(2, \"0\");\n  return [...hh, ...mm];\n}\n\n/** Full-height numeral clipped to one half of the card by an overflow-hidden window */\nfunction DigitHalf({ digit, half }: { digit: string; half: \"top\" | \"bottom\" }) {\n  return (\n    <div\n      className={`absolute left-0 flex h-[200%] w-full items-center justify-center font-bold tabular-nums ${\n        half === \"top\"\n          ? \"top-0 text-neutral-500 dark:text-neutral-400\"\n          : \"bottom-0 text-neutral-800 dark:text-neutral-100\"\n      }`}\n    >\n      {digit}\n    </div>\n  );\n}\n\nfunction FlipDigit({ digit }: { digit: string }) {\n  const reduceMotion = useReducedMotion();\n  const [current, setCurrent] = useState(digit);\n  const isFlipping = digit !== current && !reduceMotion;\n  const flipId = `${current}-${digit}`;\n\n  useEffect(() => {\n    if (reduceMotion) setCurrent(digit);\n  }, [digit, reduceMotion]);\n\n  return (\n    <div\n      className=\"relative h-48 w-40 text-8xl sm:h-24 sm:w-16 sm:text-6xl md:h-28 md:w-20 md:text-7xl\"\n      style={{ perspective: \"350px\" }}\n    >\n      {/* Static top half — the digit being revealed as the flap falls */}\n      <div className=\"absolute inset-x-0 top-0 h-1/2 overflow-hidden rounded-t-lg bg-neutral-200 dark:bg-neutral-800\">\n        <DigitHalf digit={isFlipping ? digit : current} half=\"top\" />\n      </div>\n\n      {/* Static bottom half — keeps showing the old digit until the flap lands */}\n      <div className=\"absolute inset-x-0 bottom-0 h-1/2 overflow-hidden rounded-b-lg bg-neutral-100 dark:bg-[#1f1f1f]\">\n        <DigitHalf digit={current} half=\"bottom\" />\n      </div>\n\n      <AnimatePresence>\n        {isFlipping && (\n          <motion.div key={flipId} className=\"contents\">\n            {/* Falling top flap — old digit, hinged on the center seam */}\n            <motion.div\n              className=\"absolute inset-x-0 top-0 z-20 h-1/2 overflow-hidden rounded-t-lg bg-neutral-200 dark:bg-neutral-800\"\n              style={{\n                transformOrigin: \"bottom\",\n                backfaceVisibility: \"hidden\",\n              }}\n              initial={{ rotateX: 0 }}\n              animate={{ rotateX: -90 }}\n              exit={{ rotateX: -90 }}\n              transition={{ duration: FALL_DURATION, ease: FALL_EASE }}\n            >\n              <DigitHalf digit={current} half=\"top\" />\n              {/* Darkens as it turns away from the light */}\n              <motion.div\n                className=\"absolute inset-0 bg-black\"\n                initial={{ opacity: 0 }}\n                animate={{ opacity: 0.55 }}\n                transition={{ duration: FALL_DURATION, ease: FALL_EASE }}\n              />\n            </motion.div>\n\n            {/* Landing bottom flap — new digit, waits for the fall to finish, then hits 0°, bounces, settles */}\n            <motion.div\n              className=\"absolute inset-x-0 bottom-0 z-20 h-1/2 overflow-hidden rounded-b-lg bg-neutral-100 dark:bg-[#1f1f1f]\"\n              style={{\n                transformOrigin: \"top\",\n                backfaceVisibility: \"hidden\",\n              }}\n              initial={{ rotateX: 90 }}\n              animate={{ rotateX: [90, 0, -8, 0] }}\n              exit={{ rotateX: 0 }}\n              transition={{\n                delay: FALL_DURATION,\n                duration: LAND_DURATION,\n                times: [0, 0.6, 0.8, 1],\n                ease: [\"easeIn\", \"easeOut\", \"easeInOut\"],\n              }}\n              onAnimationComplete={() => setCurrent(digit)}\n            >\n              <DigitHalf digit={digit} half=\"bottom\" />\n              {/* Catches light as it swings toward the viewer */}\n              <motion.div\n                className=\"absolute inset-0 bg-black\"\n                initial={{ opacity: 0.45 }}\n                animate={{ opacity: 0 }}\n                transition={{ delay: FALL_DURATION, duration: LAND_DURATION }}\n              />\n            </motion.div>\n          </motion.div>\n        )}\n      </AnimatePresence>\n\n      {/* Center seam */}\n      <div className=\"absolute inset-x-0 top-1/2 z-30 h-[2px] -translate-y-1/2 bg-black/10 dark:bg-black/70\" />\n    </div>\n  );\n}\n\nexport default function TeletypeClock({\n  demo = false,\n  demoIntervalMs = 3000,\n  hour12 = false,\n  className = \"\",\n}: TeletypeClockProps) {\n  // Starts at 00:00 (matches the server render), then flips to the real time\n  // on mount — doubles as an entrance animation.\n  const [digits, setDigits] = useState([\"0\", \"0\", \"0\", \"0\"]);\n  const demoOffsetRef = useRef(0);\n\n  useEffect(() => {\n    const update = () => {\n      const now = new Date();\n      now.setMinutes(now.getMinutes() + demoOffsetRef.current);\n      setDigits(formatDigits(now, hour12));\n    };\n    update();\n\n    const id = setInterval(\n      () => {\n        if (demo) demoOffsetRef.current += 1;\n        update();\n      },\n      demo ? demoIntervalMs : 1000,\n    );\n    return () => clearInterval(id);\n  }, [demo, demoIntervalMs, hour12]);\n\n  return (\n    <div className={`flex items-center gap-4 sm:gap-6 ${className}`}>\n      <div className=\"flex gap-1.5\">\n        <FlipDigit digit={digits[0]} />\n        <FlipDigit digit={digits[1]} />\n      </div>\n      <div className=\"flex gap-1.5\">\n        <FlipDigit digit={digits[2]} />\n        <FlipDigit digit={digits[3]} />\n      </div>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "~/components/craftui/blocks/teletype-clock/default.tsx"
    }
  ],
  "meta": {
    "displayName": "Teletype Clock - Split-Flap Clock",
    "description": "Four HH:MM digit cards. On change, the old digit's top half hinges down on the center seam (ease-in free fall), then the new digit's bottom half swings into place with an overshoot-and-settle bounce. Shading overlays darken the falling flap and light up the landing one for a 3D feel.",
    "category": "number-animation",
    "tags": [
      "clock",
      "flip",
      "split-flap",
      "time",
      "3d",
      "loop",
      "retro"
    ],
    "animationType": "loop"
  }
}