{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "exit-intent-popup",
  "title": "Exit Intent Popup",
  "description": "Exit-intent dialog with editable CONTENT and storage keys.",
  "dependencies": [
    "lucide-react",
    "next"
  ],
  "files": [
    {
      "path": "registry/default/blocks/exit-intent-popup.tsx",
      "content": "\"use client\"\n\nimport { X } from \"lucide-react\"\nimport Link from \"next/link\"\nimport { useCallback, useEffect, useRef, useState } from \"react\"\n\n/**\n * Edit CONTENT to change exit-intent copy and storage keys.\n */\nconst CONTENT = {\n  stamp: \"Before you go\",\n  title: \"Free 7-day MVP scoping\",\n  body: \"Not sure what to build first? Book a no-pressure scoping call - we'll map scope, timeline, and budget in one session.\",\n  primaryLabel: \"Start free scoping\",\n  primaryHref: \"/scope\",\n  secondaryLabel: \"Maybe later\",\n  storageKey: \"atroui_exit_intent_v1\",\n  sessionKey: \"atroui_exit_intent_session\",\n}\n\nfunction shouldShowPopup(): boolean {\n  if (typeof window === \"undefined\") return false\n  if (localStorage.getItem(CONTENT.storageKey)) return false\n  if (sessionStorage.getItem(CONTENT.sessionKey)) return false\n  return true\n}\n\nfunction markShown() {\n  sessionStorage.setItem(CONTENT.sessionKey, \"1\")\n}\n\nfunction markDismissed() {\n  localStorage.setItem(CONTENT.storageKey, \"dismissed\")\n  sessionStorage.setItem(CONTENT.sessionKey, \"1\")\n}\n\nexport type ExitIntentPopupProps = {\n  /** Docs / demos: open immediately and render inline. */\n  preview?: boolean\n}\n\nexport function ExitIntentPopup({ preview = false }: ExitIntentPopupProps) {\n  const [open, setOpen] = useState(preview)\n  const dialogRef = useRef<HTMLDivElement>(null)\n  const triggered = useRef(false)\n\n  const close = useCallback(() => {\n    setOpen(false)\n    if (!preview) markDismissed()\n  }, [preview])\n\n  useEffect(() => {\n    if (preview) return\n    if (!shouldShowPopup()) return\n\n    const onMouseLeave = (e: MouseEvent) => {\n      if (triggered.current) return\n      if (e.clientY > 12) return\n      if (!shouldShowPopup()) return\n      triggered.current = true\n      markShown()\n      setOpen(true)\n    }\n\n    document.documentElement.addEventListener(\"mouseleave\", onMouseLeave)\n    return () =>\n      document.documentElement.removeEventListener(\"mouseleave\", onMouseLeave)\n  }, [preview])\n\n  useEffect(() => {\n    if (!open || preview) return\n\n    const onKeyDown = (e: KeyboardEvent) => {\n      if (e.key === \"Escape\") close()\n    }\n\n    document.addEventListener(\"keydown\", onKeyDown)\n    dialogRef.current?.focus()\n\n    const focusable = dialogRef.current?.querySelectorAll<HTMLElement>(\n      'a[href], button:not([disabled]), [tabindex]:not([tabindex=\"-1\"])',\n    )\n    const first = focusable?.[0]\n    const last = focusable?.[focusable.length - 1]\n\n    const trapFocus = (e: KeyboardEvent) => {\n      if (e.key !== \"Tab\" || !first || !last) return\n      if (e.shiftKey && document.activeElement === first) {\n        e.preventDefault()\n        last.focus()\n      } else if (!e.shiftKey && document.activeElement === last) {\n        e.preventDefault()\n        first.focus()\n      }\n    }\n\n    document.addEventListener(\"keydown\", trapFocus)\n    return () => {\n      document.removeEventListener(\"keydown\", onKeyDown)\n      document.removeEventListener(\"keydown\", trapFocus)\n    }\n  }, [open, close, preview])\n\n  if (!open) return null\n\n  return (\n    <div\n      className={\n        preview\n          ? \"relative flex w-full items-center justify-center p-2\"\n          : \"fixed inset-0 z-90 flex items-center justify-center p-4\"\n      }\n      role=\"presentation\"\n    >\n      {!preview ? (\n        <button\n          type=\"button\"\n          aria-label=\"Close dialog\"\n          className=\"absolute inset-0 bg-background/70 backdrop-blur-sm\"\n          onClick={close}\n        />\n      ) : (\n        <div aria-hidden className=\"absolute inset-0 rounded-lg bg-muted/40\" />\n      )}\n      <div\n        ref={dialogRef}\n        role=\"dialog\"\n        aria-modal={!preview}\n        aria-labelledby=\"exit-intent-title\"\n        tabIndex={-1}\n        className=\"relative z-10 w-full max-w-md border border-border-subtle bg-background p-6 shadow-[0_24px_64px_-28px_rgba(0,0,0,0.4)] sm:p-7\"\n      >\n        <button\n          type=\"button\"\n          onClick={close}\n          aria-label=\"Dismiss\"\n          className=\"absolute top-4 right-4 inline-flex size-9 items-center justify-center border border-border-subtle text-muted-foreground hover:bg-muted hover:text-foreground\"\n        >\n          <X className=\"size-4\" />\n        </button>\n        <p className=\"text-[11px] font-semibold tracking-[0.14em] text-muted-foreground uppercase\">\n          {CONTENT.stamp}\n        </p>\n        <h2\n          id=\"exit-intent-title\"\n          className=\"mt-4 text-2xl font-medium text-foreground\"\n        >\n          {CONTENT.title}\n        </h2>\n        <p className=\"mt-3 text-sm leading-relaxed text-muted-foreground\">\n          {CONTENT.body}\n        </p>\n        <div className=\"mt-6 flex flex-col gap-3 sm:flex-row\">\n          <Link\n            href={CONTENT.primaryHref}\n            onClick={() => {\n              if (preview) return\n              localStorage.setItem(CONTENT.storageKey, \"converted\")\n              sessionStorage.setItem(CONTENT.sessionKey, \"1\")\n            }}\n            className=\"inline-flex h-11 flex-1 items-center justify-center rounded-lg bg-foreground px-4 text-sm font-medium text-background\"\n          >\n            {CONTENT.primaryLabel}\n          </Link>\n          <button\n            type=\"button\"\n            onClick={close}\n            className=\"inline-flex h-11 flex-1 items-center justify-center rounded-lg border border-border-subtle px-4 text-sm font-medium\"\n          >\n            {CONTENT.secondaryLabel}\n          </button>\n        </div>\n      </div>\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/blocks/exit-intent-popup.tsx"
    }
  ],
  "type": "registry:block"
}