{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"wipe-menu","type":"registry:ui","title":"Wipe Menu","description":"Full-width menu rows; hovering one wipes in a marquee of text and images from the edge you entered.","dependencies":[],"registryDependencies":["utils"],"files":[{"path":"registry/ui/wipe-menu.tsx","type":"registry:ui","content":"\"use client\";\n\nimport { useRef, type PointerEvent } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nconst css = `\n@keyframes flowing-menu-scroll{to{transform:translate3d(-50%,0,0)}}\n.flowing-menu-track{animation:flowing-menu-scroll linear infinite;animation-play-state:paused}\n.flowing-menu-row:hover .flowing-menu-track,.flowing-menu-row:focus-visible .flowing-menu-track{animation-play-state:running}\n@media (prefers-reduced-motion:reduce){.flowing-menu-track{animation:none}}\n`;\n\nexport interface WipeMenuItem {\n  label: string;\n  href?: string;\n  image: string;\n}\n\nexport interface WipeMenuProps {\n  items: WipeMenuItem[];\n  /** Seconds for one marquee loop. */\n  speed?: number;\n  /** Background of the revealed band. */\n  bandColor?: string;\n  /** Text color inside the band. */\n  bandTextColor?: string;\n  className?: string;\n}\n\nconst EASE = \"cubic-bezier(.19,1,.22,1)\";\n\nfunction Row({ item, speed, bandColor, bandTextColor }: { item: WipeMenuItem } & Required<Pick<WipeMenuProps, \"speed\" | \"bandColor\" | \"bandTextColor\">>) {\n  const band = useRef<HTMLDivElement>(null);\n  const inner = useRef<HTMLDivElement>(null);\n\n  // Band and its content move in opposite directions, so the content looks\n  // pinned in place while a window slides over it: a wipe, not a slide.\n  const slide = (to: number) => {\n    const reduced = window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches;\n    for (const [el, sign] of [\n      [band.current!, 1],\n      [inner.current!, -1],\n    ] as const) {\n      const from = getComputedStyle(el).transform;\n      el.getAnimations().forEach((a) => a.cancel());\n      el.animate([{ transform: from === \"none\" ? \"none\" : from }, { transform: `translateY(${sign * to}%)` }], {\n        duration: reduced ? 0 : 650,\n        easing: EASE,\n        fill: \"forwards\",\n      });\n    }\n  };\n  const place = (edge: number) => {\n    band.current!.getAnimations().forEach((a) => a.cancel());\n    inner.current!.getAnimations().forEach((a) => a.cancel());\n    band.current!.style.transform = `translateY(${edge}%)`;\n    inner.current!.style.transform = `translateY(${-edge}%)`;\n  };\n  const edgeOf = (e: PointerEvent<HTMLElement>) => {\n    const r = e.currentTarget.getBoundingClientRect();\n    return e.clientY - r.top < r.height / 2 ? -101 : 101;\n  };\n\n  return (\n    <a\n      href={item.href ?? \"#\"}\n      onPointerEnter={(e) => {\n        place(edgeOf(e));\n        slide(0);\n      }}\n      onPointerLeave={(e) => slide(edgeOf(e))}\n      onFocus={() => {\n        place(-101);\n        slide(0);\n      }}\n      onBlur={() => slide(101)}\n      className=\"flowing-menu-row relative flex min-h-16 flex-1 items-center justify-center overflow-hidden border-t border-border text-3xl font-semibold uppercase tracking-tight text-foreground outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring sm:text-5xl\"\n    >\n      {item.label}\n      <div\n        ref={band}\n        aria-hidden\n        className=\"pointer-events-none absolute inset-0 overflow-hidden\"\n        style={{ background: bandColor, color: bandTextColor, transform: \"translateY(101%)\" }}\n      >\n        <div ref={inner} className=\"h-full\" style={{ transform: \"translateY(-101%)\" }}>\n          <div className=\"flowing-menu-track flex h-full w-max\" style={{ animationDuration: `${speed}s` }}>\n            {[0, 1].map((half) => (\n              <div key={half} className=\"flex h-full items-center\">\n                {Array.from({ length: 4 }, (_, k) => (\n                  <div key={k} className=\"flex h-full items-center\">\n                    <span className=\"whitespace-nowrap px-6\">{item.label}</span>\n                    <span\n                      className=\"h-[62%] w-[clamp(6rem,14vw,12rem)] rounded-full bg-cover bg-center\"\n                      style={{ backgroundImage: `url(\"${item.image}\")` }}\n                    />\n                  </div>\n                ))}\n              </div>\n            ))}\n          </div>\n        </div>\n      </div>\n    </a>\n  );\n}\n\nexport function WipeMenu({\n  items,\n  speed = 14,\n  bandColor = \"#d9f75c\",\n  bandTextColor = \"#0b0d06\",\n  className,\n}: WipeMenuProps) {\n  return (\n    <nav className={cn(\"flex h-full w-full flex-col border-b border-border\", className)}>\n      <style href=\"wipe-menu\" precedence=\"default\">\n        {css}\n      </style>\n      {items.map((item) => (\n        <Row key={item.label} item={item} speed={speed} bandColor={bandColor} bandTextColor={bandTextColor} />\n      ))}\n    </nav>\n  );\n}\n"}]}