{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"not-found-terminal","type":"registry:block","title":"404 / Not Found Terminal","description":"A terminal window that types a failed cd command and a 404 status, with a blinking caret.","author":"Saurabh <saurabh10102@gmail.com>","dependencies":["clsx","motion","tailwind-merge"],"registryDependencies":[],"files":[{"path":"components/motion/not-found/terminal.tsx","type":"registry:component","target":"@components/motion/not-found/terminal.tsx","content":"\"use client\";\n// beui.dev/components/blocks/not-found\n\nimport { TextReveal } from \"@/components/motion/text-reveal\";\nimport {\n  NOT_FOUND_DEFAULTS,\n  NotFoundActions,\n  NotFoundStage,\n  type NotFoundProps,\n} from \"./shared\";\n\nconst TYPE_SPRING = { stiffness: 320, damping: 30, mass: 0.6 };\n\nexport function NotFoundTerminal({\n  className,\n  code = NOT_FOUND_DEFAULTS.code,\n  title = NOT_FOUND_DEFAULTS.title,\n  description = NOT_FOUND_DEFAULTS.description,\n  homeHref,\n  homeLabel,\n  browseHref,\n  browseLabel,\n}: NotFoundProps) {\n  return (\n    <NotFoundStage className={className}>\n      <div className=\"w-full max-w-md overflow-hidden rounded-xl border border-border bg-neutral-950 text-left shadow-lg\">\n        <div className=\"flex items-center gap-1.5 border-b border-white/10 px-4 py-3\">\n          <span className=\"h-3 w-3 rounded-full bg-[#ff5f57]\" />\n          <span className=\"h-3 w-3 rounded-full bg-[#febc2e]\" />\n          <span className=\"h-3 w-3 rounded-full bg-[#28c840]\" />\n          <span className=\"ml-2 text-xs text-white/40\">~/beui</span>\n        </div>\n        <div className=\"space-y-1.5 p-4 font-mono text-sm leading-relaxed\">\n          <TextReveal\n            as=\"p\"\n            split=\"char\"\n            stagger={0.018}\n            blur={6}\n            yOffset={0}\n            spring={TYPE_SPRING}\n            className=\"text-white/80\"\n            text=\"$ cd /page\"\n          />\n          <TextReveal\n            as=\"p\"\n            split=\"char\"\n            stagger={0.012}\n            delay={0.45}\n            blur={6}\n            yOffset={0}\n            spring={TYPE_SPRING}\n            className=\"text-[#ff5f57]\"\n            text=\"cd: no such file or directory: /page\"\n          />\n          <p className=\"flex items-center text-white/80\">\n            <TextReveal\n              as=\"span\"\n              split=\"char\"\n              stagger={0.018}\n              delay={1.1}\n              blur={6}\n              yOffset={0}\n              spring={TYPE_SPRING}\n              text={`$ status ${code}`}\n            />\n            <span className=\"ml-1 inline-block h-[1.1em] w-[0.55ch] translate-y-[0.12em] bg-white/80 motion-safe:animate-pulse\" />\n          </p>\n        </div>\n      </div>\n\n      <div className=\"flex flex-col items-center gap-2\">\n        <p className=\"text-lg font-semibold text-foreground\">{title}</p>\n        <p className=\"max-w-sm text-sm text-muted-foreground\">{description}</p>\n      </div>\n\n      <NotFoundActions\n        homeHref={homeHref}\n        homeLabel={homeLabel}\n        browseHref={browseHref}\n        browseLabel={browseLabel}\n      />\n    </NotFoundStage>\n  );\n}\n"},{"path":"components/motion/not-found/shared.tsx","type":"registry:component","target":"@components/motion/not-found/shared.tsx","content":"\"use client\";\n\nimport { motion, useReducedMotion } from \"motion/react\";\nimport { SPRING_PRESS } from \"@/lib/ease\";\nimport { useHoverCapable } from \"@/lib/hooks/use-hover-capable\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface NotFoundProps {\n  className?: string;\n  /** The big status code. */\n  code?: string;\n  title?: string;\n  description?: string;\n  homeHref?: string;\n  homeLabel?: string;\n  browseHref?: string;\n  browseLabel?: string;\n}\n\nexport const NOT_FOUND_DEFAULTS = {\n  code: \"404\",\n  title: \"Page not found\",\n  description:\n    \"The page you are looking for moved, vanished, or never existed.\",\n  homeHref: \"/\",\n  homeLabel: \"Back home\",\n  browseHref: \"/components/motion\",\n  browseLabel: \"Browse components\",\n} as const;\n\ntype ActionsProps = Pick<\n  NotFoundProps,\n  \"homeHref\" | \"homeLabel\" | \"browseHref\" | \"browseLabel\" | \"className\"\n>;\n\n/** The shared dual CTA: a primary \"Back home\" and a secondary \"Browse\". */\nexport function NotFoundActions({\n  homeHref = NOT_FOUND_DEFAULTS.homeHref,\n  homeLabel = NOT_FOUND_DEFAULTS.homeLabel,\n  browseHref = NOT_FOUND_DEFAULTS.browseHref,\n  browseLabel = NOT_FOUND_DEFAULTS.browseLabel,\n  className,\n}: ActionsProps) {\n  const reduce = useReducedMotion();\n  const canHover = useHoverCapable();\n  const whileTap = reduce ? undefined : { scale: 0.96 };\n  const whileHover = reduce || !canHover ? undefined : { scale: 1.02 };\n\n  return (\n    <div\n      className={cn(\n        \"flex flex-wrap items-center justify-center gap-3\",\n        className,\n      )}\n    >\n      <motion.a\n        href={homeHref}\n        whileTap={whileTap}\n        whileHover={whileHover}\n        transition={SPRING_PRESS}\n        className=\"inline-flex h-11 select-none items-center justify-center rounded-full bg-primary px-6 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90\"\n      >\n        {homeLabel}\n      </motion.a>\n      <motion.a\n        href={browseHref}\n        whileTap={whileTap}\n        whileHover={whileHover}\n        transition={SPRING_PRESS}\n        className=\"inline-flex h-11 select-none items-center justify-center rounded-full border border-border bg-card px-6 text-sm font-medium text-foreground transition-colors hover:bg-primary/5\"\n      >\n        {browseLabel}\n      </motion.a>\n    </div>\n  );\n}\n\n/** Centers a variant and gives it a consistent minimum stage height. */\nexport function NotFoundStage({\n  className,\n  children,\n}: {\n  className?: string;\n  children: React.ReactNode;\n}) {\n  return (\n    <div\n      className={cn(\n        \"flex min-h-[420px] w-full flex-col items-center justify-center gap-8 px-4 text-center\",\n        className,\n      )}\n    >\n      {children}\n    </div>\n  );\n}\n"},{"path":"components/motion/text-reveal.tsx","type":"registry:component","target":"@components/motion/text-reveal.tsx","content":"\"use client\";\n\nimport { motion, type Transition, useInView, useReducedMotion } from \"motion/react\";\nimport { useRef, type ElementType, type ReactNode } from \"react\";\nimport { EASE_OUT } from \"@/lib/ease\";\nimport { cn } from \"@/lib/utils\";\n\ntype SplitMode = \"word\" | \"char\";\n\nexport interface TextRevealProps {\n  text: string | string[];\n  as?: ElementType;\n  className?: string;\n  split?: SplitMode;\n  stagger?: number;\n  delay?: number;\n  blur?: number;\n  yOffset?: string | number;\n  spring?: { stiffness?: number; damping?: number; mass?: number };\n  once?: boolean;\n  whileInView?: boolean;\n  children?: ReactNode;\n}\n\nconst DEFAULT_SPRING = { stiffness: 140, damping: 26, mass: 1.2 };\n\nexport function TextReveal({\n  text,\n  as: Comp = \"span\",\n  className,\n  split = \"word\",\n  stagger = 0.09,\n  delay = 0,\n  blur = 12,\n  yOffset = \"40%\",\n  spring,\n  once = true,\n  whileInView = false,\n  children,\n}: TextRevealProps) {\n  const ref = useRef<HTMLElement>(null);\n  const inView = useInView(ref, { once, amount: 0.4 });\n  const reduce = useReducedMotion();\n  const shouldAnimate = whileInView ? inView : true;\n\n  const lines = Array.isArray(text) ? text : [text];\n  const s = { ...DEFAULT_SPRING, ...spring };\n\n  let unitIndex = 0;\n  const lineCounts = new Map<string, number>();\n\n  return (\n    <Comp ref={ref} className={cn(\"block\", className)}>\n      {lines.map((line) => {\n        const units = split === \"word\" ? line.split(\" \") : Array.from(line);\n        const lineCount = lineCounts.get(line) ?? 0;\n        lineCounts.set(line, lineCount + 1);\n        const lineKey = `${line}-${lineCount}`;\n        const unitCounts = new Map<string, number>();\n\n        return (\n          <span key={lineKey} className=\"block\">\n            {units.map((unit, i) => {\n              const d = delay + unitIndex * stagger;\n              unitIndex += 1;\n              const unitCount = unitCounts.get(unit) ?? 0;\n              unitCounts.set(unit, unitCount + 1);\n              const unitKey = `${unit}-${unitCount}`;\n              const initial = reduce\n                ? { opacity: 0 }\n                : { y: yOffset, opacity: 0, filter: `blur(${blur}px)` };\n              const animate = shouldAnimate\n                ? reduce\n                  ? { opacity: 1 }\n                  : { y: 0, opacity: 1, filter: \"blur(0px)\" }\n                : initial;\n              const transition: Transition = reduce\n                ? { opacity: { duration: 0.25, ease: EASE_OUT, delay: d * 0.3 } }\n                : {\n                    y: { type: \"spring\" as const, ...s, delay: d },\n                    opacity: { duration: 0.7, ease: EASE_OUT, delay: d },\n                    filter: { duration: 0.9, ease: EASE_OUT, delay: d },\n                  };\n              return (\n                <motion.span\n                  key={unitKey}\n                  initial={initial}\n                  animate={animate}\n                  transition={transition}\n                  className=\"inline-block will-change-transform\"\n                >\n                  {unit}\n                  {split === \"word\" && i < units.length - 1 ? (\n                    <span className=\"inline-block\">&nbsp;</span>\n                  ) : null}\n                </motion.span>\n              );\n            })}\n          </span>\n        );\n      })}\n      {children}\n    </Comp>\n  );\n}\n"},{"path":"lib/ease.ts","type":"registry:lib","target":"@lib/ease.ts","content":"// Shared motion tokens. Easing curves mirror the CSS custom properties in\n// globals.css; springs are the canonical physics used across components.\n// Strong custom variants — defaults like `ease-in`/`ease-out` feel weak.\n\nexport const EASE_OUT = [0.16, 1, 0.3, 1] as const;\nexport const EASE_IN_OUT = [0.77, 0, 0.175, 1] as const;\nexport const EASE_DRAWER = [0.32, 0.72, 0, 1] as const;\n\n/** CSS string form of EASE_OUT for inline style transitions. */\nexport const EASE_OUT_CSS = \"cubic-bezier(0.16, 1, 0.3, 1)\";\n\n/** Press feedback on buttons and other tappable surfaces. */\nexport const SPRING_PRESS = {\n  type: \"spring\",\n  stiffness: 500,\n  damping: 30,\n  mass: 0.6,\n} as const;\n\n/** Content swaps — label/icon slots trading places inside a control. */\nexport const SPRING_SWAP = {\n  type: \"spring\",\n  stiffness: 460,\n  damping: 30,\n  mass: 0.55,\n} as const;\n\n/** Overlay panel entrances — modals and sheets summoned by pointer. */\nexport const SPRING_PANEL = {\n  type: \"spring\",\n  stiffness: 420,\n  damping: 40,\n  mass: 0.5,\n} as const;\n\n/** Shared-layout glides — pills, indicators and panels morphing between positions. */\nexport const SPRING_LAYOUT = {\n  type: \"spring\",\n  stiffness: 360,\n  damping: 32,\n  mass: 0.6,\n} as const;\n\n/** Cursor-follow physics for decorative mouse tracking (magnetic, tilt, dock). */\nexport const SPRING_MOUSE = {\n  stiffness: 200,\n  damping: 15,\n  mass: 0.3,\n} as const;\n\n/** Dragged handles and fills (sliders) — critically damped `useSpring` config,\n * so the value follows the pointer butterily and never rebounds off an end. */\nexport const SPRING_GLIDE = {\n  stiffness: 700,\n  damping: 50,\n  mass: 0.5,\n} as const;\n"},{"path":"lib/hooks/use-hover-capable.ts","type":"registry:hook","target":"@lib/hooks/use-hover-capable.ts","content":"\"use client\";\n\nimport { useEffect, useState } from \"react\";\n\n/**\n * Returns true only on devices that have a true hover (mouse / trackpad).\n * Touch devices fire phantom `:hover` on tap that sticks until tap-elsewhere\n * — gate hover-only effects (scale lifts, magnetic pulls) behind this.\n */\nexport function useHoverCapable() {\n  const [canHover, setCanHover] = useState(false);\n\n  useEffect(() => {\n    if (typeof window === \"undefined\" || !window.matchMedia) return;\n    const mq = window.matchMedia(\"(hover: hover) and (pointer: fine)\");\n    const update = () => setCanHover(mq.matches);\n    update();\n    mq.addEventListener?.(\"change\", update);\n    return () => mq.removeEventListener?.(\"change\", update);\n  }, []);\n\n  return canHover;\n}\n"},{"path":"lib/utils.ts","type":"registry:lib","target":"@lib/utils.ts","content":"import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"}]}