{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "direction-aware-hover",
  "title": "Direction Aware Hover",
  "description": "Overlay enters from the edge the cursor actually crossed.",
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "src/components/velora/direction-aware-hover.tsx",
      "content": "\"use client\";\n\nimport { useRef, useState } from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\ntype Edge = \"top\" | \"right\" | \"bottom\" | \"left\";\n\nconst ENTER: Record<Edge, string> = {\n  top: \"-translate-y-full\",\n  right: \"translate-x-full\",\n  bottom: \"translate-y-full\",\n  left: \"-translate-x-full\",\n};\n\ninterface DirectionAwareHoverProps {\n  children: React.ReactNode;\n  /** Slides in from whichever edge the cursor crossed */\n  overlay: React.ReactNode;\n  className?: string;\n}\n\n/**\n * Overlay that enters from the edge the cursor actually crossed and leaves the\n * same way. Zero dependencies — the maths is one arctangent.\n */\nexport function DirectionAwareHover({\n  children,\n  overlay,\n  className,\n}: DirectionAwareHoverProps) {\n  const ref = useRef<HTMLDivElement>(null);\n  const [edge, setEdge] = useState<Edge>(\"bottom\");\n  const [hovered, setHovered] = useState(false);\n\n  const edgeFrom = (event: React.MouseEvent<HTMLDivElement>): Edge => {\n    const rect = ref.current?.getBoundingClientRect();\n    if (!rect) return \"bottom\";\n    // Angle from the centre, corrected for aspect ratio, mapped to quadrants.\n    const x = (event.clientX - rect.left - rect.width / 2) * (rect.height / rect.width);\n    const y = event.clientY - rect.top - rect.height / 2;\n    const quadrant = Math.round(Math.atan2(y, x) / (Math.PI / 2) + 4) % 4;\n    return ([\"right\", \"bottom\", \"left\", \"top\"] as const)[quadrant];\n  };\n\n  return (\n    <div\n      ref={ref}\n      // Focusable so keyboard users can reveal the overlay too.\n      tabIndex={0}\n      data-slot=\"direction-aware-hover\"\n      onPointerEnter={(event) => {\n        if (event.pointerType !== \"mouse\") return;\n        setEdge(edgeFrom(event));\n        setHovered(true);\n      }}\n      onPointerLeave={(event) => {\n        if (event.pointerType !== \"mouse\") return;\n        setEdge(edgeFrom(event));\n        setHovered(false);\n      }}\n      // Touch and pen have no hover: a tap toggles the overlay instead.\n      onPointerUp={(event) => {\n        if (event.pointerType !== \"mouse\") setHovered((value) => !value);\n      }}\n      onFocus={(event) => {\n        // Keyboard focus only; a tap is already handled by onPointerUp.\n        if ((event.target as HTMLElement).matches(\":focus-visible\")) {\n          setHovered(true);\n        }\n      }}\n      onBlur={(event) => {\n        if (!event.currentTarget.contains(event.relatedTarget)) {\n          setHovered(false);\n        }\n      }}\n      className={cn(\n        \"relative overflow-hidden rounded-2xl border bg-card focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring\",\n        className\n      )}\n    >\n      {children}\n      <div\n        className={cn(\n          \"absolute inset-0 flex items-center justify-center bg-gradient-to-br from-brand-from to-brand-to text-brand-foreground transition-transform duration-300 ease-out motion-reduce:transition-none\",\n          hovered ? \"translate-x-0 translate-y-0\" : ENTER[edge]\n        )}\n      >\n        {overlay}\n      </div>\n    </div>\n  );\n}\n",
      "type": "registry:ui",
      "target": "components/velora/direction-aware-hover.tsx"
    }
  ],
  "cssVars": {
    "theme": {
      "color-brand": "var(--brand)",
      "color-brand-from": "var(--brand-from)",
      "color-brand-via": "var(--brand-via)",
      "color-brand-to": "var(--brand-to)"
    },
    "light": {
      "brand": "oklch(0.546 0.245 263)",
      "brand-from": "oklch(0.546 0.245 263)",
      "brand-via": "oklch(0.623 0.214 260)",
      "brand-to": "oklch(0.685 0.169 237)"
    },
    "dark": {
      "brand": "oklch(0.623 0.214 260)",
      "brand-from": "oklch(0.623 0.214 260)",
      "brand-via": "oklch(0.707 0.165 255)",
      "brand-to": "oklch(0.746 0.16 233)"
    }
  },
  "type": "registry:ui"
}