{
  "name": "elevate-navbar",
  "type": "registry:component",
  "title": "Elevate Navbar",
  "description": "Responsive glass-morphism pill navigation with animated dropdowns, GSAP text-swap hover effects, and a collapsible mobile accordion menu",
  "dependencies": [
    "gsap",
    "lucide-react"
  ],
  "registryDependencies": [],
  "exportName": "ElevateNavbar",
  "exportKind": "default",
  "tier": "free",
  "version": "1.1.0",
  "changelog": [
    {
      "version": "1.1.0",
      "date": "2026-07-21",
      "summary": "Added prefers-reduced-motion support: dropdown and hover color transitions are set instantly instead of tweened when the user has reduced motion enabled",
      "breaking": false
    },
    {
      "version": "1.0.0",
      "date": "2026-06-04",
      "summary": "Initial release",
      "breaking": false
    }
  ],
  "props": [
    {
      "name": "backgroundColor",
      "type": "string",
      "default": "#d8b4fe",
      "description": "Page background behind the navbar.",
      "remixer": {
        "control": "color",
        "group": "appearance",
        "groupTitle": "Appearance"
      }
    },
    {
      "name": "ctaBackground",
      "type": "string",
      "default": "#ffffff",
      "description": "CTA button background color.",
      "remixer": {
        "control": "color",
        "group": "appearance",
        "groupTitle": "Appearance"
      }
    },
    {
      "name": "ctaHoverBackground",
      "type": "string",
      "default": "#000000",
      "description": "CTA button hover background color.",
      "remixer": {
        "control": "color",
        "group": "appearance",
        "groupTitle": "Appearance"
      }
    },
    {
      "name": "activeColor",
      "type": "string",
      "default": "#ffffff",
      "description": "Active and focused nav link color.",
      "remixer": {
        "control": "color",
        "group": "appearance",
        "groupTitle": "Appearance"
      }
    },
    {
      "name": "inactiveColor",
      "type": "string",
      "default": "rgba(255,255,255,0.5)",
      "description": "Dimmed nav link color while another item is active.",
      "remixer": {
        "control": "color",
        "group": "appearance",
        "groupTitle": "Appearance"
      }
    },
    {
      "name": "duration",
      "type": "number",
      "default": 0.35,
      "description": "Nav, dropdown, and mobile accordion animation duration.",
      "remixer": {
        "control": "range",
        "group": "motion",
        "groupTitle": "Motion",
        "min": 0.1,
        "max": 1.5,
        "step": 0.05
      }
    },
    {
      "name": "ease",
      "type": "string",
      "default": "power2.out",
      "description": "GSAP easing used for nav and dropdown transitions.",
      "remixer": {
        "control": "select",
        "group": "motion",
        "groupTitle": "Motion",
        "options": [
          {
            "value": "power2.out",
            "label": "Power 2 Out"
          },
          {
            "value": "power3.out",
            "label": "Power 3 Out"
          },
          {
            "value": "power4.out",
            "label": "Power 4 Out"
          },
          {
            "value": "expo.out",
            "label": "Expo Out"
          }
        ]
      }
    },
    {
      "name": "staggerItems",
      "type": "boolean",
      "default": true,
      "description": "Reveals dropdown items one by one.",
      "remixer": {
        "control": "checkbox",
        "group": "dropdown",
        "groupTitle": "Dropdown"
      }
    }
  ],
  "remixer": {
    "enabled": true,
    "defaultOpenGroupId": "appearance",
    "layout": {
      "buttonClassName": "right-4! top-25!",
      "panelClassName": "right-0! top-[132px]! h-[calc(100%-132px)]! w-[344px]!"
    },
    "copyCode": {
      "includeOnlyPublicProps": true
    }
  },
  "files": [
    {
      "path": "index.tsx",
      "type": "registry:component",
      "target": "src/components/effects/elevate-navbar/index.tsx",
      "content": "// Built using Hyperiux Vault: https://vault.hyperiux.com\n\n\"use client\";\n\nimport React, { useEffect, useState } from \"react\";\nimport { ElevateNavbarMobile } from \"./ElevateMobileNav\";\nimport { ElevateNavbarDesktop } from \"./ElevateDesktopNav\";\n\nconst NAV_CONFIG = {\n  backgroundColor: \"#d8b4fe\",\n  duration: 0.35,\n  navTextDuration: 0.35,\n  ctaDuration: 0.4,\n  dropdownItemOffsetY: -8,\n  dropdownPointerDelay: 0.03,\n  staggerItems: true,\n  activeColor: \"#ffffff\",\n  inactiveColor: \"rgba(255,255,255,0.5)\",\n  ease: \"power2.out\",\n  ctaBackground: \"#ffffff\",\n  ctaHoverBackground: \"#000000\",\n};\n\ntype ElevateNavbarConfig = typeof NAV_CONFIG;\n\ninterface ElevateNavbarProps extends Partial<ElevateNavbarConfig> {\n  navConfig?: Partial<ElevateNavbarConfig>;\n}\n\nexport default function ElevateNavbar({ navConfig = {}, ...props }: ElevateNavbarProps) {\n  const config = { ...NAV_CONFIG, ...navConfig, ...props };\n  const durationValue = config.duration ?? config.navTextDuration;\n  const [isMobile, setIsMobile] = useState(false);\n  const [hasMounted, setHasMounted] = useState(false);\n\n  useEffect(() => {\n    const checkWidth = () => {\n      setIsMobile(window.innerWidth < 1025);\n    };\n\n    queueMicrotask(() => {\n      checkWidth();\n      setHasMounted(true);\n    });\n\n    window.addEventListener(\"resize\", checkWidth);\n\n    return () => {\n      window.removeEventListener(\"resize\", checkWidth);\n    };\n  }, []);\n\n  if (!hasMounted) {\n    return null;\n  }\n\n  return (\n    <div\n      style={{ backgroundColor: config.backgroundColor }}\n      className=\"relative h-screen w-full font-mono text-[0.75vw]\"\n    >\n      {isMobile ? (\n        <ElevateNavbarMobile\n          menuItems={menuItems}\n          cta={cta}\n          duration={durationValue}\n          ease={config.ease}\n          activeColor={config.activeColor}\n          inactiveColor={config.inactiveColor}\n        />\n      ) : (\n        <ElevateNavbarDesktop\n          menuItems={menuItems}\n          cta={cta}\n          navTextDuration={durationValue}\n          ctaDuration={durationValue}\n          dropdownItemOffsetY={config.dropdownItemOffsetY}\n          dropdownPointerDelay={config.dropdownPointerDelay}\n          staggerItems={config.staggerItems}\n          activeColor={config.activeColor}\n          inactiveColor={config.inactiveColor}\n          ease={config.ease}\n          ctaBackground={config.ctaBackground}\n          ctaHoverBackground={config.ctaHoverBackground}\n        />\n      )}\n    </div>\n  );\n}\n\nconst menuItems = [\n  {\n    name: \"Effects\",\n    href: \"#\",\n    isDropdown: true,\n    dropdown: [\n      { title: \"All Effects\", img: \"https://pub-8abee449136941f5b0a1cd2c014534e9.r2.dev/vault-listing-images/assets-images/h-09.jpg\", href: \"#\" },\n      { title: \"Components\", img: \"https://pub-8abee449136941f5b0a1cd2c014534e9.r2.dev/vault-listing-images/assets-images/h-10.jpg\", href: \"#\" },\n      { title: \"WebGL\", img: \"https://pub-8abee449136941f5b0a1cd2c014534e9.r2.dev/vault-listing-images/assets-images/h-11.jpg\", href: \"#\" },\n    ],\n  },\n  {\n    name: \"Tech\",\n    href: \"/tech\",\n    isDropdown: true,\n    dropdown: [\n      { title: \"React Effects\", img: \"https://pub-8abee449136941f5b0a1cd2c014534e9.r2.dev/vault-listing-images/assets-images/h-01.jpg\", href: \"#\" },\n      { title: \"GSAP Effects\", img: \"https://pub-8abee449136941f5b0a1cd2c014534e9.r2.dev/vault-listing-images/assets-images/h-02.jpg\", href: \"#\" },\n      { title: \"Three.js Effects\", img: \"https://pub-8abee449136941f5b0a1cd2c014534e9.r2.dev/vault-listing-images/assets-images/h-03.jpg\", href: \"#\" },\n    ],\n  },\n  {\n    name: \"Extras\",\n    href: \"#\",\n    isDropdown: false,\n    dropdown: null,\n  },\n  {\n    name: \"Docs\",\n    href: \"#\",\n    isDropdown: true,\n    dropdown: [\n      { title: \"Introduction\", img: \"https://pub-8abee449136941f5b0a1cd2c014534e9.r2.dev/vault-listing-images/assets-images/h-05.jpg\", href: \"#\" },\n      { title: \"Installation\", img: \"https://pub-8abee449136941f5b0a1cd2c014534e9.r2.dev/vault-listing-images/assets-images/h-06.jpg\", href: \"#\" },\n      { title: \"CLI\", img: \"https://pub-8abee449136941f5b0a1cd2c014534e9.r2.dev/vault-listing-images/assets-images/h-07.jpg\", href: \"#\" },\n    ],\n  },\n];\n\nconst cta = {\n  label: \"BUILT W/ HYPERIUX\",\n  href: \"#\",\n};\n"
    },
    {
      "path": "ElevateDesktopNav.tsx",
      "type": "registry:component",
      "target": "src/components/effects/elevate-navbar/ElevateDesktopNav.tsx",
      "content": "\"use client\";\n\nimport { useCallback, useEffect, useLayoutEffect, useRef, useState } from \"react\";\nimport gsap from \"gsap\";\nimport { ChevronDown, ChevronRight } from \"lucide-react\";\n\nconst DEFAULT_MENU_ITEMS = [\n  {\n    name: \"Effects\",\n    href: \"#\",\n    isDropdown: true,\n    dropdown: [\n      { title: \"All Effects\", img: \"https://pub-8abee449136941f5b0a1cd2c014534e9.r2.dev/vault-listing-images/assets-images/h-09.jpg\", href: \"#\" },\n      { title: \"Components\", img: \"https://pub-8abee449136941f5b0a1cd2c014534e9.r2.dev/vault-listing-images/assets-images/h-10.jpg\", href: \"#\" },\n      { title: \"WebGL\", img: \"https://pub-8abee449136941f5b0a1cd2c014534e9.r2.dev/vault-listing-images/assets-images/h-11.jpg\", href: \"#\" },\n    ],\n  },\n  {\n    name: \"Tech\",\n    href: \"/tech\",\n    isDropdown: true,\n    dropdown: [\n      { title: \"React Effects\", img: \"https://pub-8abee449136941f5b0a1cd2c014534e9.r2.dev/vault-listing-images/assets-images/h-01.jpg\", href: \"#\" },\n      { title: \"GSAP Effects\", img: \"https://pub-8abee449136941f5b0a1cd2c014534e9.r2.dev/vault-listing-images/assets-images/h-02.jpg\", href: \"#\" },\n      { title: \"Three.js Effects\", img: \"https://pub-8abee449136941f5b0a1cd2c014534e9.r2.dev/vault-listing-images/assets-images/h-03.jpg\", href: \"#\" },\n    ],\n  },\n  {\n    name: \"Extras\",\n    href: \"#\",\n    isDropdown: false,\n    dropdown: null,\n  },\n  {\n    name: \"Docs\",\n    href: \"#\",\n    isDropdown: true,\n    dropdown: [\n      { title: \"Introduction\", img: \"https://pub-8abee449136941f5b0a1cd2c014534e9.r2.dev/vault-listing-images/assets-images/h-05.jpg\", href: \"#\" },\n      { title: \"Installation\", img: \"https://pub-8abee449136941f5b0a1cd2c014534e9.r2.dev/vault-listing-images/assets-images/h-06.jpg\", href: \"#\" },\n      { title: \"CLI\", img: \"https://pub-8abee449136941f5b0a1cd2c014534e9.r2.dev/vault-listing-images/assets-images/h-07.jpg\", href: \"#\" },\n    ],\n  },\n];\n\nconst DROPDOWN_ITEM_OFFSET_Y = -8;\nconst NAV_TEXT_DURATION = 0.35;\nconst CTA_DURATION = 0.4;\nconst DROPDOWN_POINTER_DELAY = 0.03;\n\nconst DIMMED_TEXT_COLOR = \"rgba(255,255,255,0.5)\";\nconst ACTIVE_TEXT_COLOR = \"rgba(255,255,255,1)\";\nconst DEFAULT_CTA_BACKGROUND = \"#fff\";\nconst HOVER_CTA_BACKGROUND = \"#000\";\n\nconst DEFAULT_CTA = {\n  label: \"BUILT W/ HYPERIUX\",\n  href: \"#\",\n};\n\nexport interface ElevateDropdownItem {\n  title: string;\n  img: string;\n  href: string;\n}\n\nexport interface ElevateMenuItem {\n  name: string;\n  href: string;\n  isDropdown: boolean;\n  dropdown?: ElevateDropdownItem[] | null;\n}\n\nexport interface ElevateCta {\n  label: string;\n  href: string;\n}\n\ninterface ElevateTextSwapData {\n  defaultText: Element | null;\n  hoverText: Element | null;\n}\n\ninterface ElevateNavbarDesktopProps {\n  menuItems?: ElevateMenuItem[];\n  cta?: ElevateCta;\n  navTextDuration?: number;\n  ctaDuration?: number;\n  activeColor?: string;\n  inactiveColor?: string;\n  ease?: string;\n  dropdownItemOffsetY?: number;\n  dropdownPointerDelay?: number;\n  staggerItems?: boolean;\n  ctaBackground?: string;\n  ctaHoverBackground?: string;\n}\n\nexport function ElevateNavbarDesktop({\n  menuItems = DEFAULT_MENU_ITEMS,\n  cta = DEFAULT_CTA,\n  navTextDuration = NAV_TEXT_DURATION,\n  ctaDuration = CTA_DURATION,\n  activeColor = ACTIVE_TEXT_COLOR,\n  inactiveColor = DIMMED_TEXT_COLOR,\n  ease = \"power2.out\",\n  dropdownItemOffsetY = DROPDOWN_ITEM_OFFSET_Y,\n  dropdownPointerDelay = DROPDOWN_POINTER_DELAY,\n  staggerItems = true,\n  ctaBackground = DEFAULT_CTA_BACKGROUND,\n  ctaHoverBackground = HOVER_CTA_BACKGROUND,\n}: ElevateNavbarDesktopProps) {\n  const navWrapRef = useRef<HTMLDivElement | null>(null);\n  const navLinksRef = useRef<(HTMLAnchorElement | null)[]>([]);\n  const ctaRef = useRef<HTMLAnchorElement | null>(null);\n  const dropdownRef = useRef<HTMLDivElement | null>(null);\n  const dropdownItemsRef = useRef<(HTMLDivElement | null)[]>([]);\n  const dropdownTextDataRef = useRef<(ElevateTextSwapData | null)[]>([]);\n  const linkDataRef = useRef<(ElevateTextSwapData | null)[]>([]);\n\n  const activeDropdownIndexRef = useRef<number | null>(null);\n  const isPointerInsideDropdownRef = useRef(false);\n  const hideCallRef = useRef<gsap.core.Tween | null>(null);\n  const switchTweenRef = useRef<gsap.core.Tween | null>(null);\n  const itemTweenRef = useRef<gsap.core.Timeline | gsap.core.Tween | null>(null);\n\n  const [renderedDropdownIndex, setRenderedDropdownIndexState] = useState<number | null>(null);\n  const [activeChevronIndex, setActiveChevronIndex] = useState<number | null>(null);\n  const reduceMotion = useCallback(\n    () =>\n      typeof window !== \"undefined\" &&\n      window.matchMedia?.(\"(prefers-reduced-motion: reduce)\")?.matches === true,\n    []\n  );\n\n  const killHideCall = useCallback(() => {\n    if (!hideCallRef.current) return;\n\n    hideCallRef.current.kill();\n    hideCallRef.current = null;\n  }, []);\n\n  const killSwitchTween = useCallback(() => {\n    if (!switchTweenRef.current) return;\n\n    switchTweenRef.current.kill();\n    switchTweenRef.current = null;\n  }, []);\n\n  const killItemTween = useCallback(() => {\n    if (!itemTweenRef.current) return;\n\n    itemTweenRef.current.kill();\n    itemTweenRef.current = null;\n  }, []);\n\n  const getCurrentDropdownItems = useCallback(() => {\n    return dropdownItemsRef.current.filter(Boolean) as HTMLDivElement[];\n  }, []);\n\n  const setRenderedDropdownIndex = useCallback((index: number | null) => {\n    dropdownItemsRef.current = [];\n    dropdownTextDataRef.current = [];\n    setRenderedDropdownIndexState(index);\n  }, []);\n\n  const showWrapper = useCallback(() => {\n    if (!dropdownRef.current) return;\n\n    gsap.set(dropdownRef.current, {\n      autoAlpha: 1,\n      pointerEvents: \"auto\",\n    });\n  }, []);\n\n  const hideWrapper = useCallback(() => {\n    if (!dropdownRef.current) return;\n\n    gsap.set(dropdownRef.current, {\n      autoAlpha: 0,\n      pointerEvents: \"none\",\n    });\n  }, []);\n\n  const setNavVisualState = useCallback((activeIndex: number | null) => {\n    navLinksRef.current.forEach((linkElement, index) => {\n      if (!linkElement) return;\n\n      const color =\n        activeIndex !== null && index !== activeIndex\n          ? inactiveColor\n          : activeColor;\n\n      if (reduceMotion()) {\n        gsap.set(linkElement, { color });\n        return;\n      }\n\n      gsap.to(linkElement, {\n        color,\n        duration: navTextDuration,\n        ease,\n        overwrite: true,\n      });\n    });\n  }, [activeColor, ease, inactiveColor, navTextDuration, reduceMotion]);\n\n  const animateTextSwap = useCallback((item: ElevateTextSwapData | null, isEntering: boolean) => {\n    if (!item) return;\n\n    if (reduceMotion()) {\n      gsap.set(item.defaultText, { yPercent: 0 });\n      gsap.set(item.hoverText, { yPercent: 100 });\n      return;\n    }\n\n    gsap\n      .timeline({\n        defaults: {\n          duration: navTextDuration,\n          ease,\n          overwrite: true,\n        },\n      })\n      .to(item.defaultText, { yPercent: isEntering ? -100 : 0 }, 0)\n      .to(item.hoverText, { yPercent: isEntering ? 0 : 100 }, 0);\n  }, [ease, navTextDuration, reduceMotion]);\n\n  const initDropdownItemText = useCallback(() => {\n    dropdownTextDataRef.current = dropdownItemsRef.current.map((itemElement) => {\n      if (!itemElement) return null;\n\n      return {\n        defaultText: itemElement.querySelector(\"[data-default]\"),\n        hoverText: itemElement.querySelector(\"[data-hover]\"),\n      };\n    });\n\n    dropdownTextDataRef.current.forEach((item) => {\n      if (!item) return;\n\n      gsap.set(item.defaultText, { yPercent: 0 });\n      gsap.set(item.hoverText, { yPercent: 100 });\n    });\n  }, []);\n\n  const animateDropdownItemsIn = useCallback(() => {\n    const dropdownItems = getCurrentDropdownItems();\n\n    if (!dropdownItems.length) return;\n\n    killItemTween();\n    gsap.killTweensOf(dropdownItems);\n\n    if (reduceMotion()) {\n      gsap.set(dropdownItems, {\n        opacity: 1,\n        y: 0,\n        pointerEvents: \"auto\",\n      });\n      return;\n    }\n\n    gsap.set(dropdownItems, {\n      opacity: 0,\n      y: dropdownItemOffsetY,\n      pointerEvents: \"auto\",\n    });\n\n    itemTweenRef.current = gsap.timeline({\n      overwrite: true,\n    });\n\n    itemTweenRef.current.to(dropdownItems, {\n      y: 0,\n      opacity: 1,\n      duration: navTextDuration,\n      stagger: staggerItems ? 0.025 : 0,\n      ease,\n    });\n  }, [dropdownItemOffsetY, ease, getCurrentDropdownItems, killItemTween, navTextDuration, reduceMotion, staggerItems]);\n\n  const animateDropdownItemsOut = useCallback(\n    (onComplete?: () => void) => {\n      const dropdownItems = getCurrentDropdownItems();\n\n      if (!dropdownItems.length) {\n        onComplete?.();\n        return;\n      }\n\n      killItemTween();\n      gsap.killTweensOf(dropdownItems);\n\n      if (reduceMotion()) {\n        gsap.set(dropdownItems, {\n          y: dropdownItemOffsetY,\n          opacity: 0,\n          pointerEvents: \"none\",\n        });\n        onComplete?.();\n        return;\n      }\n\n      itemTweenRef.current = gsap.timeline({\n        overwrite: true,\n        onStart: () => {\n          gsap.set(dropdownItems, {\n            pointerEvents: \"none\",\n          });\n        },\n        onComplete,\n      });\n\n      itemTweenRef.current.to(dropdownItems, {\n        y: dropdownItemOffsetY,\n        opacity: 0,\n        duration: navTextDuration,\n        stagger: staggerItems ? 0.015 : 0,\n        ease,\n      });\n    },\n    [dropdownItemOffsetY, ease, getCurrentDropdownItems, killItemTween, navTextDuration, reduceMotion, staggerItems]\n  );\n\n  const closeDropdown = useCallback(() => {\n    killHideCall();\n    killSwitchTween();\n\n    activeDropdownIndexRef.current = null;\n    setActiveChevronIndex(null);\n\n    animateDropdownItemsOut(() => {\n      if (\n        activeDropdownIndexRef.current !== null ||\n        isPointerInsideDropdownRef.current\n      ) {\n        return;\n      }\n\n      setRenderedDropdownIndex(null);\n      hideWrapper();\n    });\n  }, [\n    animateDropdownItemsOut,\n    hideWrapper,\n    killHideCall,\n    killSwitchTween,\n    setRenderedDropdownIndex,\n  ]);\n\n  const openDropdownForIndex = useCallback(\n    (index: number) => {\n      const menuItem = menuItems[index];\n\n      killHideCall();\n      killSwitchTween();\n\n      if (!menuItem?.isDropdown) return;\n\n      activeDropdownIndexRef.current = index;\n      isPointerInsideDropdownRef.current = false;\n\n      showWrapper();\n\n      if (renderedDropdownIndex === null) {\n        setRenderedDropdownIndex(index);\n        return;\n      }\n\n      if (renderedDropdownIndex === index) {\n        const dropdownItems = getCurrentDropdownItems();\n\n        if (!dropdownItems.length) return;\n\n        killItemTween();\n        gsap.killTweensOf(dropdownItems);\n\n        if (reduceMotion()) {\n          gsap.set(dropdownItems, {\n            y: 0,\n            opacity: 1,\n            pointerEvents: \"auto\",\n          });\n          return;\n        }\n\n        gsap.set(dropdownItems, {\n          pointerEvents: \"auto\",\n        });\n\n        itemTweenRef.current = gsap.to(dropdownItems, {\n          y: 0,\n          opacity: 1,\n          duration: navTextDuration,\n          stagger: 0.02,\n          ease,\n          overwrite: true,\n        });\n\n        return;\n      }\n\n      switchTweenRef.current = gsap.delayedCall(0, () => {\n        animateDropdownItemsOut(() => {\n          if (activeDropdownIndexRef.current === null) return;\n\n          setRenderedDropdownIndex(activeDropdownIndexRef.current);\n        });\n      });\n    },\n    [\n      animateDropdownItemsOut,\n      ease,\n      getCurrentDropdownItems,\n      killHideCall,\n      killItemTween,\n      killSwitchTween,\n      menuItems,\n      navTextDuration,\n      reduceMotion,\n      renderedDropdownIndex,\n      setRenderedDropdownIndex,\n      showWrapper,\n    ]\n  );\n\n  const scheduleCloseDropdown = useCallback(() => {\n    killHideCall();\n\n    if (reduceMotion()) {\n      if (isPointerInsideDropdownRef.current) return;\n      closeDropdown();\n      return;\n    }\n\n    hideCallRef.current = gsap.delayedCall(dropdownPointerDelay, () => {\n      if (isPointerInsideDropdownRef.current) return;\n\n      closeDropdown();\n    });\n  }, [closeDropdown, dropdownPointerDelay, killHideCall, reduceMotion]);\n\n  const onHeaderMouseEnter = useCallback(() => {\n    killHideCall();\n  }, [killHideCall]);\n\n  const onHeaderMouseLeave = useCallback(() => {\n    killHideCall();\n    killSwitchTween();\n\n    activeDropdownIndexRef.current = null;\n    isPointerInsideDropdownRef.current = false;\n\n    setActiveChevronIndex(null);\n    setNavVisualState(null);\n\n    animateDropdownItemsOut(() => {\n      setRenderedDropdownIndex(null);\n      hideWrapper();\n    });\n  }, [\n    animateDropdownItemsOut,\n    hideWrapper,\n    killHideCall,\n    killSwitchTween,\n    setNavVisualState,\n    setRenderedDropdownIndex,\n  ]);\n\n  const onNavItemEnter = useCallback(\n    (index: number) => {\n      const item = linkDataRef.current[index];\n\n      if (!item) return;\n\n      killHideCall();\n      animateTextSwap(item, true);\n      setNavVisualState(index);\n\n      if (menuItems[index]?.isDropdown) {\n        setActiveChevronIndex(index);\n        activeDropdownIndexRef.current = index;\n        openDropdownForIndex(index);\n        return;\n      }\n\n      setActiveChevronIndex(null);\n      activeDropdownIndexRef.current = null;\n      closeDropdown();\n    },\n    [\n      animateTextSwap,\n      closeDropdown,\n      killHideCall,\n      menuItems,\n      openDropdownForIndex,\n      setNavVisualState,\n    ]\n  );\n\n  const onNavItemLeave = useCallback(\n    (index: number) => {\n      const item = linkDataRef.current[index];\n\n      if (!item) return;\n\n      animateTextSwap(item, false);\n\n      if (menuItems[index]?.isDropdown) {\n        return;\n      }\n\n      setNavVisualState(null);\n    },\n    [animateTextSwap, menuItems, setNavVisualState]\n  );\n\n  const onCtaHover = useCallback((isEntering: boolean = true) => {\n    const ctaElement = ctaRef.current;\n\n    if (!ctaElement) return;\n\n    const defaultText = ctaElement.querySelector(\"[data-default]\");\n    const hoverText = ctaElement.querySelector(\"[data-hover]\");\n\n    if (reduceMotion()) {\n      gsap.set(ctaElement, {\n        backgroundColor: isEntering\n          ? ctaHoverBackground\n          : ctaBackground,\n        color: isEntering ? \"#fff\" : \"#000\",\n      });\n      gsap.set(defaultText, { yPercent: 0 });\n      gsap.set(hoverText, { yPercent: 100 });\n      return;\n    }\n\n    gsap\n      .timeline({\n        defaults: {\n          duration: ctaDuration,\n          ease,\n          overwrite: true,\n        },\n      })\n      .to(\n        ctaElement,\n        {\n          backgroundColor: isEntering\n            ? ctaHoverBackground\n            : ctaBackground,\n        },\n        0\n      )\n      .to(defaultText, { yPercent: isEntering ? -100 : 0 }, 0)\n      .to(hoverText, { yPercent: isEntering ? 0 : 100 }, 0);\n  }, [ctaBackground, ctaDuration, ctaHoverBackground, ease, reduceMotion]);\n\n  const onDropdownMouseEnter = useCallback(() => {\n    isPointerInsideDropdownRef.current = true;\n    killHideCall();\n    showWrapper();\n  }, [killHideCall, showWrapper]);\n\n  const onDropdownMouseLeave = useCallback(() => {\n    isPointerInsideDropdownRef.current = false;\n    scheduleCloseDropdown();\n  }, [scheduleCloseDropdown]);\n\n  const onDropdownItemEnter = useCallback(\n    (index: number) => {\n      animateTextSwap(dropdownTextDataRef.current[index], true);\n    },\n    [animateTextSwap]\n  );\n\n  const onDropdownItemLeave = useCallback(\n    (index: number) => {\n      animateTextSwap(dropdownTextDataRef.current[index], false);\n    },\n    [animateTextSwap]\n  );\n\n  useLayoutEffect(() => {\n    const context = gsap.context(() => {\n      linkDataRef.current = navLinksRef.current.map((linkElement) => {\n        if (!linkElement) return null;\n\n        return {\n          defaultText: linkElement.querySelector(\"[data-default]\"),\n          hoverText: linkElement.querySelector(\"[data-hover]\"),\n        };\n      });\n\n      linkDataRef.current.forEach((item) => {\n        if (!item) return;\n\n        gsap.set(item.defaultText, { yPercent: 0 });\n        gsap.set(item.hoverText, { yPercent: 100 });\n      });\n\n      if (ctaRef.current) {\n        const defaultText = ctaRef.current.querySelector(\"[data-default]\");\n        const hoverText = ctaRef.current.querySelector(\"[data-hover]\");\n\n        gsap.set(defaultText, { yPercent: 0 });\n        gsap.set(hoverText, { yPercent: 100 });\n      }\n\n      hideWrapper();\n    }, navWrapRef);\n\n    return () => context.revert();\n  }, [hideWrapper]);\n\n  useLayoutEffect(() => {\n    if (renderedDropdownIndex === null) return;\n\n    initDropdownItemText();\n    animateDropdownItemsIn();\n  }, [animateDropdownItemsIn, initDropdownItemText, renderedDropdownIndex]);\n\n  useEffect(() => {\n    return () => {\n      killHideCall();\n      killSwitchTween();\n      killItemTween();\n    };\n  }, [killHideCall, killItemTween, killSwitchTween]);\n\n  const dropdownItems =\n    renderedDropdownIndex !== null\n      ? menuItems[renderedDropdownIndex]?.dropdown || []\n      : [];\n\n  return (\n    <div\n      ref={navWrapRef}\n      onMouseEnter={onHeaderMouseEnter}\n      onMouseLeave={onHeaderMouseLeave}\n      className=\"absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 rounded-[0.55vw] bg-[#363737] text-[0.98vw]\"\n    >\n      <div className=\"relative flex h-full w-full items-center gap-[1.95vw] p-[0.39vw] pl-[1.3vw]\">\n        <div className=\"flex h-full items-center gap-[1.95vw]\">\n          {menuItems.map((item, index) => {\n            const isDropdownActive = activeChevronIndex === index;\n\n            return (\n              <a\n                key={item.name}\n                ref={(element) => {\n                  navLinksRef.current[index] = element;\n                }}\n                href={item.href}\n                className=\"relative flex items-center gap-[0.32vw] py-[0.65vw] text-[0.98vw] uppercase leading-none\"\n                style={{ color: inactiveColor }}\n                onMouseEnter={() => onNavItemEnter(index)}\n                onMouseLeave={() => onNavItemLeave(index)}\n              >\n                <div className=\"relative overflow-hidden\">\n                  <span data-default className=\"block\">\n                    {item.name}\n                  </span>\n\n                  <span\n                    data-hover\n                    className=\"absolute inset-0 flex items-center\"\n                  >\n                    {item.name}\n                  </span>\n                </div>\n\n                {item.isDropdown && (\n                  <ChevronDown\n                    className={`h-[1.3vw] w-[1.3vw] shrink-0 transition-transform duration-100 ease-out motion-reduce:rotate-0 motion-reduce:transition-none ${\n                      isDropdownActive ? \"-rotate-180\" : \"rotate-0\"\n                    }`}\n                  />\n                )}\n              </a>\n            );\n          })}\n        </div>\n\n        <a\n          ref={ctaRef}\n          href={cta.href}\n          className=\"relative overflow-hidden rounded-[0.26vw] bg-white px-[0.65vw] py-[0.65vw] text-[0.98vw] leading-none text-black\"\n          onMouseEnter={() => onCtaHover(true)}\n          onMouseLeave={() => onCtaHover(false)}\n        >\n          <span data-default className=\"block\">\n            {cta.label}\n          </span>\n\n          <span\n            data-hover\n            className=\"absolute inset-0 flex items-center justify-center text-white\"\n          >\n            {cta.label}\n          </span>\n        </a>\n\n        <div\n          ref={dropdownRef}\n          onMouseEnter={onDropdownMouseEnter}\n          onMouseLeave={onDropdownMouseLeave}\n          className=\"absolute left-0 top-full h-fit w-full pt-[0.46vw]\"\n        >\n          <div className=\"space-y-[0.52vw]\">\n            {dropdownItems.map((item, index) => (\n              <div\n                key={`${renderedDropdownIndex}-${item.title}`}\n                ref={(element) => {\n                  dropdownItemsRef.current[index] = element;\n                }}\n                className=\"link-btns\"\n              >\n                <a\n                  href={item.href}\n                  onMouseEnter={() => onDropdownItemEnter(index)}\n                  onMouseLeave={() => onDropdownItemLeave(index)}\n                  className=\"flex items-center justify-between rounded-[0.55vw] bg-[#363737] p-[0.52vw] text-[0.98vw] text-white transition-all duration-300 hover:scale-[1.02] hover:bg-white hover:text-black! motion-reduce:scale-100 motion-reduce:bg-[#363737] motion-reduce:text-white! motion-reduce:transition-none\"\n                >\n                  <div className=\"flex items-center gap-[1.95vw]\">\n                    <div className=\"size-[6.5vw] overflow-hidden rounded-[0.55vw]\">\n                      <img\n                        src={item.img}\n                        alt={item.title}\n                        width={500}\n                        height={500}\n                        className=\"h-full w-full object-cover\"\n                      />\n                    </div>\n\n                    <div className=\"relative overflow-hidden uppercase leading-none\">\n                      <span data-default className=\"block\">\n                        {item.title}\n                      </span>\n\n                      <span\n                        data-hover\n                        className=\"absolute inset-0 flex items-center\"\n                      >\n                        {item.title}\n                      </span>\n                    </div>\n                  </div>\n\n                  <ChevronRight className=\"mr-[2.6vw] h-[1.3vw] w-[1.3vw]\" />\n                </a>\n              </div>\n            ))}\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n}\n"
    },
    {
      "path": "ElevateMobileNav.tsx",
      "type": "registry:component",
      "target": "src/components/effects/elevate-navbar/ElevateMobileNav.tsx",
      "content": "\"use client\";\n\nimport { useEffect, useRef, useState } from \"react\";\nimport gsap from \"gsap\";\nimport { ChevronDown, Menu, X } from \"lucide-react\";\nimport { useFocusTrap } from \"./useFocusTrap\";\nimport type { ElevateMenuItem, ElevateCta, ElevateDropdownItem } from \"./ElevateDesktopNav\";\n\nconst DEFAULT_MENU_ITEMS: ElevateMenuItem[] = [\n  {\n    name: \"Effects\",\n    href: \"#\",\n    isDropdown: true,\n    dropdown: [\n      { title: \"All Effects\", img: \"https://pub-8abee449136941f5b0a1cd2c014534e9.r2.dev/vault-listing-images/assets-images/h-09.jpg\", href: \"#\" },\n      { title: \"Components\", img: \"https://pub-8abee449136941f5b0a1cd2c014534e9.r2.dev/vault-listing-images/assets-images/h-10.jpg\", href: \"#\" },\n      { title: \"WebGL\", img: \"https://pub-8abee449136941f5b0a1cd2c014534e9.r2.dev/vault-listing-images/assets-images/h-11.jpg\", href: \"#\" },\n    ],\n  },\n  {\n    name: \"Tech\",\n    href: \"/tech\",\n    isDropdown: true,\n    dropdown: [\n      { title: \"React Effects\", img: \"https://pub-8abee449136941f5b0a1cd2c014534e9.r2.dev/vault-listing-images/assets-images/h-01.jpg\", href: \"#\" },\n      { title: \"GSAP Effects\", img: \"https://pub-8abee449136941f5b0a1cd2c014534e9.r2.dev/vault-listing-images/assets-images/h-02.jpg\", href: \"#\" },\n      { title: \"Three.js Effects\", img: \"https://pub-8abee449136941f5b0a1cd2c014534e9.r2.dev/vault-listing-images/assets-images/h-03.jpg\", href: \"#\" },\n    ],\n  },\n  {\n    name: \"Extras\",\n    href: \"#\",\n    isDropdown: false,\n    dropdown: null,\n  },\n  {\n    name: \"Docs\",\n    href: \"#\",\n    isDropdown: true,\n    dropdown: [\n      { title: \"Introduction\", img: \"https://pub-8abee449136941f5b0a1cd2c014534e9.r2.dev/vault-listing-images/assets-images/h-05.jpg\", href: \"#\" },\n      { title: \"Installation\", img: \"https://pub-8abee449136941f5b0a1cd2c014534e9.r2.dev/vault-listing-images/assets-images/h-06.jpg\", href: \"#\" },\n      { title: \"CLI\", img: \"https://pub-8abee449136941f5b0a1cd2c014534e9.r2.dev/vault-listing-images/assets-images/h-07.jpg\", href: \"#\" },\n    ],\n  },\n];\n\nconst DEFAULT_CTA: ElevateCta = {\n  label: \"BUILT W/ HYPERIUX\",\n  href: \"#\",\n};\n\nconst BACKDROP_DURATION = 0.2;\nconst PANEL_OFFSET_Y = -20;\nconst PANEL_OPEN_DURATION = 0.3;\nconst PANEL_CLOSE_DURATION = 0.2;\nconst ACCORDION_DURATION = 0.25;\n\ninterface ElevateNavbarMobileProps {\n  menuItems?: ElevateMenuItem[];\n  cta?: ElevateCta;\n  duration?: number;\n  ease?: string;\n  activeColor?: string;\n  inactiveColor?: string;\n}\n\nexport function ElevateNavbarMobile({\n  menuItems = DEFAULT_MENU_ITEMS,\n  cta = DEFAULT_CTA,\n  duration = 0.35,\n  ease = \"power3.out\",\n  activeColor = \"#ffffff\",\n  inactiveColor = \"rgba(255,255,255,0.85)\",\n}: ElevateNavbarMobileProps) {\n  const [isMenuOpen, setIsMenuOpen] = useState(false);\n  const [activeDropdownIndex, setActiveDropdownIndex] = useState<number | null>(null);\n\n  const panelRef = useRef<HTMLDivElement | null>(null);\n  const backdropRef = useRef<HTMLDivElement | null>(null);\n  const sectionsRef = useRef<(HTMLDivElement | null)[]>([]);\n  const containerRef = useRef<HTMLDivElement | null>(null);\n  const toggleButtonRef = useRef<HTMLButtonElement | null>(null);\n  const reduceMotion = () =>\n    typeof window !== \"undefined\" &&\n    window.matchMedia?.(\"(prefers-reduced-motion: reduce)\")?.matches === true;\n  const motionDuration = Math.max(0.05, Number(duration) || 0.35);\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: () => setIsMenuOpen(false),\n  });\n\n  useEffect(() => {\n    const panelElement = panelRef.current;\n    const backdropElement = backdropRef.current;\n\n    if (!panelElement || !backdropElement) return;\n\n    if (isMenuOpen) {\n      gsap.set(panelElement, {\n        pointerEvents: \"auto\",\n      });\n\n      if (reduceMotion()) {\n        gsap.set(backdropElement, { autoAlpha: 1 });\n        gsap.set(panelElement, {\n          autoAlpha: 1,\n          y: 0,\n        });\n        return;\n      }\n\n      gsap.to(backdropElement, {\n        autoAlpha: 1,\n        duration: motionDuration * 0.6,\n      });\n\n      gsap.fromTo(\n        panelElement,\n        {\n          autoAlpha: 0,\n          y: PANEL_OFFSET_Y,\n        },\n        {\n          autoAlpha: 1,\n          y: 0,\n          duration: motionDuration,\n          ease,\n        }\n      );\n\n      return;\n    }\n\n    if (reduceMotion()) {\n      gsap.set(panelElement, {\n        autoAlpha: 0,\n        y: PANEL_OFFSET_Y,\n        pointerEvents: \"none\",\n      });\n      gsap.set(backdropElement, {\n        autoAlpha: 0,\n      });\n      return;\n    }\n\n    gsap.to(panelElement, {\n      autoAlpha: 0,\n      y: PANEL_OFFSET_Y,\n      duration: motionDuration * 0.7,\n      onComplete: () => {\n        gsap.set(panelElement, {\n          pointerEvents: \"none\",\n        });\n      },\n    });\n\n    gsap.to(backdropElement, {\n      autoAlpha: 0,\n      duration: motionDuration * 0.6,\n    });\n  }, [ease, isMenuOpen, motionDuration]);\n\n  useEffect(() => {\n    sectionsRef.current.forEach((sectionElement, index) => {\n      if (!sectionElement) return;\n\n      const isSectionOpen = activeDropdownIndex === index;\n\n      if (reduceMotion()) {\n        gsap.set(sectionElement, {\n          height: isSectionOpen ? sectionElement.scrollHeight : 0,\n          autoAlpha: isSectionOpen ? 1 : 0,\n        });\n        return;\n      }\n\n      gsap.to(sectionElement, {\n        height: isSectionOpen ? sectionElement.scrollHeight : 0,\n        autoAlpha: isSectionOpen ? 1 : 0,\n        duration: motionDuration,\n        ease,\n      });\n    });\n  }, [activeDropdownIndex, ease, motionDuration]);\n\n  return (\n    <div\n      ref={containerRef}\n      className=\"fixed h-fit left-1/2 max-[1025px]:top-[10%] max-md:top-[31%] -translate-x-1/2 z-999\"\n    >\n      <div\n        ref={backdropRef}\n        onClick={() => setIsMenuOpen(false)}\n        className=\"fixed inset-0 \"\n      />\n\n      <div className=\"flex items-center justify-between gap-[2vw] rounded-[4vw] border border-white/10 bg-[#2f2f2f]/90 px-[3vw] max-[1025px]:py-[1vw] max-md:py-[2vw] backdrop-blur-xl\">\n        <span className=\"px-[2vw] text-[3vw] uppercase tracking-wide text-white/80\">\n          Hyperiux\n        </span>\n\n        <button\n          ref={toggleButtonRef}\n          type=\"button\"\n          onClick={() => setIsMenuOpen((currentValue) => !currentValue)}\n          className=\"flex h-[8vw] w-[8vw] items-center justify-center rounded-[2vw] text-white transition hover:bg-white/10 motion-reduce:bg-transparent motion-reduce:transition-none\"\n          style={{ backgroundColor: isMenuOpen ? activeColor : undefined }}\n          aria-label={isMenuOpen ? \"Close menu\" : \"Open menu\"}\n        >\n          {isMenuOpen ? (\n            <X className=\"max-md:h-[4.5vw] max-md:w-[4.5vw] max-[1025px]:w-[3.5vw] max-[1025px]:h-[3.5vw]\" />\n          ) : (\n            <Menu className=\"max-md:h-[4.5vw] max-md:w-[4.5vw] max-[1025px]:w-[3.5vw] max-[1025px]:h-[3.5vw]\" />\n          )}\n        </button>\n      </div>\n\n      <div\n        ref={panelRef}\n        className=\"mt-[2vw] w-[92vw] rounded-[4vw] border border-white/10 bg-[#2f2f2f]/95 p-[2vw] backdrop-blur-xl\"\n        style={{\n          pointerEvents: \"none\",\n          opacity: 0,\n        }}\n      >\n        <div className=\"space-y-[1vw]\">\n          {menuItems.map((item, index) => {\n            const hasDropdown = Boolean(item.dropdown);\n            const isDropdownOpen = activeDropdownIndex === index;\n\n            if (!hasDropdown) {\n              return (\n                <a\n                  key={item.name}\n                  href={item.href}\n                  onClick={() => setIsMenuOpen(false)}\n                  className=\"flex items-center justify-between rounded-[2.5vw] px-[3vw] py-[2.5vw] max-[1025px]:text-[2.5vw] max-md:text-[3vw] uppercase text-white/85 transition hover:bg-white/10 motion-reduce:bg-transparent motion-reduce:transition-none\"\n                  style={{ color: inactiveColor }}\n                >\n                  {item.name}\n                </a>\n              );\n            }\n\n            return (\n              <div key={item.name}>\n                <button\n                  type=\"button\"\n                  onClick={() =>\n                    setActiveDropdownIndex((currentIndex) =>\n                      currentIndex === index ? null : index\n                    )\n                  }\n                  className=\"flex w-full items-center justify-between rounded-[2.5vw] px-[3vw] py-[2.5vw] max-[1025px]:text-[2.5vw]! max-md:text-[3vw]! uppercase text-white/85 transition hover:bg-white/10 motion-reduce:bg-transparent motion-reduce:transition-none\"\n                  style={{ color: inactiveColor }}\n                >\n                  {item.name}\n\n                  <ChevronDown\n                    className={`h-[3.5vw] w-[3.5vw] transition-transform motion-reduce:rotate-0 motion-reduce:transition-none ${\n                      isDropdownOpen ? \"rotate-180\" : \"\"\n                    }`}\n                  />\n                </button>\n\n                <div\n                  ref={(element) => {\n                    sectionsRef.current[index] = element;\n                  }}\n                  className=\"overflow-hidden pl-[2vw]\"\n                  style={{\n                    height: 0,\n                    opacity: 0,\n                  }}\n                >\n                  <div className=\"space-y-[2vw] pt-[1vw]\">\n                    {(item.dropdown as ElevateDropdownItem[]).map((dropdownItem) => (\n                      <a\n                        key={dropdownItem.title}\n                        href={dropdownItem.href}\n                        onClick={() => setIsMenuOpen(false)}\n                        className=\"flex items-center gap-[3vw] rounded-[2vw] bg-white/5 p-[2vw] max-md:text-[2.8vw] max-[1025px]:text-[2vw]  uppercase text-white/80 transition hover:bg-white/10 motion-reduce:bg-white/5 motion-reduce:transition-none\"\n                        style={{ color: inactiveColor }}\n                      >\n                        <div className=\"relative h-[10vw] w-[10vw] overflow-hidden rounded-[2vw] bg-white/25\">\n                          <img\n                            src={dropdownItem.img}\n                            alt={dropdownItem.title}\n                            width={80}\n                            height={80}\n                            className=\"h-full w-full object-cover\"\n                          />\n                        </div>\n\n                        <span className=\"flex-1\">{dropdownItem.title}</span>\n                      </a>\n                    ))}\n                  </div>\n                </div>\n              </div>\n            );\n          })}\n\n          <div className=\"pt-[3vw]\">\n            <a\n              href={cta.href}\n              onClick={() => setIsMenuOpen(false)}\n              className=\"block w-full max-md:py-[3vw] rounded-[3vw] bg-white py-[1.5vw] text-center max-md:text-[3vw] max-[1025px]:text-[2.5vw] font-semibold text-black\"\n            >\n              {cta.label}\n            </a>\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n}\n"
    },
    {
      "path": "useFocusTrap.ts",
      "type": "registry:component",
      "target": "src/components/effects/elevate-navbar/useFocusTrap.ts",
      "content": "\"use client\";\n\nimport { useEffect, useRef, type RefObject } 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?: HTMLElement | null): boolean => {\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?: HTMLElement | null): HTMLElement[] => {\n  if (!container) {\n    return [];\n  }\n\n  return (\n    Array.from(container.querySelectorAll(FOCUSABLE_SELECTOR)) as HTMLElement[]\n  ).filter(isVisible);\n};\n\ninterface UseFocusTrapParams {\n  active: boolean;\n  containerRef: RefObject<HTMLElement | null>;\n  initialFocusRef?: RefObject<HTMLElement | null>;\n  onEscape?: () => void;\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}: UseFocusTrapParams) {\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: KeyboardEvent) => {\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"
    }
  ]
}