{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "encrypted-card",
  "title": "Encrypted Card",
  "description": "A card whose surface hides a live stream of scrambling ciphertext, revealed only through a soft spotlight that follows the pointer.",
  "registryDependencies": ["@godui/godui-theme"],
  "files": [
    {
      "path": "packages/components/src/encrypted-card/encrypted-card.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nexport type EncryptedCardProps = React.HTMLAttributes<HTMLDivElement> & {\n  /** Pool of glyphs the encrypted stream is drawn from. */\n  characters?: string;\n  /** Milliseconds between glyph re-randomizations while hovered. */\n  speed?: number;\n  /** Radius of the reveal window that follows the pointer, in pixels. */\n  revealRadius?: number;\n  /** Color of the encrypted glyph stream. Accepts any CSS color. */\n  streamColor?: string;\n  /**\n   * Opacity of the revealed glyph stream, 0–1. Lower values make the ciphertext\n   * subtler. Defaults to 1 (fully opaque inside the reveal window).\n   */\n  streamOpacity?: number;\n};\n\n// A wide alphabet keeps the stream looking like ciphertext rather than a word.\nconst DEFAULT_CHARS =\n  \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789<>[]{}/*#$%&\";\n// Enough characters to flood any reasonably sized card.\nconst STREAM_LEN = 1500;\n\nconst ROOT_BASE =\n  \"group relative overflow-hidden rounded-xl border border-border bg-card text-card-foreground\";\n\n// The glyph layer, revealed only inside a soft radial mask centered on the\n// pointer (`--x` / `--y`, defaulting to center so the first paint isn't a corner\n// flash). `mask-image` is a single longhand so the shorthand-reset trap does not\n// apply here.\nconst STREAM_BASE =\n  \"pointer-events-none absolute inset-0 select-none break-all font-mono text-[11px] leading-[1.15] tracking-[0.15em] opacity-0 [transition:opacity_300ms_ease] group-hover:[opacity:var(--stream-opacity,1)] motion-reduce:[transition:none] [mask-image:radial-gradient(var(--reveal)_circle_at_var(--x,50%)_var(--y,50%),#000_0%,transparent_100%)] [-webkit-mask-image:radial-gradient(var(--reveal)_circle_at_var(--x,50%)_var(--y,50%),#000_0%,transparent_100%)]\";\n\nfunction randomString(length: number, chars: string): string {\n  let out = \"\";\n  for (let i = 0; i < length; i++) {\n    out += chars[Math.floor(Math.random() * chars.length)];\n  }\n  return out;\n}\n\nfunction usePrefersReducedMotion(): boolean {\n  const [reduced, setReduced] = React.useState(false);\n  React.useEffect(() => {\n    const mq = window.matchMedia(\"(prefers-reduced-motion: reduce)\");\n    const update = () => setReduced(mq.matches);\n    update();\n    mq.addEventListener(\"change\", update);\n    return () => mq.removeEventListener(\"change\", update);\n  }, []);\n  return reduced;\n}\n\nconst EncryptedCard = React.forwardRef<HTMLDivElement, EncryptedCardProps>(\n  (\n    {\n      characters = DEFAULT_CHARS,\n      speed = 55,\n      revealRadius = 130,\n      streamColor = \"var(--primary)\",\n      streamOpacity = 1,\n      className,\n      style,\n      children,\n      onPointerMove,\n      onPointerEnter,\n      onPointerLeave,\n      ...props\n    },\n    forwardedRef,\n  ) => {\n    const ref = React.useRef<HTMLDivElement>(null);\n    React.useImperativeHandle(\n      forwardedRef,\n      () => ref.current as HTMLDivElement,\n    );\n\n    const [stream, setStream] = React.useState(\"\");\n    const [hovered, setHovered] = React.useState(false);\n    const reduced = usePrefersReducedMotion();\n\n    // Fill on mount (client-only, so SSR markup stays deterministic) and, while\n    // hovered with motion enabled, re-randomize on an interval.\n    React.useEffect(() => {\n      setStream(randomString(STREAM_LEN, characters));\n      if (reduced || !hovered) return;\n      const id = window.setInterval(() => {\n        setStream(randomString(STREAM_LEN, characters));\n      }, speed);\n      return () => window.clearInterval(id);\n    }, [characters, speed, hovered, reduced]);\n\n    const handlePointerMove = (e: React.PointerEvent<HTMLDivElement>) => {\n      const el = ref.current;\n      if (el) {\n        const rect = el.getBoundingClientRect();\n        el.style.setProperty(\"--x\", `${e.clientX - rect.left}px`);\n        el.style.setProperty(\"--y\", `${e.clientY - rect.top}px`);\n      }\n      onPointerMove?.(e);\n    };\n\n    const handlePointerEnter = (e: React.PointerEvent<HTMLDivElement>) => {\n      setHovered(true);\n      onPointerEnter?.(e);\n    };\n\n    const handlePointerLeave = (e: React.PointerEvent<HTMLDivElement>) => {\n      setHovered(false);\n      onPointerLeave?.(e);\n    };\n\n    return (\n      <div\n        ref={ref}\n        data-slot=\"encrypted-card\"\n        onPointerMove={handlePointerMove}\n        onPointerEnter={handlePointerEnter}\n        onPointerLeave={handlePointerLeave}\n        className={`${ROOT_BASE} ${className ?? \"\"}`}\n        style={{\n          [\"--reveal\" as string]: `${revealRadius}px`,\n          [\"--stream-opacity\" as string]: streamOpacity,\n          ...style,\n        }}\n        {...props}\n      >\n        <div aria-hidden className={STREAM_BASE} style={{ color: streamColor }}>\n          {stream}\n        </div>\n        <div className=\"relative z-raised\">{children}</div>\n      </div>\n    );\n  },\n);\nEncryptedCard.displayName = \"EncryptedCard\";\n\nexport { EncryptedCard };\n",
      "type": "registry:ui",
      "target": "components/godui/encrypted-card.tsx"
    }
  ],
  "type": "registry:ui"
}
