{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"glare-hover","type":"registry:component","title":"Glare Hover","description":"A diagonal band of light sweeps across a card or button on hover. Color, angle and duration props; touch devices skip it, reduced-motion swaps the sweep for a soft opacity highlight.","author":"UI Lab","dependencies":["clsx","motion","tailwind-merge"],"registryDependencies":[],"files":[{"path":"components/motion/glare-hover.tsx","type":"registry:component","target":"@components/motion/glare-hover.tsx","content":"// ui-lab-ten.vercel.app/components/motion/glare-hover\n// Ported from motion-anything (nexu-io, Apache-2.0); upstream: reactbits.dev \"Glare Hover\", redistributed with permission.\n\n\"use client\";\n\nimport { motion, useReducedMotion } from \"motion/react\";\nimport { useState, type ReactNode } from \"react\";\nimport { EASE_OUT_CSS } from \"@/lib/ease\";\nimport { useHoverCapable } from \"@/lib/hooks/use-hover-capable\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface GlareHoverProps {\n  /** Content the glare sweeps across. Give it its own opaque background (card, button, image, …) — the wrapper only paints the glare layer. */\n  children: ReactNode;\n  className?: string;\n  /** Color of the light band. Defaults to a soft translucent white. */\n  color?: string;\n  /** Angle of the sweeping band, in degrees. */\n  angle?: number;\n  /** Duration of one sweep, in ms. */\n  duration?: number;\n  /** Play the sweep once on first hover instead of replaying on every hover. */\n  playOnce?: boolean;\n}\n\n/**\n * A diagonal light band sweeps across an element's surface on hover — a\n * premium, tactile sheen for a card or button. Pure CSS: a skewed gradient\n * layer sits above the content and translates across on hover via a\n * transform transition.\n */\nexport function GlareHover({\n  children,\n  className,\n  color = \"rgba(255, 255, 255, 0.55)\",\n  angle = -30,\n  duration = 550,\n  playOnce = false,\n}: GlareHoverProps) {\n  const reduce = useReducedMotion();\n  const canHover = useHoverCapable();\n  // Decorative hover sheen: skip entirely on touch (phantom hover sticks after tap).\n  const enabled = canHover;\n\n  const [hovered, setHovered] = useState(false);\n  const [played, setPlayed] = useState(false);\n\n  const active = enabled && (hovered || (playOnce && played));\n\n  const handleEnter = () => {\n    if (!enabled) return;\n    setHovered(true);\n    if (playOnce) setPlayed(true);\n  };\n\n  const handleLeave = () => {\n    if (!enabled) return;\n    setHovered(false);\n  };\n\n  return (\n    <motion.div\n      onMouseEnter={handleEnter}\n      onMouseLeave={handleLeave}\n      className={cn(\"relative isolate overflow-hidden\", className)}\n    >\n      {children}\n      {enabled ? (\n        <span\n          aria-hidden\n          className=\"pointer-events-none absolute inset-[-50%] will-change-[transform,opacity]\"\n          style={\n            reduce\n              ? {\n                  background: color,\n                  opacity: active ? 0.35 : 0,\n                  transitionProperty: \"opacity\",\n                  transitionDuration: `${duration}ms`,\n                  transitionTimingFunction: EASE_OUT_CSS,\n                }\n              : {\n                  background: `linear-gradient(${angle}deg, transparent 40%, ${color} 50%, transparent 60%)`,\n                  transform: active ? \"translateX(150%)\" : \"translateX(-150%)\",\n                  transitionProperty: \"transform\",\n                  transitionDuration: `${duration}ms`,\n                  transitionTimingFunction: EASE_OUT_CSS,\n                }\n          }\n        />\n      ) : null}\n    </motion.div>\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"},{"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"}]}