{
  "name": "directional-menu",
  "type": "registry:component",
  "title": "Directional Menu",
  "description": "Mega menu with directional content transitions using GSAP",
  "dependencies": [
    "gsap",
    "lucide-react"
  ],
  "registryDependencies": [],
  "exportName": "DirectionalMenu",
  "exportKind": "default",
  "tier": "free",
  "version": "1.1.0",
  "changelog": [
    {
      "version": "1.1.0",
      "date": "2026-07-21",
      "summary": "Added prefers-reduced-motion support: mega menu open/close panel animations use a reduced-motion-safe fade instead of the full height/slide tween",
      "breaking": false
    },
    {
      "version": "1.0.0",
      "date": "2026-06-04",
      "summary": "Initial release",
      "breaking": false
    }
  ],
  "files": [
    {
      "path": "index.jsx",
      "type": "registry:component",
      "target": "src/components/effects/directional-menu/index.jsx",
      "content": "// Built using Hyperiux Vault: https://vault.hyperiux.com\n\n\"use client\";\n\nimport { DirectionalMegaMenu } from \"./DirectionalMegaMenu\";\nimport { Menu1 } from \"./Menu1\";\nimport { Menu2 } from \"./Menu2\";\nimport { Menu3 } from \"./Menu3\";\nimport { MobileDirectionalMenu } from \"./MobileDirectionalMenu\";\n\nconst menuItems = [\n  {\n    label: \"Products\",\n    customContent: <Menu1 />,\n  },\n  {\n    label: \"Solutions\",\n    customContent: <Menu2 />,\n  },\n  {\n    label: \"Developers\",\n    customContent: <Menu3 />,\n  },\n  {\n    label: \"About\",\n    href: \"#\",\n  },\n];\n\nexport default function DirectionalMenu() {\n  return (\n    <div className=\"relative h-screen overflow-hidden bg-black text-white max-md:h-full max-md:overflow-visible max-md:pb-10\">\n      {/* Navbar */}\n      <header className=\"relative z-50 border-b border-white/10\">\n        <div className=\"max-w-7xl mx-auto max-[1025px]:hidden h-19 flex items-center justify-between\">\n          {/* Logo */}\n          <div className=\"flex items-center gap-3\">\n            <a href=\"/\" className=\"flex items-center gap-3 cursor-pointer\">\n              <svg width=\"30\" height=\"30\" viewBox=\"0 0 58 65\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n                <path d=\"M0 0H9.02977V28.5943H0V0Z\" fill=\"#ffffff\"/>\n                <path d=\"M57.1895 64.7134H48.1597V36.1192H57.1895V64.7134Z\" fill=\"#ffffff\"/>\n                <path d=\"M0.0195312 36.1192V64.7135H9.0493V42.139L21.5405 37.4737V28.7449L0.0195312 36.1192Z\" fill=\"#ffffff\"/>\n                <path d=\"M48.1777 22.5746V0.00012207H57.3579V28.5944L34.332 37.8697V28.5944L48.1777 22.5746Z\" fill=\"#ffffff\"/>\n                <path d=\"M21.9912 29.0459L28.4868 26.8346C28.8573 26.7085 29.2624 26.7316 29.6161 26.8992L34.7834 29.3469M21.9912 29.0459L28.1616 32.2063M21.9912 29.0459V37.1727L28.1616 40.0321M34.7834 29.3469L28.1616 32.2063M34.7834 29.3469C34.7834 32.3443 34.7834 34.1753 34.7834 37.1727L28.1616 40.0321M28.1616 32.2063V40.0321\" stroke=\"#ffffff\" strokeWidth=\"0.902977\"/>\n              </svg>\n\n              <div className=\"flex items-center gap-2 text-white\">\n                <svg width=\"156\" height=\"45\" viewBox=\"0 0 351 43\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n                  <path d=\"M315.441 6.10352e-05H306.862L320.055 15.9603L324.019 21.0695L306.862 42.139H315.591L332.597 21.0695L328.555 15.9603L315.441 6.10352e-05Z\" fill=\"white\"/>\n                  <path d=\"M350.055 6.10352e-05H341.326L332.598 10.6853L336.962 15.9527L350.055 6.10352e-05Z\" fill=\"white\"/>\n                  <path d=\"M349.905 42.139L341.176 42.139L332.598 31.7548L336.962 26.3369L349.905 42.139Z\" fill=\"white\"/>\n                  <path d=\"M264.874 6.10352e-05H258.252V34.0122L269.088 42.139H289.555L300.391 34.0122V6.10352e-05H293.769V29.9349C293.769 30.4164 293.539 30.8688 293.149 31.152L287.543 35.2293C287.286 35.4164 286.976 35.5172 286.658 35.5172H271.985C271.667 35.5172 271.357 35.4164 271.1 35.2293L265.494 31.152C265.104 30.8688 264.874 30.4164 264.874 29.9349V6.10352e-05Z\" fill=\"white\"/>\n                  <rect x=\"244.406\" y=\"6.10352e-05\" width=\"6.62183\" height=\"42.1389\" fill=\"white\"/>\n                  <path d=\"M195.043 0.000183105H228.002V6.62202H201.665V42.2896H195.043V0.000183105Z\" fill=\"white\"/>\n                  <path d=\"M233.269 24.0796L237.182 18.1404V6.62202V0.000183105H195.043V6.62202H230.56V15.8023L225.594 24.0796H233.269Z\" fill=\"white\"/>\n                  <path d=\"M221.078 17.7586H201.664V24.3804H217.466L229.205 42.139H237.163L221.078 17.7586Z\" fill=\"white\"/>\n                  <path d=\"M182.401 24.3804V17.7585H162.235L153.205 27.9923H162.084L165.245 24.3804H182.401Z\" fill=\"white\"/>\n                  <path d=\"M158.322 0H188.421V6.62183H161.031L152.904 15.9526V35.5171H188.421V42.1389H146.282V12.0397L158.322 0Z\" fill=\"white\"/>\n                  <rect x=\"97.5234\" y=\"17.7585\" width=\"6.62183\" height=\"24.3804\" fill=\"white\"/>\n                  <path d=\"M139.662 0H97.5234V6.62183H133.041V17.7586H111.15L104.534 24.3804H133.643L139.662 18.0595V0Z\" fill=\"white\"/>\n                  <path d=\"M55.3826 10.8357L55.3826 0H48.7607L48.7607 14.8991L66.5754 24.2299L66.5193 42.1389H73.1411V24.2299L90.8997 14.7486V0H84.2779V10.8357L69.8302 18.4786L55.3826 10.8357Z\" fill=\"white\"/>\n                  <rect width=\"6.62183\" height=\"42.1389\" fill=\"white\"/>\n                  <rect x=\"35.5166\" width=\"6.62183\" height=\"42.1389\" fill=\"white\"/>\n                  <rect x=\"6.62305\" y=\"17.7585\" width=\"28.8953\" height=\"6.62183\" fill=\"white\"/>\n                </svg>\n              </div>\n            </a>\n          </div>\n\n          {/* Menu */}\n          <DirectionalMegaMenu\n            items={menuItems}\n            closeDelay={80}\n            navClassName=\"mx-auto w-full  max-w-7xl justify-center px-6 md:px-10 max-[1025px]:translate-x-10 max-md:translate-x-0 \"\n            contentWrapperClassName=\"p-8 bg-white border border-white/10 rounded-3xl max-md:h-[92vh] max-[1025px]:h-[75vh]\"\n            animation={{\n              duration: 0.35,\n              ease: \"power2.inOut\",\n              distance: 100,\n              closeOpacityDuration: 0.3,\n              openOpacityDuration: 0.3,\n              fade: true,\n              heightDuration: 0.35,\n              heightEase: \"power2.inOut\",\n            }}\n          />\n        </div>\n        <MobileDirectionalMenu items={menuItems} />\n      </header>\n\n      {/* Hero Section */}\n      <section className=\"relative z-10 pt-25 flex items-center justify-center  px-6\">\n        <div className=\"max-w-5xl mx-auto text-center\">\n          {/* Heading */}\n          <h1 className=\"text-[10vw] sm:text-[11vw] md:text-[7rem] leading-none  tracking-[-0.06em]\">\n            Directional Menu\n          </h1>\n\n          <p className=\"mt-8 max-w-200 mx-auto text-white/70 max-md:text-sm text-2xl leading-[1.2]\">\n            Interact with the navigation above to see smooth transitions,\n            motion, and details crafted for a more immersive experience.\n          </p>\n          {/* Buttons */}\n          <div className=\"mt-12 flex flex-col sm:flex-row items-center justify-center gap-4 max-md:mt-20\">\n            <button className=\"px-7 py-3 cursor-pointer rounded-full bg-white text-black font-medium hover:bg-white/90 transition-all duration-300\">\n              <a href={\"/effects/navigation/directional-menu\"}>\n                Get Started\n              </a>\n            </button>\n\n            <button className=\"px-7 py-3 rounded-full border cursor-pointer border-white/10 bg-white/3 hover:bg-white/6 transition-all duration-300 text-white/80\">\n              <a href={\"/effects\"}>Explore Platform</a>\n            </button>\n          </div>\n        </div>\n      </section>\n    </div>\n  );\n}\n"
    },
    {
      "path": "DirectionalMegaMenu.jsx",
      "type": "registry:component",
      "target": "src/components/effects/directional-menu/DirectionalMegaMenu.jsx",
      "content": "\"use client\";\n\nimport { useEffect, useLayoutEffect, useMemo, useRef, useState } from \"react\";\nimport gsap from \"gsap\";\nimport { ChevronDown } from \"lucide-react\";\n\nconst DEFAULT_ANIMATION = {\n  contentFadeDelay: 0.06,\n  contentFadeDuration: 0.18,\n  distance: 56,\n  duration: 0.26,\n  ease: \"power2.out\",\n  fade: true,\n  heightDuration: 0.22,\n  heightEase: \"power2.out\",\n};\n\nconst DEFAULT_TRANSITION_DIRECTION = 1;\nconst NO_INDEX = null;\n\nconst useIsomorphicLayoutEffect =\n  typeof window !== \"undefined\" ? useLayoutEffect : useEffect;\n\nconst hasDropdownContent = (item) => Boolean(item?.customContent);\n\nconst getPaneHeight = (element) => {\n  if (!element) {\n    return 0;\n  }\n\n  return element.offsetHeight || element.scrollHeight || 0;\n};\n\nconst prefersReducedMotion = () =>\n  typeof window !== \"undefined\" &&\n  window.matchMedia?.(\"(prefers-reduced-motion: reduce)\")?.matches === true;\n\nconst REDUCED_MOTION_FADE_DURATION = 0.16;\n\nexport function DirectionalMegaMenu({\n  items = [],\n  className = \"\",\n  navClassName = \"\",\n  navItemClassName = \"\",\n  activeClassName = \"text-white\",\n  inactiveClassName = \"text-white\",\n  panelClassName = \"\",\n  contentWrapperClassName = \"\",\n  initialIndex = NO_INDEX,\n  closeDelay = 180,\n  animation = {},\n  panelGap = 0,\n  menuTop = \"\",\n}) {\n  // Animation config\n  const config = useMemo(\n    () => ({\n      contentFadeDelay: animation.contentFadeDelay ?? DEFAULT_ANIMATION.contentFadeDelay,\n      contentFadeDuration:\n        animation.contentFadeDuration ??\n  \n        DEFAULT_ANIMATION.contentFadeDuration,\n      distance: animation.distance ?? DEFAULT_ANIMATION.distance,\n      duration: animation.duration ?? DEFAULT_ANIMATION.duration,\n      ease: animation.ease ?? DEFAULT_ANIMATION.ease,\n      fade: animation.fade ?? DEFAULT_ANIMATION.fade,\n      heightDuration:\n        animation.heightDuration ?? DEFAULT_ANIMATION.heightDuration,\n      heightEase: animation.heightEase ?? DEFAULT_ANIMATION.heightEase,\n    }),\n    [animation]\n  );\n\n  // State and refs\n  const [isOpen, setIsOpen] = useState(initialIndex !== NO_INDEX);\n  const [currentIndex, setCurrentIndex] = useState(initialIndex);\n  const [nextIndex, setNextIndex] = useState(NO_INDEX);\n  const [highlightedIndex, setHighlightedIndex] = useState(initialIndex);\n\n  const panelRef = useRef(null);\n  const viewportRef = useRef(null);\n  const currentPaneRef = useRef(null);\n  const nextPaneRef = useRef(null);\n  const menuPanelRef = useRef(null);\n  const closeTimerRef = useRef(null);\n  const isAnimatingRef = useRef(false);\n  const queuedIndexRef = useRef(NO_INDEX);\n  const hasOpenedOnceRef = useRef(initialIndex !== NO_INDEX);\n  const visualIndexRef = useRef(initialIndex);\n  const transitionDirectionRef = useRef(DEFAULT_TRANSITION_DIRECTION);\n\n  const clearCloseTimer = () => {\n    if (!closeTimerRef.current) {\n      return;\n    }\n\n    clearTimeout(closeTimerRef.current);\n    closeTimerRef.current = null;\n  };\n\n  const resetMenuState = () => {\n    setIsOpen(false);\n    setCurrentIndex(NO_INDEX);\n    setNextIndex(NO_INDEX);\n    setHighlightedIndex(NO_INDEX);\n    isAnimatingRef.current = false;\n    queuedIndexRef.current = NO_INDEX;\n    hasOpenedOnceRef.current = false;\n    visualIndexRef.current = NO_INDEX;\n    transitionDirectionRef.current = DEFAULT_TRANSITION_DIRECTION;\n  };\n\n  const killPanelTweens = () => {\n    gsap.killTweensOf([\n      menuPanelRef.current,\n      panelRef.current,\n      viewportRef.current,\n      currentPaneRef.current,\n      nextPaneRef.current,\n    ]);\n  };\n\n  const restoreMenuPanel = () => {\n    if (!menuPanelRef.current) {\n      return;\n    }\n\n    gsap.killTweensOf(menuPanelRef.current);\n    gsap.set(menuPanelRef.current, {\n      opacity: 1,\n      height: \"auto\",\n    });\n  };\n\n  const closeMenu = () => {\n    clearCloseTimer();\n\n    if (!menuPanelRef.current) {\n      resetMenuState();\n      return;\n    }\n\n    killPanelTweens();\n\n    if (prefersReducedMotion()) {\n      gsap.to(menuPanelRef.current, {\n        opacity: 0,\n        duration: REDUCED_MOTION_FADE_DURATION,\n        ease: \"power2.out\",\n        onComplete: () => {\n          gsap.set(menuPanelRef.current, {\n            height: 0,\n          });\n          resetMenuState();\n        },\n      });\n      return;\n    }\n\n    gsap.to(menuPanelRef.current, {\n      opacity: 0,\n      height: 0,\n      duration: config.heightDuration,\n      ease: config.heightEase,\n      onComplete: resetMenuState,\n    });\n  };\n\n  const scheduleClose = () => {\n    clearCloseTimer();\n    closeTimerRef.current = setTimeout(() => {\n      closeMenu();\n    }, closeDelay);\n  };\n\n  const openMenuAt = (index) => {\n    const item = items[index];\n\n    if (!item || !hasDropdownContent(item)) {\n      return;\n    }\n\n    restoreMenuPanel();\n    setIsOpen(true);\n    setCurrentIndex(index);\n    setNextIndex(NO_INDEX);\n    setHighlightedIndex(index);\n\n    queuedIndexRef.current = NO_INDEX;\n    isAnimatingRef.current = false;\n    hasOpenedOnceRef.current = false;\n    visualIndexRef.current = index;\n    transitionDirectionRef.current = DEFAULT_TRANSITION_DIRECTION;\n  };\n\n  const requestSwitch = (index) => {\n    const item = items[index];\n\n    if (!item) {\n      return;\n    }\n\n    clearCloseTimer();\n    restoreMenuPanel();\n    setHighlightedIndex(index);\n\n    if (!hasDropdownContent(item)) {\n      setHighlightedIndex(NO_INDEX);\n\n      if (isOpen) {\n        closeMenu();\n      }\n\n      return;\n    }\n\n    if (!isOpen || currentIndex === NO_INDEX) {\n      openMenuAt(index);\n      return;\n    }\n\n    if (index === currentIndex && nextIndex === NO_INDEX) {\n      visualIndexRef.current = index;\n      return;\n    }\n\n    if (index === nextIndex) {\n      visualIndexRef.current = index;\n      return;\n    }\n\n    const baseIndex =\n      queuedIndexRef.current !== NO_INDEX\n        ? queuedIndexRef.current\n        : nextIndex !== NO_INDEX\n          ? nextIndex\n          : visualIndexRef.current !== NO_INDEX\n            ? visualIndexRef.current\n            : currentIndex;\n\n    const direction = index > baseIndex ? 1 : -1;\n\n    if (isAnimatingRef.current) {\n      queuedIndexRef.current = index;\n      visualIndexRef.current = index;\n      transitionDirectionRef.current = direction;\n      return;\n    }\n\n    visualIndexRef.current = index;\n    transitionDirectionRef.current = direction;\n    setNextIndex(index);\n  };\n\n  const onWrapperMouseLeave = () => {\n    scheduleClose();\n  };\n\n  const onItemEnter = (index) => {\n    const item = items[index];\n\n    if (!hasDropdownContent(item)) {\n      clearCloseTimer();\n      setHighlightedIndex(NO_INDEX);\n\n      if (isOpen) {\n        closeMenu();\n      }\n\n      return;\n    }\n\n    requestSwitch(index);\n  };\n\n  // Initial panel open\n  useIsomorphicLayoutEffect(() => {\n    if (!isOpen || currentIndex === NO_INDEX || !panelRef.current || !viewportRef.current) {\n      return;\n    }\n\n    if (!currentPaneRef.current) {\n      return;\n    }\n\n    gsap.killTweensOf([menuPanelRef.current, panelRef.current, viewportRef.current]);\n\n    const contentHeight = getPaneHeight(currentPaneRef.current);\n\n    if (!hasOpenedOnceRef.current) {\n      if (prefersReducedMotion()) {\n        gsap.set(menuPanelRef.current, {\n          height: \"auto\",\n          opacity: 0,\n        });\n\n        gsap.set(viewportRef.current, {\n          height: contentHeight,\n          opacity: 1,\n        });\n\n        gsap.to(menuPanelRef.current, {\n          opacity: 1,\n          duration: REDUCED_MOTION_FADE_DURATION,\n          ease: \"power2.out\",\n          onComplete: () => {\n            hasOpenedOnceRef.current = true;\n          },\n        });\n        return;\n      }\n\n      gsap.set(menuPanelRef.current, {\n        height: 0,\n        opacity: 1,\n      });\n\n      gsap.set(viewportRef.current, {\n        height: contentHeight,\n        opacity: 0,\n      });\n\n      const timeline = gsap.timeline({\n        onComplete: () => {\n          hasOpenedOnceRef.current = true;\n          gsap.set(viewportRef.current, { height: contentHeight, opacity: 1 });\n        },\n      });\n\n      timeline\n        .to(\n          menuPanelRef.current,\n          {\n            height: \"auto\",\n            duration: config.heightDuration,\n            ease: config.heightEase,\n          },\n          0\n        )\n        .to(\n          viewportRef.current,\n          {\n            opacity: 1,\n            duration: config.contentFadeDuration,\n            ease: \"power2.out\",\n          },\n          config.contentFadeDelay\n        );\n\n      return;\n    }\n\n    gsap.set(viewportRef.current, {\n      height: contentHeight,\n    });\n  }, [\n    config.contentFadeDelay,\n    config.contentFadeDuration,\n    config.heightDuration,\n    config.heightEase,\n    currentIndex,\n    isOpen,\n  ]);\n\n  // Sync static panel height\n  useIsomorphicLayoutEffect(() => {\n    if (currentIndex === NO_INDEX || nextIndex !== NO_INDEX) {\n      return;\n    }\n\n    if (!currentPaneRef.current || !viewportRef.current) {\n      return;\n    }\n\n    const height = getPaneHeight(currentPaneRef.current);\n    gsap.set(viewportRef.current, { height });\n  }, [currentIndex, nextIndex]);\n\n  // Animate panel switch\n  useIsomorphicLayoutEffect(() => {\n    if (nextIndex === NO_INDEX) {\n      return;\n    }\n\n    if (!currentPaneRef.current || !nextPaneRef.current || !viewportRef.current) {\n      return;\n    }\n\n    const currentElement = currentPaneRef.current;\n    const nextElement = nextPaneRef.current;\n    const currentHeight = getPaneHeight(currentElement);\n    const nextHeight = getPaneHeight(nextElement);\n    const targetIndex = nextIndex;\n    const direction = transitionDirectionRef.current;\n    const distance = config.distance;\n\n    isAnimatingRef.current = true;\n\n    gsap.killTweensOf([currentElement, nextElement, viewportRef.current]);\n\n    if (prefersReducedMotion()) {\n      gsap.set(currentElement, {\n        position: \"absolute\",\n        inset: 0,\n        x: 0,\n        opacity: 0,\n        zIndex: 1,\n        pointerEvents: \"none\",\n      });\n\n      gsap.set(nextElement, {\n        position: \"absolute\",\n        inset: 0,\n        x: 0,\n        opacity: 1,\n        zIndex: 2,\n        pointerEvents: \"none\",\n      });\n\n      gsap.set(viewportRef.current, {\n        height: nextHeight,\n      });\n\n      setCurrentIndex(targetIndex);\n      setNextIndex(NO_INDEX);\n      isAnimatingRef.current = false;\n      visualIndexRef.current = targetIndex;\n\n      const queuedIndex = queuedIndexRef.current;\n      queuedIndexRef.current = NO_INDEX;\n\n      if (\n        queuedIndex !== NO_INDEX &&\n        queuedIndex !== targetIndex &&\n        items[queuedIndex] &&\n        hasDropdownContent(items[queuedIndex])\n      ) {\n        requestSwitch(queuedIndex);\n      }\n\n      return;\n    }\n\n    gsap.set(currentElement, {\n      position: \"absolute\",\n      inset: 0,\n      x: 0,\n      opacity: 1,\n      zIndex: 1,\n      pointerEvents: \"none\",\n    });\n\n    gsap.set(nextElement, {\n      position: \"absolute\",\n      inset: 0,\n      x: direction > 0 ? distance : -distance,\n      opacity: config.fade ? 0 : 1,\n      zIndex: 2,\n      pointerEvents: \"none\",\n    });\n\n    gsap.set(viewportRef.current, {\n      height: currentHeight,\n    });\n\n    const timeline = gsap.timeline({\n      onComplete: () => {\n        setCurrentIndex(targetIndex);\n        setNextIndex(NO_INDEX);\n        isAnimatingRef.current = false;\n        visualIndexRef.current = targetIndex;\n\n        const queuedIndex = queuedIndexRef.current;\n        queuedIndexRef.current = NO_INDEX;\n\n        if (\n          queuedIndex !== NO_INDEX &&\n          queuedIndex !== targetIndex &&\n          items[queuedIndex] &&\n          hasDropdownContent(items[queuedIndex])\n        ) {\n          requestSwitch(queuedIndex);\n        }\n      },\n    });\n\n    timeline\n      .to(\n        viewportRef.current,\n        {\n          height: nextHeight,\n          duration: config.heightDuration,\n          ease: config.heightEase,\n        },\n        0\n      )\n      .to(\n        currentElement,\n        {\n          x: direction > 0 ? -distance : distance,\n          opacity: config.fade ? 0 : 1,\n          duration: config.duration,\n          ease: config.ease,\n        },\n        0\n      )\n      .to(\n        nextElement,\n        {\n          x: 0,\n          opacity: 1,\n          duration: config.duration,\n          ease: config.ease,\n        },\n        0\n      );\n  }, [config.distance, config.duration, config.ease, config.fade, config.heightDuration, config.heightEase, items, nextIndex]);\n\n  // Cleanup\n  useEffect(() => {\n    const panel = panelRef.current;\n    const viewport = viewportRef.current;\n    const currentPane = currentPaneRef.current;\n    const nextPane = nextPaneRef.current;\n\n    return () => {\n      clearCloseTimer();\n      gsap.killTweensOf(panel);\n      gsap.killTweensOf(viewport);\n      gsap.killTweensOf([currentPane, nextPane]);\n    };\n  }, []);\n\n  return (\n    <div\n      className={`pointer-events-none absolute inset-0 z-50 ${className}`}\n      onMouseEnter={clearCloseTimer}\n      onMouseLeave={onWrapperMouseLeave}\n    >\n      <div\n        className={`flex h-full items-center justify-center ${navClassName}`}\n      >\n        <div className=\"pointer-events-auto flex items-center justify-center gap-8\">\n          {items.map((item, index) => {\n            const isActive = highlightedIndex === index;\n\n            return (\n              <button\n                key={item.label || index}\n                type=\"button\"\n                onMouseEnter={() => onItemEnter(index)}\n                className={`relative flex cursor-pointer items-center gap-1 text-sm font-medium text-white transition-colors duration-300 hover:text-white motion-reduce:text-white motion-reduce:transition-none max-[1025px]:text-lg max-md:text-sm ${navItemClassName} ${isActive ? activeClassName : inactiveClassName\n                  }`}\n              >\n                {item.label}\n\n                {hasDropdownContent(item) && (\n                  <ChevronDown\n                    className={`h-4 w-4 transition-transform duration-300 motion-reduce:rotate-0 motion-reduce:transition-none ${isActive ? \"-rotate-180\" : \"rotate-0\"\n                      }`}\n                  />\n                )}\n              </button>\n            );\n          })}\n        </div>\n      </div>\n\n      {isOpen && currentIndex !== NO_INDEX && hasDropdownContent(items[currentIndex]) && (\n        <div className=\"absolute left-0 w-full\" style={{ top: `calc(100% + ${panelGap}px)` }}>\n          <div\n            ref={menuPanelRef}\n            className={`menu-container pointer-events-auto w-full overflow-hidden ${menuTop}`}\n          >\n            <div\n              ref={panelRef}\n              className={`w-full overflow-hidden rounded-[0.5vw] border border-neutral-200 bg-white shadow-[0_20px_60px_rgba(0,0,0,0.08)] transform-gpu ${panelClassName} ${contentWrapperClassName}`}\n            >\n              <div\n                ref={viewportRef}\n                className=\"relative\"\n                onMouseEnter={clearCloseTimer}\n              >\n                <div\n                  key={`current-${currentIndex}`}\n                  ref={currentPaneRef}\n                  className=\"relative w-full\"\n                >\n                  {items[currentIndex]?.customContent || null}\n                </div>\n\n                {nextIndex !== NO_INDEX && hasDropdownContent(items[nextIndex]) && (\n                  <div\n                    key={`next-${nextIndex}`}\n                    ref={nextPaneRef}\n                    className=\"absolute left-0 top-0 w-full\"\n                  >\n                    {items[nextIndex]?.customContent || null}\n                  </div>\n                )}\n              </div>\n            </div>\n          </div>\n        </div>\n      )}\n    </div>\n  );\n}\n"
    },
    {
      "path": "Menu1.jsx",
      "type": "registry:component",
      "target": "src/components/effects/directional-menu/Menu1.jsx",
      "content": "\"use client\";\n\nconst PRODUCT_LINKS = [\n  { label: \"Overview\", href: \"#\" },\n  { label: \"Pricing\", href: \"#\" },\n  { label: \"Integrations\", href: \"#\" },\n  { label: \"Documentation\", href: \"#\" },\n];\n\nconst PREVIEW_IMAGES = [\n  {\n    alt: \"Dashboard preview\",\n    heightClassName: \"h-54 max-[1025px]:h-60 max-md:h-40\",\n    src: \"https://pub-8abee449136941f5b0a1cd2c014534e9.r2.dev/vault-listing-images/assets-images/h-09.jpg\",\n  },\n  {\n    alt: \"Product showcase\",\n    heightClassName: \"h-54 max-[1025px]:h-60 max-md:h-40\",\n    src: \"https://pub-8abee449136941f5b0a1cd2c014534e9.r2.dev/vault-listing-images/assets-images/h-07.jpg\",\n  },\n];\n\nexport function Menu1() {\n  return (\n    <div className=\"flex justify-between gap-8 max-[1025px]:flex-wrap max-md:flex-col\">\n      <div className=\"flex w-[65%] flex-col gap-6 max-[1025px]:w-[70%] max-md:w-full\">\n        <div className=\"flex flex-col gap-2 max-[1025px]:gap-3 max-md:gap-2\">\n          <p className=\"text-xs font-semibold uppercase tracking-[0.14em] text-neutral-800 max-[1025px]:text-base max-md:text-xs\">\n            Product Navigation\n          </p>\n          <h3 className=\"text-2xl text-neutral-800 font-semibold max-[1025px]:text-4xl max-md:text-2xl\">\n            Explore the product suite\n          </h3>\n        </div>\n\n        <div className=\"flex flex-col max-md:flex-wrap gap-4\">\n          {PRODUCT_LINKS.map((item) => (\n            <a\n              key={item.label}\n              href={item.href}\n              className=\"flex flex-col gap-1 rounded-lg border border-neutral-200 p-4 transition-colors duration-200 hover:bg-neutral-100 motion-reduce:bg-transparent motion-reduce:transition-none\"\n            >\n              <div className=\"text-lg font-medium text-neutral-700 max-[1025px]:text-2xl max-md:text-xl\">{item.label}</div>\n              <div className=\"text-sm text-neutral-600 max-[1025px]:text-lg max-md:text-base\">\n                Learn more about {item.label.toLowerCase()}.\n              </div>\n            </a>\n          ))}\n        </div>\n      </div>\n\n      <div className=\"flex w-[30%] max-[1025px]:hidden flex-col justify-end gap-4  max-[1025px]:w-full max-[1025px]:grid-cols-2 max-[1025px]:gap-4  \">\n        {PREVIEW_IMAGES.map((image) => (\n          <div\n            key={image.src}\n            className={`${image.heightClassName} overflow-hidden rounded-lg `}\n          >\n            <img\n              src={image.src}\n              alt={image.alt}\n              width={600}\n              height={400}\n              className=\"h-full w-full object-cover\"\n            />\n          </div>\n        ))}\n      </div>\n    </div>\n  );\n}\n"
    },
    {
      "path": "Menu2.jsx",
      "type": "registry:component",
      "target": "src/components/effects/directional-menu/Menu2.jsx",
      "content": "\"use client\";\n\nconst SOLUTION_BLOCKS = [\n  {\n    title: \"Startups\",\n    description:\n      \"Launch faster with flexible infrastructure, simple onboarding, and scalable workflows.\",\n    href: \"#\",\n  },\n  {\n    title: \"Enterprise\",\n    description:\n      \"Bring governance, performance, and operational control to complex digital systems.\",\n    href: \"#\",\n  },\n  {\n    title: \"Agencies\",\n    description:\n      \"Build repeatable client delivery with modular systems, collaboration, and speed.\",\n    href: \"#\",\n  },\n];\n\nexport function Menu2() {\n  return (\n    <div className=\"flex flex-col gap-6 max-[1025px]:gap-10\">\n      <div className=\"flex flex-col gap-2 max-[1025px]:gap-3\">\n        <p className=\"text-xs font-semibold uppercase tracking-[0.14em] text-neutral-800 max-[1025px]:text-base max-md:text-xs\">\n          Solutions\n        </p>\n        <h3 className=\"text-2xl text-neutral-800 font-semibold max-[1025px]:text-4xl max-md:text-2xl\">\n          Built for different teams\n        </h3>\n      </div>\n\n      <div className=\"grid grid-cols-3 gap-6 max-[1025px]:grid-cols-2\">\n        {SOLUTION_BLOCKS.map((block) => (\n          <a\n            key={block.title}\n            href={block.href}\n            className=\"flex flex-col gap-3 rounded-lg border border-neutral-200 p-6 transition-all duration-200 hover:bg-neutral-100 max-md:p-4 motion-reduce:bg-transparent motion-reduce:transition-none\"\n          >\n            <h4 className=\"text-lg font-semibold text-neutral-800 max-[1025px]:text-3xl max-md:text-xl\">{block.title}</h4>\n            <p className=\"text-sm leading-6 text-neutral-600 max-[1025px]:text-xl max-md:text-base max-md:leading-[1.2]\">\n              {block.description}\n            </p>\n          </a>\n        ))}\n      </div>\n    </div>\n  );\n}\n"
    },
    {
      "path": "Menu3.jsx",
      "type": "registry:component",
      "target": "src/components/effects/directional-menu/Menu3.jsx",
      "content": "\"use client\";\n\nconst LINK_GROUPS = [\n  {\n    heading: \"Documentation\",\n    links: [\n      { label: \"API Reference\", href: \"#\" },\n      { label: \"Guides\", href: \"#\" },\n      { label: \"Examples\", href: \"#\" },\n    ],\n  },\n  {\n    heading: \"Resources\",\n    links: [\n      { label: \"Changelog\", href: \"#\" },\n      { label: \"Community\", href: \"#\" },\n      { label: \"Support\", href: \"#\" },\n    ],\n  },\n  {\n    heading: \"Company\",\n    links: [\n      { label: \"About\", href: \"#\" },\n      { label: \"Careers\", href: \"#\" },\n      { label: \"Contact\", href: \"#\" },\n    ],\n  },\n];\n\nexport function Menu3() {\n  return (\n    <div className=\"grid grid-cols-3 gap-10 max-[1025px]:grid-cols-2\">\n      {LINK_GROUPS.map((group) => (\n        <div key={group.heading} className=\"flex flex-col gap-4\">\n          <h4 className=\"text-sm font-semibold uppercase tracking-[0.08em] text-neutral-800 max-[1025px]:text-2xl max-md:text-sm\">\n            {group.heading}\n          </h4>\n\n          <div className=\"flex flex-col gap-3\">\n            {group.links.map((link) => (\n              <a\n                key={link.label}\n                href={link.href}\n                className=\"block text-sm font-medium text-neutral-600 transition-opacity duration-200 hover:text-black max-[1025px]:text-xl max-md:text-base motion-reduce:text-neutral-600 motion-reduce:transition-none\"\n              >\n                {link.label}\n              </a>\n            ))}\n          </div>\n        </div>\n      ))}\n    </div>\n  );\n}\n"
    },
    {
      "path": "MobileDirectionalMenu.jsx",
      "type": "registry:component",
      "target": "src/components/effects/directional-menu/MobileDirectionalMenu.jsx",
      "content": "\"use client\";\n\nimport { useRef, useState } from \"react\";\nimport { ChevronDown, Menu, X } from \"lucide-react\";\nimport { useFocusTrap } from \"./useFocusTrap\";\n\nconst hasDropdownContent = (item) => Boolean(item?.customContent);\n\nexport function MobileDirectionalMenu({ items = [] }) {\n  const [isMenuOpen, setIsMenuOpen] = useState(false);\n  const [activeIndex, setActiveIndex] = useState(null);\n\n  const containerRef = useRef(null);\n  const toggleButtonRef = useRef(null);\n\n  const closeMenu = () => {\n    setIsMenuOpen(false);\n    setActiveIndex(null);\n  };\n\n  // Trap focus across the toggle + panel while open, restore it on close.\n  useFocusTrap({\n    active: isMenuOpen,\n    containerRef,\n    initialFocusRef: toggleButtonRef,\n    onEscape: closeMenu,\n  });\n\n  const onToggleMenu = () => {\n    setIsMenuOpen((currentValue) => {\n      const nextValue = !currentValue;\n\n      if (!nextValue) {\n        setActiveIndex(null);\n      }\n\n      return nextValue;\n    });\n  };\n\n  const onToggleSection = (index) => {\n    setActiveIndex((currentValue) => (currentValue === index ? null : index));\n  };\n\n  return (\n    <div ref={containerRef} className=\"relative hidden max-[1025px]:block px-6 py-4\">\n      <div className=\"flex items-center justify-between gap-4\">\n        <a href=\"/\" className=\"flex items-center  gap-3 cursor-pointer\">\n          <svg width=\"35\" height=\"35\" viewBox=\"0 0 58 65\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M0 0H9.02977V28.5943H0V0Z\" fill=\"#ffffff\"/>\n            <path d=\"M57.1895 64.7134H48.1597V36.1192H57.1895V64.7134Z\" fill=\"#ffffff\"/>\n            <path d=\"M0.0195312 36.1192V64.7135H9.0493V42.139L21.5405 37.4737V28.7449L0.0195312 36.1192Z\" fill=\"#ffffff\"/>\n            <path d=\"M48.1777 22.5746V0.00012207H57.3579V28.5944L34.332 37.8697V28.5944L48.1777 22.5746Z\" fill=\"#ffffff\"/>\n            <path d=\"M21.9912 29.0459L28.4868 26.8346C28.8573 26.7085 29.2624 26.7316 29.6161 26.8992L34.7834 29.3469M21.9912 29.0459L28.1616 32.2063M21.9912 29.0459V37.1727L28.1616 40.0321M34.7834 29.3469L28.1616 32.2063M34.7834 29.3469C34.7834 32.3443 34.7834 34.1753 34.7834 37.1727L28.1616 40.0321M28.1616 32.2063V40.0321\" stroke=\"#ffffff\" strokeWidth=\"0.902977\"/>\n          </svg>\n          <svg width=\"166\" height=\"55\" viewBox=\"0 0 351 43\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M315.441 6.10352e-05H306.862L320.055 15.9603L324.019 21.0695L306.862 42.139H315.591L332.597 21.0695L328.555 15.9603L315.441 6.10352e-05Z\" fill=\"white\"/>\n            <path d=\"M350.055 6.10352e-05H341.326L332.598 10.6853L336.962 15.9527L350.055 6.10352e-05Z\" fill=\"white\"/>\n            <path d=\"M349.905 42.139L341.176 42.139L332.598 31.7548L336.962 26.3369L349.905 42.139Z\" fill=\"white\"/>\n            <path d=\"M264.874 6.10352e-05H258.252V34.0122L269.088 42.139H289.555L300.391 34.0122V6.10352e-05H293.769V29.9349C293.769 30.4164 293.539 30.8688 293.149 31.152L287.543 35.2293C287.286 35.4164 286.976 35.5172 286.658 35.5172H271.985C271.667 35.5172 271.357 35.4164 271.1 35.2293L265.494 31.152C265.104 30.8688 264.874 30.4164 264.874 29.9349V6.10352e-05Z\" fill=\"white\"/>\n            <rect x=\"244.406\" y=\"6.10352e-05\" width=\"6.62183\" height=\"42.1389\" fill=\"white\"/>\n            <path d=\"M195.043 0.000183105H228.002V6.62202H201.665V42.2896H195.043V0.000183105Z\" fill=\"white\"/>\n            <path d=\"M233.269 24.0796L237.182 18.1404V6.62202V0.000183105H195.043V6.62202H230.56V15.8023L225.594 24.0796H233.269Z\" fill=\"white\"/>\n            <path d=\"M221.078 17.7586H201.664V24.3804H217.466L229.205 42.139H237.163L221.078 17.7586Z\" fill=\"white\"/>\n            <path d=\"M182.401 24.3804V17.7585H162.235L153.205 27.9923H162.084L165.245 24.3804H182.401Z\" fill=\"white\"/>\n            <path d=\"M158.322 0H188.421V6.62183H161.031L152.904 15.9526V35.5171H188.421V42.1389H146.282V12.0397L158.322 0Z\" fill=\"white\"/>\n            <rect x=\"97.5234\" y=\"17.7585\" width=\"6.62183\" height=\"24.3804\" fill=\"white\"/>\n            <path d=\"M139.662 0H97.5234V6.62183H133.041V17.7586H111.15L104.534 24.3804H133.643L139.662 18.0595V0Z\" fill=\"white\"/>\n            <path d=\"M55.3826 10.8357L55.3826 0H48.7607L48.7607 14.8991L66.5754 24.2299L66.5193 42.1389H73.1411V24.2299L90.8997 14.7486V0H84.2779V10.8357L69.8302 18.4786L55.3826 10.8357Z\" fill=\"white\"/>\n            <rect width=\"6.62183\" height=\"42.1389\" fill=\"white\"/>\n            <rect x=\"35.5166\" width=\"6.62183\" height=\"42.1389\" fill=\"white\"/>\n            <rect x=\"6.62305\" y=\"17.7585\" width=\"28.8953\" height=\"6.62183\" fill=\"white\"/>\n          </svg>\n        </a>\n\n        <button\n          ref={toggleButtonRef}\n          type=\"button\"\n          onClick={onToggleMenu}\n          className=\"flex h-11 w-11 items-center justify-center rounded-full  text-white transition-colors duration-200 hover:bg-white/10 motion-reduce:bg-transparent motion-reduce:transition-none\"\n          aria-expanded={isMenuOpen}\n          aria-label={isMenuOpen ? \"Close menu\" : \"Open menu\"}\n        >\n          {isMenuOpen ? <X className=\"max-md:h-5 max-md:w-5 h-10 w-10\" /> : <Menu className=\" max-md:h-7 max-md:w-7 h-10 w-10\" />}\n        </button>\n      </div>\n\n      <div\n        className={`absolute left-6 right-6 top-18 z-50 overflow-hidden rounded-lg bg-white text-black transition-all duration-300 ease-in-out motion-reduce:transition-none ${\n          isMenuOpen\n            ? \"max-h-[80vh] overflow-y-scroll p-8 opacity-100 max-md:max-h-[90vh] max-md:p-3\"\n            : \"max-h-0 p-0 opacity-0 pointer-events-none\"\n        }`}\n        aria-hidden={!isMenuOpen}\n      >\n          <div className=\"flex flex-col max-md:gap-2 gap-5\">\n            {items.map((item, index) => {\n              const isOpen = activeIndex === index;\n              const isDropdown = hasDropdownContent(item);\n\n              if (!isDropdown) {\n                return (\n                  <a\n                    key={item.label}\n                    href={item.href || \"#\"}\n                    className=\"rounded-lg border border-neutral-200 bg-neutral-50/60 px-4 py-4 max-md:text-sm max-[1025px]:text-lg font-semibold uppercase tracking-[0.14em] text-neutral-500 transition-all duration-300 ease-in-out hover:bg-neutral-100 motion-reduce:bg-neutral-50/60 motion-reduce:transition-none max-[1025px]:rounded-none max-[1025px]:border-x-0 max-[1025px]:border-t-0\"\n                    onClick={() => setIsMenuOpen(false)}\n                  >\n                    {item.label}\n                  </a>\n                );\n              }\n\n              return (\n                <section\n                  key={item.label}\n                  className=\"rounded-lg border border-neutral-200 bg-neutral-50/60 transition-all duration-300 ease-in-out motion-reduce:transition-none max-[1025px]:rounded-none max-[1025px]:border-x-0 max-[1025px]:border-t-0\"\n                >\n                  <button\n                    type=\"button\"\n                    onClick={() => onToggleSection(index)}\n                    className=\"flex w-full items-center justify-between gap-4 px-4 py-4 text-left transition-all duration-300 ease-in-out motion-reduce:transition-none\"\n                    aria-expanded={isOpen}\n                  >\n                    <span className=\"max-md:text-sm max-[1025px]:text-lg font-semibold uppercase tracking-[0.14em] text-neutral-500\">\n                      {item.label}\n                    </span>\n\n                    <ChevronDown\n                      className={`h-4 w-4 text-neutral-500 transition-transform duration-300 ease-in-out motion-reduce:rotate-0 motion-reduce:transition-none ${\n                        isOpen ? \"rotate-180\" : \"\"\n                      }`}\n                    />\n                  </button>\n\n                  <div\n                    className={`overflow-hidden transition-all duration-300 ease-in-out motion-reduce:transition-none ${\n                      isOpen\n                        ? \"max-h-[60rem]  px-4 py-4 opacity-100\"\n                        : \"max-h-0 border-t-0 px-4 py-0 opacity-0\"\n                    }`}\n                  >\n                      <div className=\"max-[1025px]:[&_.grid]:grid-cols-2 max-md:[&_.grid]:grid-cols-1 \">\n                        {item.customContent}\n                      </div>\n                    </div>\n                </section>\n              );\n            })}\n          </div>\n      </div>\n    </div>\n  );\n}\n"
    },
    {
      "path": "useFocusTrap.js",
      "type": "registry:component",
      "target": "src/components/effects/directional-menu/useFocusTrap.js",
      "content": "\"use client\";\n\nimport { useEffect, useRef } from \"react\";\n\nconst FOCUSABLE_SELECTOR = [\n  \"a[href]\",\n  \"button:not([disabled])\",\n  \"input:not([disabled])\",\n  \"select:not([disabled])\",\n  \"textarea:not([disabled])\",\n  '[tabindex]:not([tabindex=\"-1\"])',\n].join(\",\");\n\nconst isVisible = (element) => {\n  if (!element || element.hidden) {\n    return false;\n  }\n\n  const style = window.getComputedStyle(element);\n\n  if (style.visibility === \"hidden\" || style.visibility === \"collapse\") {\n    return false;\n  }\n\n  return element.getClientRects().length > 0;\n};\n\nconst getFocusableElements = (container) => {\n  if (!container) {\n    return [];\n  }\n\n  return Array.from(container.querySelectorAll(FOCUSABLE_SELECTOR)).filter(\n    isVisible\n  );\n};\n\n/**\n * Keeps keyboard focus inside `containerRef` while `active` is true.\n *\n * - Captures the element focused before opening and restores focus to it on\n *   close (so the trigger button gets focus back).\n * - Moves focus into the menu on open (to `initialFocusRef` when provided,\n *   otherwise the first focusable element).\n * - Wraps Tab / Shift+Tab around the menu's focusable elements.\n * - Calls `onEscape` when the Escape key is pressed.\n */\nexport function useFocusTrap({\n  active,\n  containerRef,\n  initialFocusRef,\n  onEscape,\n}) {\n  const onEscapeRef = useRef(onEscape);\n  onEscapeRef.current = onEscape;\n\n  useEffect(() => {\n    if (!active) {\n      return;\n    }\n\n    const container = containerRef.current;\n\n    if (!container) {\n      return;\n    }\n\n    const previouslyFocused =\n      document.activeElement instanceof HTMLElement\n        ? document.activeElement\n        : null;\n\n    const focusInitial = () => {\n      const target =\n        initialFocusRef?.current ??\n        getFocusableElements(container)[0] ??\n        container;\n\n      if (!(target instanceof HTMLElement)) {\n        return;\n      }\n\n      if (target === container && !container.hasAttribute(\"tabindex\")) {\n        container.setAttribute(\"tabindex\", \"-1\");\n      }\n\n      target.focus();\n    };\n\n    // Defer focus so it lands after the menu has mounted / started animating in.\n    const focusFrame = requestAnimationFrame(focusInitial);\n\n    const onKeyDown = (event) => {\n      if (event.key === \"Escape\") {\n        event.preventDefault();\n        onEscapeRef.current?.();\n        return;\n      }\n\n      if (event.key !== \"Tab\") {\n        return;\n      }\n\n      const focusable = getFocusableElements(container);\n\n      if (!focusable.length) {\n        event.preventDefault();\n        return;\n      }\n\n      const first = focusable[0];\n      const last = focusable[focusable.length - 1];\n      const activeElement = document.activeElement;\n\n      if (event.shiftKey) {\n        if (activeElement === first || !container.contains(activeElement)) {\n          event.preventDefault();\n          last.focus();\n        }\n\n        return;\n      }\n\n      if (activeElement === last || !container.contains(activeElement)) {\n        event.preventDefault();\n        first.focus();\n      }\n    };\n\n    document.addEventListener(\"keydown\", onKeyDown);\n\n    return () => {\n      cancelAnimationFrame(focusFrame);\n      document.removeEventListener(\"keydown\", onKeyDown);\n\n      if (previouslyFocused && document.contains(previouslyFocused)) {\n        previouslyFocused.focus();\n      }\n    };\n  }, [active, containerRef, initialFocusRef]);\n}\n"
    }
  ]
}