{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "slug": "fullscreen-navbar",
  "name": "Fullscreen Navbar",
  "title": "Fullscreen Navbar",
  "type": "registry:ui",
  "description": "An editorial fullscreen navbar with numbered navigation, smooth dropdowns, spring-driven link entrances, and a Dev Club spotlight panel.",
  "summary": "An image-friendly header opens into a full-screen editorial menu. The curtain slides in from the right on desktop and from above on narrow screens. Dev Club navigation, a featured component, connect links and a primary action are synchronized with scoped GSAP timelines. Customize typography content, colors, reveal timing, spring stiffness and damping.",
  "category": "navbar",
  "tags": [
    "navbar",
    "fullscreen",
    "menu",
    "dropdown",
    "spring",
    "responsive",
    "gsap"
  ],
  "dependencies": [
    "gsap",
    "@gsap/react",
    "@radix-ui/react-dialog",
    "lucide-react",
    "clsx",
    "tailwind-merge"
  ],
  "version": "1.0.0",
  "createdDate": "2026-10-04",
  "updatedDate": "2026-10-04",
  "interactive": true,
  "supportsColor": false,
  "highlights": [
    "Right-to-left desktop curtain and top-to-bottom mobile curtain",
    "Numbered Dev Club navigation with accessible nested groups",
    "Physically damped spring response with adjustable stiffness and damping",
    "Featured component, connect links and primary action",
    "Full-screen scrollable mobile layout with a persistent header",
    "Radix dialog focus management, Escape to close and reduced-motion fallback",
    "Profile-style moving hover highlight with spring-driven labels and arrows"
  ],
  "anatomy": [
    "Brand header, primary action and menu trigger",
    "Radix modal dialog and GSAP reveal curtain",
    "Numbered navigation links and expanding submenus",
    "Spotlight image and community links",
    "Persistent close control and scrollable menu body"
  ],
  "physics": {
    "engine": "GSAP",
    "description": "A scoped reversible timeline coordinates curtain transforms, link entrances and secondary content. A damped oscillator function maps stiffness and damping to GSAP easing for link entrances and chevrons. Submenus animate measured auto height. Match-media contexts clean up animations, and ResizeObserver selects the layout from container width. A shared highlight uses quickTo setters to follow hovered or keyboard-focused links, and resize observation keeps it aligned during submenu expansion.",
    "parameters": [
      {
        "label": "Curtain",
        "value": "0.8s · power3.inOut"
      },
      {
        "label": "Spring stiffness",
        "value": "220"
      },
      {
        "label": "Spring damping",
        "value": "26"
      },
      {
        "label": "Link stagger",
        "value": "0.045s"
      }
    ]
  },
  "accessibility": {
    "role": "dialog / navigation",
    "keyboard": [
      {
        "key": "Tab / Shift+Tab",
        "description": "Navigate the modal focus loop and visible links."
      },
      {
        "key": "Enter / Space",
        "description": "Open and close the menu or toggle a nested group."
      },
      {
        "key": "Escape",
        "description": "Close the menu and return focus to the trigger."
      }
    ],
    "aria": "Radix provides modal dialog semantics, background isolation, focus trapping and focus restoration. Each submenu has aria-expanded, aria-controls, a labeled region and an inert closed state.",
    "reducedMotion": "Curtain, stagger and submenu animations resolve immediately when prefers-reduced-motion is enabled."
  },
  "guidelines": {
    "recommended": [
      "Image-led Dev Club landing pages",
      "Editorial navigation with nested component and documentation links",
      "Custom featured content and community destinations"
    ],
    "bestPractices": [
      "Use a positioned full-height container when contained is enabled",
      "Provide real destinations and concise link labels",
      "Keep the primary action and contact link useful for your site",
      "Choose contrasting menu, header and accent colors",
      "Use controlled open for application-managed menu state"
    ]
  },
  "props": [
    {
      "name": "brand",
      "type": "string",
      "description": "Brand label.",
      "defaultValue": "devclub"
    },
    {
      "name": "brandHref",
      "type": "string",
      "description": "Brand destination.",
      "defaultValue": "/"
    },
    {
      "name": "logo",
      "type": "ReactNode",
      "description": "Custom brand mark; defaults to a code icon."
    },
    {
      "name": "items",
      "type": "readonly FullscreenNavbarItem[]",
      "description": "Main navigation labels with either a destination or expandable link group.",
      "defaultValue": "FULLSCREEN_NAVBAR_ITEMS"
    },
    {
      "name": "connectLinks",
      "type": "readonly FullscreenNavbarLink[]",
      "description": "Connect links in the secondary column.",
      "defaultValue": "FULLSCREEN_NAVBAR_CONNECT_LINKS"
    },
    {
      "name": "spotlight",
      "type": "FullscreenNavbarSpotlight",
      "description": "Optional featured card with image, title, description, tag and destination."
    },
    {
      "name": "actionLabel",
      "type": "string",
      "description": "Primary action text.",
      "defaultValue": "Start building"
    },
    {
      "name": "actionHref",
      "type": "string",
      "description": "Primary action destination.",
      "defaultValue": "/docs/installation"
    },
    {
      "name": "footerText",
      "type": "string",
      "description": "Menu footer copy.",
      "defaultValue": "Built by Dev Club. Made for your next idea."
    },
    {
      "name": "contactLabel",
      "type": "string",
      "description": "Contact link text.",
      "defaultValue": "Let's build something together."
    },
    {
      "name": "contactHref",
      "type": "string",
      "description": "Contact destination.",
      "defaultValue": "https://github.com/devclub-nstru/component_library/issues"
    },
    {
      "name": "duration",
      "type": "number",
      "description": "Menu reveal duration in seconds, bounded to 0.25–1.5.",
      "defaultValue": "0.8"
    },
    {
      "name": "stiffness",
      "type": "number",
      "description": "Stiffness for the damped spring response of link entrances and dropdown arrows, bounded to 80–500.",
      "defaultValue": "220"
    },
    {
      "name": "damping",
      "type": "number",
      "description": "Damping for the spring response, bounded to 12–50.",
      "defaultValue": "26"
    },
    {
      "name": "stagger",
      "type": "number",
      "description": "Delay between link entrances, bounded to 0–0.12 seconds.",
      "defaultValue": "0.045"
    },
    {
      "name": "background",
      "type": "string",
      "description": "Expanded menu background.",
      "defaultValue": "#f7f5ef"
    },
    {
      "name": "foreground",
      "type": "string",
      "description": "Expanded menu text color.",
      "defaultValue": "#171717"
    },
    {
      "name": "accent",
      "type": "string",
      "description": "Action, active link and focus accent.",
      "defaultValue": "#d34b30"
    },
    {
      "name": "headerForeground",
      "type": "string",
      "description": "Collapsed header text color.",
      "defaultValue": "#ffffff"
    },
    {
      "name": "hoverDropdowns",
      "type": "boolean",
      "description": "Enable desktop mouse hover to open nested groups. Click and keyboard also work.",
      "defaultValue": "true"
    },
    {
      "name": "contained",
      "type": "boolean",
      "description": "Render within a positioned page or preview container. Default fills the viewport.",
      "defaultValue": "false"
    },
    {
      "name": "open",
      "type": "boolean",
      "description": "Optional controlled open state."
    },
    {
      "name": "defaultOpen",
      "type": "boolean",
      "description": "Initial uncontrolled open state.",
      "defaultValue": "false"
    },
    {
      "name": "onOpenChange",
      "type": "(open: boolean) => void",
      "description": "Called when the requested open state changes."
    },
    {
      "name": "className",
      "type": "string",
      "description": "Additional container styling."
    }
  ],
  "files": [
    {
      "name": "fullscreen-navbar.tsx",
      "path": "registry/ui/fullscreen-navbar.tsx",
      "code": "\"use client\";\n\nimport {\n  useCallback,\n  useEffect,\n  useId,\n  useRef,\n  useState,\n  type CSSProperties,\n  type ReactNode,\n} from \"react\";\nimport * as Dialog from \"@radix-ui/react-dialog\";\nimport { useGSAP } from \"@gsap/react\";\nimport gsap from \"gsap\";\nimport { ArrowUpRight, ChevronDown, Code2, Menu, X } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\n\ngsap.registerPlugin(useGSAP);\n\nexport interface FullscreenNavbarLink {\n  label: string;\n  href: string;\n}\n\nexport interface FullscreenNavbarItem {\n  label: string;\n  href?: string;\n  links?: readonly FullscreenNavbarLink[];\n}\n\nexport interface FullscreenNavbarSpotlight {\n  title: string;\n  description: string;\n  tag?: string;\n  image: string;\n  href: string;\n}\n\nexport interface FullscreenNavbarProps {\n  brand?: string;\n  brandHref?: string;\n  logo?: ReactNode;\n  items?: readonly FullscreenNavbarItem[];\n  connectLinks?: readonly FullscreenNavbarLink[];\n  spotlight?: FullscreenNavbarSpotlight;\n  actionLabel?: string;\n  actionHref?: string;\n  footerText?: string;\n  contactLabel?: string;\n  contactHref?: string;\n  duration?: number;\n  stiffness?: number;\n  damping?: number;\n  stagger?: number;\n  background?: string;\n  foreground?: string;\n  accent?: string;\n  headerForeground?: string;\n  hoverDropdowns?: boolean;\n  contained?: boolean;\n  open?: boolean;\n  defaultOpen?: boolean;\n  onOpenChange?: (open: boolean) => void;\n  className?: string;\n}\n\nexport const FULLSCREEN_NAVBAR_ITEMS: readonly FullscreenNavbarItem[] = [\n  {\n    label: \"Components\",\n    links: [\n      { label: \"Explore the library\", href: \"/components\" },\n      { label: \"Animated buttons\", href: \"/components/animated-button\" },\n      { label: \"Navigation bars\", href: \"/components/curved-navbar\" },\n      { label: \"Image loaders\", href: \"/components/grid-image-loader\" },\n    ],\n  },\n  {\n    label: \"Documentation\",\n    links: [\n      { label: \"Introduction\", href: \"/docs/introduction\" },\n      { label: \"Installation\", href: \"/docs/installation\" },\n      { label: \"Theming\", href: \"/docs/theming\" },\n      { label: \"CLI & registry\", href: \"/docs/cli\" },\n    ],\n  },\n  {\n    label: \"Community\",\n    links: [\n      {\n        label: \"Dev Club on GitHub\",\n        href: \"https://github.com/devclub-nstru/component_library\",\n      },\n      {\n        label: \"Report an issue\",\n        href: \"https://github.com/devclub-nstru/component_library/issues\",\n      },\n      {\n        label: \"Contribute\",\n        href: \"https://github.com/devclub-nstru/component_library/blob/main/CONTRIBUTING.md\",\n      },\n    ],\n  },\n  { label: \"Showcase\", href: \"/components\" },\n  { label: \"Our story\", href: \"/docs/introduction\" },\n  {\n    label: \"Source code\",\n    href: \"https://github.com/devclub-nstru/component_library\",\n  },\n];\n\nexport const FULLSCREEN_NAVBAR_CONNECT_LINKS: readonly FullscreenNavbarLink[] =\n  [\n    {\n      label: \"GitHub\",\n      href: \"https://github.com/devclub-nstru/component_library\",\n    },\n    { label: \"Docs\", href: \"/docs\" },\n    { label: \"Dev Club\", href: \"https://devclub.co\" },\n  ];\n\nconst bounded = (value: number, min: number, max: number, fallback: number) =>\n  Number.isFinite(value) ? Math.min(max, Math.max(min, value)) : fallback;\n\nfunction springEase(stiffness: number, damping: number, duration: number) {\n  const frequency = Math.sqrt(stiffness);\n  const ratio = damping / (2 * frequency);\n  const response = (time: number) => {\n    if (ratio < 1) {\n      const rate = frequency * Math.sqrt(1 - ratio * ratio);\n      return (\n        1 -\n        Math.exp((-damping * time) / 2) *\n          (Math.cos(rate * time) +\n            (damping / (2 * rate)) * Math.sin(rate * time))\n      );\n    }\n    if (Math.abs(ratio - 1) < 0.001)\n      return 1 - (1 + frequency * time) * Math.exp(-frequency * time);\n    const delta = Math.sqrt(ratio * ratio - 1);\n    const slow = -frequency * (ratio - delta);\n    const fast = -frequency * (ratio + delta);\n    return (\n      1 -\n      (fast * Math.exp(slow * time) - slow * Math.exp(fast * time)) /\n        (fast - slow)\n    );\n  };\n  const end = response(duration);\n  return (progress: number) =>\n    progress === 1 ? 1 : response(progress * duration) / Math.max(0.001, end);\n}\n\nfunction NavbarHeader({\n  brand,\n  brandHref,\n  logo,\n  actionLabel,\n  actionHref,\n  menuButton,\n  onNavigate,\n}: {\n  brand: string;\n  brandHref: string;\n  logo?: ReactNode;\n  actionLabel: string;\n  actionHref: string;\n  menuButton: ReactNode;\n  onNavigate?: () => void;\n}) {\n  return (\n    <div className=\"flex h-20 w-full items-center justify-between gap-3 px-5 @[760px]/fullnav:px-9\">\n      <a\n        href={brandHref}\n        onClick={onNavigate}\n        className=\"flex min-w-0 items-center gap-2.5 rounded-lg text-xl font-semibold tracking-[-0.04em] focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-(--fullnav-accent)\"\n      >\n        <span\n          aria-hidden=\"true\"\n          className=\"flex size-8 shrink-0 items-center justify-center overflow-hidden rounded-lg\"\n        >\n          {logo ?? <Code2 className=\"size-7\" />}\n        </span>\n        <span className=\"max-w-32 truncate\">{brand}</span>\n      </a>\n      <div className=\"flex shrink-0 items-center gap-2\">\n        <a\n          href={actionHref}\n          onClick={onNavigate}\n          className=\"hidden min-h-10 items-center gap-1.5 rounded-full bg-(--fullnav-accent) px-4 text-xs font-medium text-white focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-(--fullnav-accent) @[480px]/fullnav:flex\"\n        >\n          {actionLabel}\n          <ArrowUpRight aria-hidden=\"true\" className=\"size-3.5\" />\n        </a>\n        {menuButton}\n      </div>\n    </div>\n  );\n}\n\nconst HOVER_CLICK_GRACE_MS = 350;\n\nconst menuButtonClass =\n  \"flex min-h-10 min-w-24 cursor-pointer items-center justify-center gap-2 rounded-full border border-current/15 bg-current/5 px-4 text-xs font-medium backdrop-blur-lg hover:bg-current/10 focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-[var(--fullnav-accent)]\";\n\nfunction NavbarRow({\n  item,\n  index,\n  expanded,\n  onExpand,\n  onNavigate,\n  mobile,\n  duration,\n  stiffness,\n  damping,\n  hoverDropdowns,\n}: {\n  item: FullscreenNavbarItem;\n  index: number;\n  expanded: boolean;\n  onExpand: () => void;\n  onNavigate: () => void;\n  mobile: boolean;\n  duration: number;\n  stiffness: number;\n  damping: number;\n  hoverDropdowns: boolean;\n}) {\n  const rootRef = useRef<HTMLDivElement>(null);\n  const panelRef = useRef<HTMLDivElement>(null);\n  const contentRef = useRef<HTMLDivElement>(null);\n  const arrowRef = useRef<SVGSVGElement>(null);\n  const animateRef = useRef<((next: boolean) => void) | null>(null);\n  const expandedRef = useRef(expanded);\n  const hoverOpenedAtRef = useRef(0);\n  const id = useId();\n  const hasLinks = Boolean(item.links?.length);\n  useEffect(() => {\n    expandedRef.current = expanded;\n  }, [expanded]);\n\n  useGSAP(\n    () => {\n      const panel = panelRef.current;\n      const content = contentRef.current;\n      const arrow = arrowRef.current;\n      if (!panel || !content || !arrow) return;\n      const media = gsap.matchMedia();\n      media.add(\n        { reduced: \"(prefers-reduced-motion: reduce)\", all: \"all\" },\n        (context) => {\n          const reduced = Boolean(context.conditions?.reduced);\n          const animate = context.add(\"animate\", (next: boolean) => {\n            gsap.killTweensOf([panel, arrow, content]);\n            if (reduced) {\n              gsap.set(panel, {\n                height: next ? \"auto\" : 0,\n                autoAlpha: next ? 1 : 0,\n              });\n              gsap.set(arrow, { rotation: next ? 180 : 0 });\n              gsap.set(content, { y: 0, opacity: 1 });\n              return;\n            }\n            gsap.to(panel, {\n              height: next ? \"auto\" : 0,\n              autoAlpha: next ? 1 : 0,\n              duration: duration * 0.55,\n              ease: \"power3.inOut\",\n              overwrite: \"auto\",\n            });\n            gsap.to(arrow, {\n              rotation: next ? 180 : 0,\n              duration: duration * 0.65,\n              ease: springEase(stiffness, damping, duration * 0.65),\n              overwrite: \"auto\",\n            });\n            gsap.fromTo(\n              content,\n              { y: next ? 8 : 0, opacity: next ? 0 : 1 },\n              {\n                y: 0,\n                opacity: next ? 1 : 0,\n                duration: duration * 0.45,\n                ease: \"power3.out\",\n              },\n            );\n          });\n          animateRef.current = (next) => animate(next);\n          animate(expandedRef.current);\n          return () => {\n            animateRef.current = null;\n          };\n        },\n      );\n      return () => media.revert();\n    },\n    {\n      scope: rootRef,\n      dependencies: [duration, stiffness, damping, item.links],\n      revertOnUpdate: true,\n    },\n  );\n\n  useGSAP(\n    () => {\n      animateRef.current?.(expanded);\n    },\n    { scope: rootRef, dependencies: [expanded] },\n  );\n\n  const label = (\n    <>\n      <span\n        aria-hidden=\"true\"\n        className=\"w-7 shrink-0 pt-1 text-[10px] font-normal tabular-nums tracking-wide opacity-35 @[760px]/fullnav:w-9\"\n      >\n        {String(index + 1).padStart(2, \"0\")}\n      </span>\n      <span\n        data-fullnav-label\n        className=\"min-w-0 wrap-break-words text-[clamp(1.65rem,4.8cqw,3.75rem)] font-semibold leading-[1.08] tracking-[-0.055em]\"\n      >\n        {item.label}\n      </span>\n      {hasLinks ? (\n        <ChevronDown\n          ref={arrowRef}\n          data-fullnav-arrow=\"chevron\"\n          aria-hidden=\"true\"\n          className=\"ml-2 size-5 shrink-0 opacity-35 @[760px]/fullnav:size-7\"\n        />\n      ) : (\n        <ArrowUpRight\n          aria-hidden=\"true\"\n          data-fullnav-arrow=\"link\"\n          className=\"ml-2 size-5 shrink-0 opacity-0\"\n        />\n      )}\n    </>\n  );\n  const rowClass = cn(\n    \"group relative z-10 flex w-full items-center rounded-lg py-2.5 text-left focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--fullnav-accent)]\",\n    expanded && \"text-[var(--fullnav-accent)]\",\n  );\n  return (\n    <div ref={rootRef} data-fullnav-entry>\n      {hasLinks ? (\n        <button\n          type=\"button\"\n          data-fullnav-hit\n          id={`trigger-${id}`}\n          aria-expanded={expanded}\n          aria-controls={`panel-${id}`}\n          onClick={(event) => {\n            const hoverOpenedAt = hoverOpenedAtRef.current;\n            hoverOpenedAtRef.current = 0;\n            if (\n              event.detail > 0 &&\n              hoverOpenedAt > 0 &&\n              performance.now() - hoverOpenedAt < HOVER_CLICK_GRACE_MS\n            ) {\n              return;\n            }\n            onExpand();\n          }}\n          onPointerEnter={(event) => {\n            if (\n              !mobile &&\n              hoverDropdowns &&\n              event.pointerType === \"mouse\" &&\n              !expanded\n            ) {\n              hoverOpenedAtRef.current = performance.now();\n              onExpand();\n            }\n          }}\n          className={cn(rowClass, \"cursor-pointer\")}\n        >\n          {label}\n        </button>\n      ) : (\n        <a\n          data-fullnav-hit\n          href={item.href ?? \"#\"}\n          onClick={onNavigate}\n          className={rowClass}\n        >\n          {label}\n        </a>\n      )}\n      {hasLinks && (\n        <div\n          ref={panelRef}\n          id={`panel-${id}`}\n          role=\"region\"\n          aria-labelledby={`trigger-${id}`}\n          aria-hidden={!expanded}\n          inert={!expanded}\n          className=\"invisible h-0 overflow-hidden\"\n        >\n          <div\n            ref={contentRef}\n            className=\"flex flex-col items-start gap-2.5 pb-4 pl-7 pt-2 @[760px]/fullnav:pl-9\"\n          >\n            {item.links!.map((link) => (\n              <a\n                key={`${link.label}-${link.href}`}\n                data-fullnav-hit\n                href={link.href}\n                onClick={onNavigate}\n                className=\"relative z-10 max-w-full rounded-md text-sm leading-relaxed opacity-60 focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-(--fullnav-accent)\"\n              >\n                <span data-fullnav-label className=\"inline-block\">\n                  {link.label}\n                </span>\n              </a>\n            ))}\n          </div>\n        </div>\n      )}\n    </div>\n  );\n}\n\nfunction FullscreenPanel({\n  open,\n  onExit,\n  onClose,\n  mobile,\n  settings,\n}: {\n  open: boolean;\n  onExit: () => void;\n  onClose: () => void;\n  mobile: boolean;\n  settings: Required<\n    Pick<\n      FullscreenNavbarProps,\n      | \"brand\"\n      | \"brandHref\"\n      | \"items\"\n      | \"connectLinks\"\n      | \"actionLabel\"\n      | \"actionHref\"\n      | \"footerText\"\n      | \"contactLabel\"\n      | \"contactHref\"\n      | \"duration\"\n      | \"stiffness\"\n      | \"damping\"\n      | \"stagger\"\n      | \"background\"\n      | \"foreground\"\n      | \"accent\"\n      | \"hoverDropdowns\"\n      | \"contained\"\n    >\n  > &\n    Pick<FullscreenNavbarProps, \"logo\" | \"spotlight\">;\n}) {\n  const rootRef = useRef<HTMLDivElement>(null);\n  const sheetRef = useRef<HTMLDivElement>(null);\n  const overlayRef = useRef<HTMLDivElement>(null);\n  const closeRef = useRef<HTMLButtonElement>(null);\n  const motionRef = useRef<((next: boolean) => void) | null>(null);\n  const enteredRef = useRef(false);\n  const openRef = useRef(open);\n  const [active, setActive] = useState<number | null>(null);\n  const {\n    brand,\n    brandHref,\n    logo,\n    items,\n    connectLinks,\n    spotlight,\n    actionLabel,\n    actionHref,\n    footerText,\n    contactLabel,\n    contactHref,\n    duration,\n    stiffness,\n    damping,\n    stagger,\n    background,\n    foreground,\n    accent,\n    hoverDropdowns,\n    contained,\n  } = settings;\n  useEffect(() => {\n    openRef.current = open;\n  }, [open]);\n\n  useGSAP(\n    () => {\n      const sheet = sheetRef.current;\n      const overlay = overlayRef.current;\n      const root = rootRef.current;\n      if (!sheet || !overlay || !root) return;\n      const entries = root.querySelectorAll(\"[data-fullnav-entry]\");\n      const side = root.querySelector(\"[data-fullnav-side]\");\n      const media = gsap.matchMedia();\n      media.add(\n        { reduced: \"(prefers-reduced-motion: reduce)\", all: \"all\" },\n        (context) => {\n          const reduced = Boolean(context.conditions?.reduced);\n          gsap.set(sheet, {\n            xPercent: mobile ? 0 : 100,\n            yPercent: mobile ? -100 : 0,\n          });\n          gsap.set(entries, { y: 24, opacity: 0 });\n          if (side) gsap.set(side, { y: 16, opacity: 0 });\n          gsap.set(overlay, { opacity: 0 });\n          const timeline = gsap\n            .timeline({\n              paused: true,\n              onComplete: () => {\n                enteredRef.current = true;\n              },\n              onReverseComplete: onExit,\n            })\n            .to(\n              sheet,\n              { xPercent: 0, yPercent: 0, duration, ease: \"power3.inOut\" },\n              0,\n            )\n            .to(overlay, { opacity: 1, duration: duration * 0.6 }, 0)\n            .to(\n              entries,\n              {\n                y: 0,\n                opacity: 1,\n                duration: duration * 0.6,\n                ease: springEase(stiffness, damping, duration * 0.6),\n                stagger,\n              },\n              duration * 0.25,\n            );\n          if (side)\n            timeline.to(\n              side,\n              {\n                y: 0,\n                opacity: 1,\n                duration: duration * 0.6,\n                ease: \"power3.out\",\n              },\n              duration * 0.4,\n            );\n          const nav = root.querySelector<HTMLElement>(\"[data-fullnav-list]\");\n          const highlight = root.querySelector<HTMLElement>(\n            \"[data-fullnav-highlight]\",\n          );\n          const hoverDuration = Math.min(0.5, Math.max(0.25, duration * 0.45));\n          const hoverEase = springEase(stiffness, damping, hoverDuration);\n          let selected: HTMLElement | null = null;\n          const quick =\n            highlight && !reduced\n              ? {\n                  x: gsap.quickTo(highlight, \"x\", {\n                    duration: hoverDuration,\n                    ease: hoverEase,\n                  }),\n                  y: gsap.quickTo(highlight, \"y\", {\n                    duration: hoverDuration,\n                    ease: hoverEase,\n                  }),\n                  width: gsap.quickTo(highlight, \"width\", {\n                    duration: hoverDuration,\n                    ease: \"power3.out\",\n                  }),\n                  height: gsap.quickTo(highlight, \"height\", {\n                    duration: hoverDuration,\n                    ease: \"power3.out\",\n                  }),\n                }\n              : null;\n          const positionHighlight = context.add(\"positionHighlight\", () => {\n            if (!nav || !highlight || !selected) return;\n            const bounds = selected.getBoundingClientRect();\n            const parent = nav.getBoundingClientRect();\n            const geometry = {\n              x: bounds.left - parent.left - 8,\n              y: bounds.top - parent.top - 2,\n              width: bounds.width + 16,\n              height: bounds.height + 4,\n            };\n            if (quick) {\n              quick.x(geometry.x);\n              quick.y(geometry.y);\n              quick.width(geometry.width);\n              quick.height(geometry.height);\n            } else gsap.set(highlight, geometry);\n          });\n          const select = context.add(\"select\", (target: HTMLElement | null) => {\n            if (!highlight || !nav || target === selected) return;\n            if (selected) {\n              const oldLabel = selected.querySelector(\"[data-fullnav-label]\");\n              const oldArrow = selected.querySelector<SVGSVGElement>(\n                \"[data-fullnav-arrow]\",\n              );\n              if (oldLabel)\n                gsap.to(oldLabel, {\n                  x: 0,\n                  duration: reduced ? 0 : hoverDuration,\n                  ease: hoverEase,\n                  overwrite: \"auto\",\n                });\n              if (oldArrow)\n                gsap.to(oldArrow, {\n                  x: 0,\n                  opacity: oldArrow.dataset.fullnavArrow === \"link\" ? 0 : 0.35,\n                  duration: reduced ? 0 : hoverDuration,\n                  ease: \"power3.out\",\n                  overwrite: \"auto\",\n                });\n              const previous = selected;\n              gsap.to(previous, {\n                color:\n                  previous.getAttribute(\"aria-expanded\") === \"true\"\n                    ? accent\n                    : foreground,\n                duration: reduced ? 0 : 0.2,\n                overwrite: \"auto\",\n                onComplete: () => previous.style.removeProperty(\"color\"),\n              });\n            }\n            const first = selected === null;\n            selected = target;\n            if (first && target) {\n              const bounds = target.getBoundingClientRect();\n              const parent = nav.getBoundingClientRect();\n              gsap.set(highlight, {\n                x: bounds.left - parent.left - 8,\n                y: bounds.top - parent.top - 2,\n                width: bounds.width + 16,\n                height: bounds.height + 4,\n              });\n            }\n            positionHighlight();\n            gsap.to(highlight, {\n              opacity: target ? 1 : 0,\n              duration: reduced ? 0 : 0.2,\n              overwrite: \"auto\",\n            });\n            if (!target) return;\n            const label = target.querySelector(\"[data-fullnav-label]\");\n            const arrow = target.querySelector(\"[data-fullnav-arrow]\");\n            if (label)\n              gsap.to(label, {\n                x: reduced ? 0 : 6,\n                duration: reduced ? 0 : hoverDuration,\n                ease: hoverEase,\n                overwrite: \"auto\",\n              });\n            if (arrow)\n              gsap.to(arrow, {\n                x: reduced ? 0 : 3,\n                opacity: 0.65,\n                duration: reduced ? 0 : hoverDuration,\n                ease: hoverEase,\n                overwrite: \"auto\",\n              });\n            gsap.to(target, {\n              color: accent,\n              duration: reduced ? 0 : 0.2,\n              overwrite: \"auto\",\n            });\n          });\n          const point = (event: Event) => {\n            if (\n              event instanceof PointerEvent &&\n              (event.pointerType !== \"mouse\" || mobile)\n            )\n              return;\n            const target =\n              event.target instanceof Element\n                ? event.target.closest<HTMLElement>(\"[data-fullnav-hit]\")\n                : null;\n            if (target && nav?.contains(target)) select(target);\n          };\n          const leave = () => {\n            if (!nav?.contains(document.activeElement)) select(null);\n          };\n          const blur = (event: FocusEvent) => {\n            if (\n              !(event.relatedTarget instanceof Node) ||\n              !nav?.contains(event.relatedTarget)\n            )\n              select(null);\n          };\n          nav?.addEventListener(\"pointerover\", point);\n          nav?.addEventListener(\"focusin\", point);\n          nav?.addEventListener(\"pointerleave\", leave);\n          nav?.addEventListener(\"focusout\", blur);\n          const hoverObserver = new ResizeObserver(() => positionHighlight());\n          if (nav) hoverObserver.observe(nav);\n          const move = context.add(\"move\", (next: boolean) => {\n            if (reduced) {\n              gsap.set(sheet, { xPercent: 0, yPercent: 0 });\n              gsap.set(entries, { y: 0, opacity: 1 });\n              if (side) gsap.set(side, { y: 0, opacity: 1 });\n              gsap.set(overlay, { opacity: 1 });\n              if (!next) onExit();\n            } else if (next) timeline.play();\n            else timeline.timeScale(1.25).reverse();\n          });\n          motionRef.current = (next) => move(next);\n          if (enteredRef.current && openRef.current && !reduced)\n            timeline.progress(1);\n          else move(openRef.current);\n          return () => {\n            motionRef.current = null;\n            nav?.removeEventListener(\"pointerover\", point);\n            nav?.removeEventListener(\"focusin\", point);\n            nav?.removeEventListener(\"pointerleave\", leave);\n            nav?.removeEventListener(\"focusout\", blur);\n            hoverObserver.disconnect();\n          };\n        },\n      );\n      return () => media.revert();\n    },\n    {\n      scope: rootRef,\n      dependencies: [\n        mobile,\n        duration,\n        stiffness,\n        damping,\n        stagger,\n        items,\n        accent,\n        foreground,\n        onExit,\n      ],\n      revertOnUpdate: true,\n    },\n  );\n\n  useGSAP(\n    () => {\n      motionRef.current?.(open);\n    },\n    { scope: rootRef, dependencies: [open] },\n  );\n\n  return (\n    <Dialog.Content\n      ref={rootRef}\n      className={cn(\n        \"@container/fullnav inset-0 z-50 overflow-hidden outline-none pointer-events-auto\",\n        contained ? \"absolute\" : \"fixed\",\n      )}\n      style={{ color: foreground, \"--fullnav-accent\": accent } as CSSProperties}\n      onOpenAutoFocus={(event) => {\n        event.preventDefault();\n        closeRef.current?.focus({ preventScroll: true });\n      }}\n      onEscapeKeyDown={(event) => {\n        event.preventDefault();\n        onClose();\n      }}\n      onPointerDownOutside={(event) => event.preventDefault()}\n    >\n      <Dialog.Title className=\"sr-only\">{brand} navigation</Dialog.Title>\n      <Dialog.Description className=\"sr-only\">\n        Explore components, documentation, and community resources.\n      </Dialog.Description>\n      <div\n        ref={overlayRef}\n        aria-hidden=\"true\"\n        className=\"absolute inset-0 bg-black/25\"\n      />\n      <div ref={sheetRef} className=\"absolute inset-0\" style={{ background }}>\n        <div\n          aria-hidden=\"true\"\n          className=\"pointer-events-none absolute inset-0 bg-[radial-gradient(ellipse_at_top_left,rgba(0,0,0,0.04),transparent_65%)]\"\n        />\n        <div className=\"relative flex h-full flex-col\">\n          <div className=\"shrink-0 border-b border-current/8\">\n            <NavbarHeader\n              brand={brand}\n              brandHref={brandHref}\n              logo={logo}\n              actionLabel={actionLabel}\n              actionHref={actionHref}\n              onNavigate={onClose}\n              menuButton={\n                <button\n                  ref={closeRef}\n                  type=\"button\"\n                  aria-label=\"Close menu\"\n                  onClick={onClose}\n                  className={menuButtonClass}\n                >\n                  Close\n                  <X aria-hidden=\"true\" className=\"size-4\" />\n                </button>\n              }\n            />\n          </div>\n          <div className=\"min-h-0 flex-1 overflow-y-auto overscroll-contain px-5 py-7 scrollbar-thin @[760px]/fullnav:px-9 @[760px]/fullnav:py-9\">\n            <div className=\"mx-auto grid w-full max-w-7xl gap-10 @[760px]/fullnav:grid-cols-[1.45fr_1fr] @[760px]/fullnav:gap-10\">\n              <nav\n                data-fullnav-list\n                aria-label={`${brand} main navigation`}\n                className=\"relative isolate min-w-0\"\n                onPointerLeave={(event) => {\n                  if (\n                    !mobile &&\n                    event.pointerType === \"mouse\" &&\n                    !event.currentTarget.contains(document.activeElement)\n                  )\n                    setActive(null);\n                }}\n              >\n                <div\n                  data-fullnav-highlight\n                  aria-hidden=\"true\"\n                  className=\"pointer-events-none absolute left-0 top-0 z-0 rounded-xl bg-[color-mix(in_srgb,var(--fullnav-accent)_7%,transparent)] opacity-0\"\n                />\n                {items.map((item, index) => (\n                  <NavbarRow\n                    key={`${item.label}-${index}`}\n                    item={item}\n                    index={index}\n                    expanded={active === index}\n                    onExpand={() =>\n                      setActive((current) => (current === index ? null : index))\n                    }\n                    onNavigate={onClose}\n                    mobile={mobile}\n                    duration={duration}\n                    stiffness={stiffness}\n                    damping={damping}\n                    hoverDropdowns={hoverDropdowns}\n                  />\n                ))}\n              </nav>\n              <aside\n                data-fullnav-side\n                className=\"min-w-0 border-t border-current/10 pt-7 @[760px]/fullnav:border-l @[760px]/fullnav:border-t-0 @[760px]/fullnav:pl-8 @[760px]/fullnav:pt-0\"\n              >\n                {spotlight && (\n                  <div className=\"mb-7\">\n                    <p className=\"mb-4 text-[10px] font-medium uppercase tracking-[0.2em] opacity-40\">\n                      Spotlight\n                    </p>\n                    <a\n                      href={spotlight.href}\n                      onClick={onClose}\n                      className=\"group relative block aspect-16/10 overflow-hidden rounded-2xl bg-black/10 focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-(--fullnav-accent)\"\n                    >\n                      <img\n                        src={spotlight.image}\n                        alt=\"\"\n                        className=\"absolute inset-0 h-full w-full object-cover object-center\"\n                      />\n                      <div className=\"absolute inset-0 bg-linear-to-t from-black/90 via-black/20 to-transparent\" />\n                      {spotlight.tag && (\n                        <span className=\"absolute left-4 top-4 rounded-full border border-white/20 bg-white/15 px-2.5 py-1 text-[9px] uppercase tracking-wider text-white backdrop-blur-lg\">\n                          {spotlight.tag}\n                        </span>\n                      )}\n                      <div className=\"absolute bottom-4 left-4 right-4 text-white\">\n                        <h3 className=\"max-w-64 text-xl font-medium leading-tight tracking-tight\">\n                          {spotlight.title}\n                        </h3>\n                        <p className=\"mt-2 max-w-72 text-[11px] leading-relaxed text-white/65\">\n                          {spotlight.description}\n                        </p>\n                      </div>\n                      <ArrowUpRight\n                        aria-hidden=\"true\"\n                        className=\"absolute top-4 right-4 size-4 text-white/75\"\n                      />\n                    </a>\n                  </div>\n                )}\n                <div className=\"mb-7\">\n                  <p className=\"mb-3 text-[10px] font-medium uppercase tracking-[0.2em] opacity-40\">\n                    Connect\n                  </p>\n                  <div className=\"flex flex-wrap gap-2\">\n                    {connectLinks.map((link) => (\n                      <a\n                        key={`${link.label}-${link.href}`}\n                        href={link.href}\n                        onClick={onClose}\n                        target={\n                          link.href.startsWith(\"http\") ? \"_blank\" : undefined\n                        }\n                        rel={\n                          link.href.startsWith(\"http\")\n                            ? \"noopener noreferrer\"\n                            : undefined\n                        }\n                        className=\"flex min-h-9 items-center gap-2 rounded-full border border-current/12 px-3 text-[11px] hover:bg-current/5 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--fullnav-accent)\"\n                      >\n                        {link.label}\n                        <ArrowUpRight aria-hidden=\"true\" className=\"size-3\" />\n                      </a>\n                    ))}\n                  </div>\n                </div>\n                <p className=\"mb-2 text-[10px] font-medium uppercase tracking-[0.2em] opacity-40\">\n                  Build with us\n                </p>\n                <a\n                  href={contactHref}\n                  onClick={onClose}\n                  className=\"block w-fit max-w-full wrap-break-words rounded-md text-lg font-medium tracking-tight hover:text-(--fullnav-accent) focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-(--fullnav-accent)\"\n                >\n                  {contactLabel}\n                </a>\n                <a\n                  href={actionHref}\n                  onClick={onClose}\n                  className=\"mt-7 flex min-h-10 w-fit items-center gap-3 rounded-full bg-(--fullnav-accent) px-5 text-xs font-medium text-white focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-(--fullnav-accent)\"\n                >\n                  {actionLabel}\n                  <ArrowUpRight aria-hidden=\"true\" className=\"size-3.5\" />\n                </a>\n              </aside>\n            </div>\n            <p className=\"mx-auto mt-10 max-w-7xl border-t border-current/10 pt-4 text-[10px] tracking-wide opacity-40\">\n              {footerText}\n            </p>\n          </div>\n        </div>\n      </div>\n    </Dialog.Content>\n  );\n}\n\nexport function FullscreenNavbar({\n  brand = \"devclub\",\n  brandHref = \"/\",\n  logo,\n  items = FULLSCREEN_NAVBAR_ITEMS,\n  connectLinks = FULLSCREEN_NAVBAR_CONNECT_LINKS,\n  spotlight,\n  actionLabel = \"Start building\",\n  actionHref = \"/docs/installation\",\n  footerText = \"Built by Dev Club. Made for your next idea.\",\n  contactLabel = \"Let's build something together.\",\n  contactHref = \"https://github.com/devclub-nstru/component_library/issues\",\n  duration = 0.8,\n  stiffness = 220,\n  damping = 26,\n  stagger = 0.045,\n  background = \"#f7f5ef\",\n  foreground = \"#171717\",\n  accent = \"#d34b30\",\n  headerForeground = \"#ffffff\",\n  hoverDropdowns = true,\n  contained = false,\n  open: controlledOpen,\n  defaultOpen = false,\n  onOpenChange,\n  className,\n}: FullscreenNavbarProps) {\n  const [internalOpen, setInternalOpen] = useState(defaultOpen);\n  const open = controlledOpen ?? internalOpen;\n  const [present, setPresent] = useState(open);\n  const [host, setHost] = useState<HTMLDivElement | null>(null);\n  const [mobile, setMobile] = useState(false);\n  const finishExit = useCallback(() => setPresent(false), []);\n  if (open && !present) setPresent(true);\n  const changeOpen = (next: boolean) => {\n    if (controlledOpen === undefined) setInternalOpen(next);\n    onOpenChange?.(next);\n  };\n\n  useEffect(() => {\n    if (!host) return;\n    const observer = new ResizeObserver(() =>\n      setMobile(host.clientWidth < 760),\n    );\n    observer.observe(host);\n    return () => observer.disconnect();\n  }, [host]);\n\n  const settings = {\n    brand,\n    brandHref,\n    logo,\n    items,\n    connectLinks,\n    spotlight,\n    actionLabel,\n    actionHref,\n    footerText,\n    contactLabel,\n    contactHref,\n    duration: bounded(duration, 0.25, 1.5, 0.8),\n    stiffness: bounded(stiffness, 80, 500, 220),\n    damping: bounded(damping, 12, 50, 26),\n    stagger: bounded(stagger, 0, 0.12, 0.045),\n    background,\n    foreground,\n    accent,\n    hoverDropdowns,\n    contained,\n  };\n  return (\n    <div\n      ref={setHost}\n      data-slot=\"fullscreen-navbar\"\n      className={cn(\n        \"pointer-events-none inset-0 z-30\",\n        contained ? \"absolute\" : \"fixed\",\n        className,\n      )}\n    >\n      <Dialog.Root open={present} onOpenChange={changeOpen}>\n        <div\n          aria-hidden={present}\n          inert={present}\n          className=\"@container/fullnav pointer-events-auto absolute inset-x-0 top-0\"\n          style={\n            {\n              color: headerForeground,\n              \"--fullnav-accent\": accent,\n            } as CSSProperties\n          }\n        >\n          <NavbarHeader\n            brand={brand}\n            brandHref={brandHref}\n            logo={logo}\n            actionLabel={actionLabel}\n            actionHref={actionHref}\n            menuButton={\n              <Dialog.Trigger asChild>\n                <button\n                  type=\"button\"\n                  aria-label=\"Open menu\"\n                  className={menuButtonClass}\n                >\n                  Menu\n                  <Menu aria-hidden=\"true\" className=\"size-4\" />\n                </button>\n              </Dialog.Trigger>\n            }\n          />\n        </div>\n        <Dialog.Portal container={contained ? host : undefined}>\n          <FullscreenPanel\n            open={open}\n            onExit={finishExit}\n            onClose={() => changeOpen(false)}\n            mobile={mobile}\n            settings={settings}\n          />\n        </Dialog.Portal>\n      </Dialog.Root>\n    </div>\n  );\n}\n\nexport default FullscreenNavbar;\n",
      "content": "\"use client\";\n\nimport {\n  useCallback,\n  useEffect,\n  useId,\n  useRef,\n  useState,\n  type CSSProperties,\n  type ReactNode,\n} from \"react\";\nimport * as Dialog from \"@radix-ui/react-dialog\";\nimport { useGSAP } from \"@gsap/react\";\nimport gsap from \"gsap\";\nimport { ArrowUpRight, ChevronDown, Code2, Menu, X } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\n\ngsap.registerPlugin(useGSAP);\n\nexport interface FullscreenNavbarLink {\n  label: string;\n  href: string;\n}\n\nexport interface FullscreenNavbarItem {\n  label: string;\n  href?: string;\n  links?: readonly FullscreenNavbarLink[];\n}\n\nexport interface FullscreenNavbarSpotlight {\n  title: string;\n  description: string;\n  tag?: string;\n  image: string;\n  href: string;\n}\n\nexport interface FullscreenNavbarProps {\n  brand?: string;\n  brandHref?: string;\n  logo?: ReactNode;\n  items?: readonly FullscreenNavbarItem[];\n  connectLinks?: readonly FullscreenNavbarLink[];\n  spotlight?: FullscreenNavbarSpotlight;\n  actionLabel?: string;\n  actionHref?: string;\n  footerText?: string;\n  contactLabel?: string;\n  contactHref?: string;\n  duration?: number;\n  stiffness?: number;\n  damping?: number;\n  stagger?: number;\n  background?: string;\n  foreground?: string;\n  accent?: string;\n  headerForeground?: string;\n  hoverDropdowns?: boolean;\n  contained?: boolean;\n  open?: boolean;\n  defaultOpen?: boolean;\n  onOpenChange?: (open: boolean) => void;\n  className?: string;\n}\n\nexport const FULLSCREEN_NAVBAR_ITEMS: readonly FullscreenNavbarItem[] = [\n  {\n    label: \"Components\",\n    links: [\n      { label: \"Explore the library\", href: \"/components\" },\n      { label: \"Animated buttons\", href: \"/components/animated-button\" },\n      { label: \"Navigation bars\", href: \"/components/curved-navbar\" },\n      { label: \"Image loaders\", href: \"/components/grid-image-loader\" },\n    ],\n  },\n  {\n    label: \"Documentation\",\n    links: [\n      { label: \"Introduction\", href: \"/docs/introduction\" },\n      { label: \"Installation\", href: \"/docs/installation\" },\n      { label: \"Theming\", href: \"/docs/theming\" },\n      { label: \"CLI & registry\", href: \"/docs/cli\" },\n    ],\n  },\n  {\n    label: \"Community\",\n    links: [\n      {\n        label: \"Dev Club on GitHub\",\n        href: \"https://github.com/devclub-nstru/component_library\",\n      },\n      {\n        label: \"Report an issue\",\n        href: \"https://github.com/devclub-nstru/component_library/issues\",\n      },\n      {\n        label: \"Contribute\",\n        href: \"https://github.com/devclub-nstru/component_library/blob/main/CONTRIBUTING.md\",\n      },\n    ],\n  },\n  { label: \"Showcase\", href: \"/components\" },\n  { label: \"Our story\", href: \"/docs/introduction\" },\n  {\n    label: \"Source code\",\n    href: \"https://github.com/devclub-nstru/component_library\",\n  },\n];\n\nexport const FULLSCREEN_NAVBAR_CONNECT_LINKS: readonly FullscreenNavbarLink[] =\n  [\n    {\n      label: \"GitHub\",\n      href: \"https://github.com/devclub-nstru/component_library\",\n    },\n    { label: \"Docs\", href: \"/docs\" },\n    { label: \"Dev Club\", href: \"https://devclub.co\" },\n  ];\n\nconst bounded = (value: number, min: number, max: number, fallback: number) =>\n  Number.isFinite(value) ? Math.min(max, Math.max(min, value)) : fallback;\n\nfunction springEase(stiffness: number, damping: number, duration: number) {\n  const frequency = Math.sqrt(stiffness);\n  const ratio = damping / (2 * frequency);\n  const response = (time: number) => {\n    if (ratio < 1) {\n      const rate = frequency * Math.sqrt(1 - ratio * ratio);\n      return (\n        1 -\n        Math.exp((-damping * time) / 2) *\n          (Math.cos(rate * time) +\n            (damping / (2 * rate)) * Math.sin(rate * time))\n      );\n    }\n    if (Math.abs(ratio - 1) < 0.001)\n      return 1 - (1 + frequency * time) * Math.exp(-frequency * time);\n    const delta = Math.sqrt(ratio * ratio - 1);\n    const slow = -frequency * (ratio - delta);\n    const fast = -frequency * (ratio + delta);\n    return (\n      1 -\n      (fast * Math.exp(slow * time) - slow * Math.exp(fast * time)) /\n        (fast - slow)\n    );\n  };\n  const end = response(duration);\n  return (progress: number) =>\n    progress === 1 ? 1 : response(progress * duration) / Math.max(0.001, end);\n}\n\nfunction NavbarHeader({\n  brand,\n  brandHref,\n  logo,\n  actionLabel,\n  actionHref,\n  menuButton,\n  onNavigate,\n}: {\n  brand: string;\n  brandHref: string;\n  logo?: ReactNode;\n  actionLabel: string;\n  actionHref: string;\n  menuButton: ReactNode;\n  onNavigate?: () => void;\n}) {\n  return (\n    <div className=\"flex h-20 w-full items-center justify-between gap-3 px-5 @[760px]/fullnav:px-9\">\n      <a\n        href={brandHref}\n        onClick={onNavigate}\n        className=\"flex min-w-0 items-center gap-2.5 rounded-lg text-xl font-semibold tracking-[-0.04em] focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-(--fullnav-accent)\"\n      >\n        <span\n          aria-hidden=\"true\"\n          className=\"flex size-8 shrink-0 items-center justify-center overflow-hidden rounded-lg\"\n        >\n          {logo ?? <Code2 className=\"size-7\" />}\n        </span>\n        <span className=\"max-w-32 truncate\">{brand}</span>\n      </a>\n      <div className=\"flex shrink-0 items-center gap-2\">\n        <a\n          href={actionHref}\n          onClick={onNavigate}\n          className=\"hidden min-h-10 items-center gap-1.5 rounded-full bg-(--fullnav-accent) px-4 text-xs font-medium text-white focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-(--fullnav-accent) @[480px]/fullnav:flex\"\n        >\n          {actionLabel}\n          <ArrowUpRight aria-hidden=\"true\" className=\"size-3.5\" />\n        </a>\n        {menuButton}\n      </div>\n    </div>\n  );\n}\n\nconst HOVER_CLICK_GRACE_MS = 350;\n\nconst menuButtonClass =\n  \"flex min-h-10 min-w-24 cursor-pointer items-center justify-center gap-2 rounded-full border border-current/15 bg-current/5 px-4 text-xs font-medium backdrop-blur-lg hover:bg-current/10 focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-[var(--fullnav-accent)]\";\n\nfunction NavbarRow({\n  item,\n  index,\n  expanded,\n  onExpand,\n  onNavigate,\n  mobile,\n  duration,\n  stiffness,\n  damping,\n  hoverDropdowns,\n}: {\n  item: FullscreenNavbarItem;\n  index: number;\n  expanded: boolean;\n  onExpand: () => void;\n  onNavigate: () => void;\n  mobile: boolean;\n  duration: number;\n  stiffness: number;\n  damping: number;\n  hoverDropdowns: boolean;\n}) {\n  const rootRef = useRef<HTMLDivElement>(null);\n  const panelRef = useRef<HTMLDivElement>(null);\n  const contentRef = useRef<HTMLDivElement>(null);\n  const arrowRef = useRef<SVGSVGElement>(null);\n  const animateRef = useRef<((next: boolean) => void) | null>(null);\n  const expandedRef = useRef(expanded);\n  const hoverOpenedAtRef = useRef(0);\n  const id = useId();\n  const hasLinks = Boolean(item.links?.length);\n  useEffect(() => {\n    expandedRef.current = expanded;\n  }, [expanded]);\n\n  useGSAP(\n    () => {\n      const panel = panelRef.current;\n      const content = contentRef.current;\n      const arrow = arrowRef.current;\n      if (!panel || !content || !arrow) return;\n      const media = gsap.matchMedia();\n      media.add(\n        { reduced: \"(prefers-reduced-motion: reduce)\", all: \"all\" },\n        (context) => {\n          const reduced = Boolean(context.conditions?.reduced);\n          const animate = context.add(\"animate\", (next: boolean) => {\n            gsap.killTweensOf([panel, arrow, content]);\n            if (reduced) {\n              gsap.set(panel, {\n                height: next ? \"auto\" : 0,\n                autoAlpha: next ? 1 : 0,\n              });\n              gsap.set(arrow, { rotation: next ? 180 : 0 });\n              gsap.set(content, { y: 0, opacity: 1 });\n              return;\n            }\n            gsap.to(panel, {\n              height: next ? \"auto\" : 0,\n              autoAlpha: next ? 1 : 0,\n              duration: duration * 0.55,\n              ease: \"power3.inOut\",\n              overwrite: \"auto\",\n            });\n            gsap.to(arrow, {\n              rotation: next ? 180 : 0,\n              duration: duration * 0.65,\n              ease: springEase(stiffness, damping, duration * 0.65),\n              overwrite: \"auto\",\n            });\n            gsap.fromTo(\n              content,\n              { y: next ? 8 : 0, opacity: next ? 0 : 1 },\n              {\n                y: 0,\n                opacity: next ? 1 : 0,\n                duration: duration * 0.45,\n                ease: \"power3.out\",\n              },\n            );\n          });\n          animateRef.current = (next) => animate(next);\n          animate(expandedRef.current);\n          return () => {\n            animateRef.current = null;\n          };\n        },\n      );\n      return () => media.revert();\n    },\n    {\n      scope: rootRef,\n      dependencies: [duration, stiffness, damping, item.links],\n      revertOnUpdate: true,\n    },\n  );\n\n  useGSAP(\n    () => {\n      animateRef.current?.(expanded);\n    },\n    { scope: rootRef, dependencies: [expanded] },\n  );\n\n  const label = (\n    <>\n      <span\n        aria-hidden=\"true\"\n        className=\"w-7 shrink-0 pt-1 text-[10px] font-normal tabular-nums tracking-wide opacity-35 @[760px]/fullnav:w-9\"\n      >\n        {String(index + 1).padStart(2, \"0\")}\n      </span>\n      <span\n        data-fullnav-label\n        className=\"min-w-0 wrap-break-words text-[clamp(1.65rem,4.8cqw,3.75rem)] font-semibold leading-[1.08] tracking-[-0.055em]\"\n      >\n        {item.label}\n      </span>\n      {hasLinks ? (\n        <ChevronDown\n          ref={arrowRef}\n          data-fullnav-arrow=\"chevron\"\n          aria-hidden=\"true\"\n          className=\"ml-2 size-5 shrink-0 opacity-35 @[760px]/fullnav:size-7\"\n        />\n      ) : (\n        <ArrowUpRight\n          aria-hidden=\"true\"\n          data-fullnav-arrow=\"link\"\n          className=\"ml-2 size-5 shrink-0 opacity-0\"\n        />\n      )}\n    </>\n  );\n  const rowClass = cn(\n    \"group relative z-10 flex w-full items-center rounded-lg py-2.5 text-left focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--fullnav-accent)]\",\n    expanded && \"text-[var(--fullnav-accent)]\",\n  );\n  return (\n    <div ref={rootRef} data-fullnav-entry>\n      {hasLinks ? (\n        <button\n          type=\"button\"\n          data-fullnav-hit\n          id={`trigger-${id}`}\n          aria-expanded={expanded}\n          aria-controls={`panel-${id}`}\n          onClick={(event) => {\n            const hoverOpenedAt = hoverOpenedAtRef.current;\n            hoverOpenedAtRef.current = 0;\n            if (\n              event.detail > 0 &&\n              hoverOpenedAt > 0 &&\n              performance.now() - hoverOpenedAt < HOVER_CLICK_GRACE_MS\n            ) {\n              return;\n            }\n            onExpand();\n          }}\n          onPointerEnter={(event) => {\n            if (\n              !mobile &&\n              hoverDropdowns &&\n              event.pointerType === \"mouse\" &&\n              !expanded\n            ) {\n              hoverOpenedAtRef.current = performance.now();\n              onExpand();\n            }\n          }}\n          className={cn(rowClass, \"cursor-pointer\")}\n        >\n          {label}\n        </button>\n      ) : (\n        <a\n          data-fullnav-hit\n          href={item.href ?? \"#\"}\n          onClick={onNavigate}\n          className={rowClass}\n        >\n          {label}\n        </a>\n      )}\n      {hasLinks && (\n        <div\n          ref={panelRef}\n          id={`panel-${id}`}\n          role=\"region\"\n          aria-labelledby={`trigger-${id}`}\n          aria-hidden={!expanded}\n          inert={!expanded}\n          className=\"invisible h-0 overflow-hidden\"\n        >\n          <div\n            ref={contentRef}\n            className=\"flex flex-col items-start gap-2.5 pb-4 pl-7 pt-2 @[760px]/fullnav:pl-9\"\n          >\n            {item.links!.map((link) => (\n              <a\n                key={`${link.label}-${link.href}`}\n                data-fullnav-hit\n                href={link.href}\n                onClick={onNavigate}\n                className=\"relative z-10 max-w-full rounded-md text-sm leading-relaxed opacity-60 focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-(--fullnav-accent)\"\n              >\n                <span data-fullnav-label className=\"inline-block\">\n                  {link.label}\n                </span>\n              </a>\n            ))}\n          </div>\n        </div>\n      )}\n    </div>\n  );\n}\n\nfunction FullscreenPanel({\n  open,\n  onExit,\n  onClose,\n  mobile,\n  settings,\n}: {\n  open: boolean;\n  onExit: () => void;\n  onClose: () => void;\n  mobile: boolean;\n  settings: Required<\n    Pick<\n      FullscreenNavbarProps,\n      | \"brand\"\n      | \"brandHref\"\n      | \"items\"\n      | \"connectLinks\"\n      | \"actionLabel\"\n      | \"actionHref\"\n      | \"footerText\"\n      | \"contactLabel\"\n      | \"contactHref\"\n      | \"duration\"\n      | \"stiffness\"\n      | \"damping\"\n      | \"stagger\"\n      | \"background\"\n      | \"foreground\"\n      | \"accent\"\n      | \"hoverDropdowns\"\n      | \"contained\"\n    >\n  > &\n    Pick<FullscreenNavbarProps, \"logo\" | \"spotlight\">;\n}) {\n  const rootRef = useRef<HTMLDivElement>(null);\n  const sheetRef = useRef<HTMLDivElement>(null);\n  const overlayRef = useRef<HTMLDivElement>(null);\n  const closeRef = useRef<HTMLButtonElement>(null);\n  const motionRef = useRef<((next: boolean) => void) | null>(null);\n  const enteredRef = useRef(false);\n  const openRef = useRef(open);\n  const [active, setActive] = useState<number | null>(null);\n  const {\n    brand,\n    brandHref,\n    logo,\n    items,\n    connectLinks,\n    spotlight,\n    actionLabel,\n    actionHref,\n    footerText,\n    contactLabel,\n    contactHref,\n    duration,\n    stiffness,\n    damping,\n    stagger,\n    background,\n    foreground,\n    accent,\n    hoverDropdowns,\n    contained,\n  } = settings;\n  useEffect(() => {\n    openRef.current = open;\n  }, [open]);\n\n  useGSAP(\n    () => {\n      const sheet = sheetRef.current;\n      const overlay = overlayRef.current;\n      const root = rootRef.current;\n      if (!sheet || !overlay || !root) return;\n      const entries = root.querySelectorAll(\"[data-fullnav-entry]\");\n      const side = root.querySelector(\"[data-fullnav-side]\");\n      const media = gsap.matchMedia();\n      media.add(\n        { reduced: \"(prefers-reduced-motion: reduce)\", all: \"all\" },\n        (context) => {\n          const reduced = Boolean(context.conditions?.reduced);\n          gsap.set(sheet, {\n            xPercent: mobile ? 0 : 100,\n            yPercent: mobile ? -100 : 0,\n          });\n          gsap.set(entries, { y: 24, opacity: 0 });\n          if (side) gsap.set(side, { y: 16, opacity: 0 });\n          gsap.set(overlay, { opacity: 0 });\n          const timeline = gsap\n            .timeline({\n              paused: true,\n              onComplete: () => {\n                enteredRef.current = true;\n              },\n              onReverseComplete: onExit,\n            })\n            .to(\n              sheet,\n              { xPercent: 0, yPercent: 0, duration, ease: \"power3.inOut\" },\n              0,\n            )\n            .to(overlay, { opacity: 1, duration: duration * 0.6 }, 0)\n            .to(\n              entries,\n              {\n                y: 0,\n                opacity: 1,\n                duration: duration * 0.6,\n                ease: springEase(stiffness, damping, duration * 0.6),\n                stagger,\n              },\n              duration * 0.25,\n            );\n          if (side)\n            timeline.to(\n              side,\n              {\n                y: 0,\n                opacity: 1,\n                duration: duration * 0.6,\n                ease: \"power3.out\",\n              },\n              duration * 0.4,\n            );\n          const nav = root.querySelector<HTMLElement>(\"[data-fullnav-list]\");\n          const highlight = root.querySelector<HTMLElement>(\n            \"[data-fullnav-highlight]\",\n          );\n          const hoverDuration = Math.min(0.5, Math.max(0.25, duration * 0.45));\n          const hoverEase = springEase(stiffness, damping, hoverDuration);\n          let selected: HTMLElement | null = null;\n          const quick =\n            highlight && !reduced\n              ? {\n                  x: gsap.quickTo(highlight, \"x\", {\n                    duration: hoverDuration,\n                    ease: hoverEase,\n                  }),\n                  y: gsap.quickTo(highlight, \"y\", {\n                    duration: hoverDuration,\n                    ease: hoverEase,\n                  }),\n                  width: gsap.quickTo(highlight, \"width\", {\n                    duration: hoverDuration,\n                    ease: \"power3.out\",\n                  }),\n                  height: gsap.quickTo(highlight, \"height\", {\n                    duration: hoverDuration,\n                    ease: \"power3.out\",\n                  }),\n                }\n              : null;\n          const positionHighlight = context.add(\"positionHighlight\", () => {\n            if (!nav || !highlight || !selected) return;\n            const bounds = selected.getBoundingClientRect();\n            const parent = nav.getBoundingClientRect();\n            const geometry = {\n              x: bounds.left - parent.left - 8,\n              y: bounds.top - parent.top - 2,\n              width: bounds.width + 16,\n              height: bounds.height + 4,\n            };\n            if (quick) {\n              quick.x(geometry.x);\n              quick.y(geometry.y);\n              quick.width(geometry.width);\n              quick.height(geometry.height);\n            } else gsap.set(highlight, geometry);\n          });\n          const select = context.add(\"select\", (target: HTMLElement | null) => {\n            if (!highlight || !nav || target === selected) return;\n            if (selected) {\n              const oldLabel = selected.querySelector(\"[data-fullnav-label]\");\n              const oldArrow = selected.querySelector<SVGSVGElement>(\n                \"[data-fullnav-arrow]\",\n              );\n              if (oldLabel)\n                gsap.to(oldLabel, {\n                  x: 0,\n                  duration: reduced ? 0 : hoverDuration,\n                  ease: hoverEase,\n                  overwrite: \"auto\",\n                });\n              if (oldArrow)\n                gsap.to(oldArrow, {\n                  x: 0,\n                  opacity: oldArrow.dataset.fullnavArrow === \"link\" ? 0 : 0.35,\n                  duration: reduced ? 0 : hoverDuration,\n                  ease: \"power3.out\",\n                  overwrite: \"auto\",\n                });\n              const previous = selected;\n              gsap.to(previous, {\n                color:\n                  previous.getAttribute(\"aria-expanded\") === \"true\"\n                    ? accent\n                    : foreground,\n                duration: reduced ? 0 : 0.2,\n                overwrite: \"auto\",\n                onComplete: () => previous.style.removeProperty(\"color\"),\n              });\n            }\n            const first = selected === null;\n            selected = target;\n            if (first && target) {\n              const bounds = target.getBoundingClientRect();\n              const parent = nav.getBoundingClientRect();\n              gsap.set(highlight, {\n                x: bounds.left - parent.left - 8,\n                y: bounds.top - parent.top - 2,\n                width: bounds.width + 16,\n                height: bounds.height + 4,\n              });\n            }\n            positionHighlight();\n            gsap.to(highlight, {\n              opacity: target ? 1 : 0,\n              duration: reduced ? 0 : 0.2,\n              overwrite: \"auto\",\n            });\n            if (!target) return;\n            const label = target.querySelector(\"[data-fullnav-label]\");\n            const arrow = target.querySelector(\"[data-fullnav-arrow]\");\n            if (label)\n              gsap.to(label, {\n                x: reduced ? 0 : 6,\n                duration: reduced ? 0 : hoverDuration,\n                ease: hoverEase,\n                overwrite: \"auto\",\n              });\n            if (arrow)\n              gsap.to(arrow, {\n                x: reduced ? 0 : 3,\n                opacity: 0.65,\n                duration: reduced ? 0 : hoverDuration,\n                ease: hoverEase,\n                overwrite: \"auto\",\n              });\n            gsap.to(target, {\n              color: accent,\n              duration: reduced ? 0 : 0.2,\n              overwrite: \"auto\",\n            });\n          });\n          const point = (event: Event) => {\n            if (\n              event instanceof PointerEvent &&\n              (event.pointerType !== \"mouse\" || mobile)\n            )\n              return;\n            const target =\n              event.target instanceof Element\n                ? event.target.closest<HTMLElement>(\"[data-fullnav-hit]\")\n                : null;\n            if (target && nav?.contains(target)) select(target);\n          };\n          const leave = () => {\n            if (!nav?.contains(document.activeElement)) select(null);\n          };\n          const blur = (event: FocusEvent) => {\n            if (\n              !(event.relatedTarget instanceof Node) ||\n              !nav?.contains(event.relatedTarget)\n            )\n              select(null);\n          };\n          nav?.addEventListener(\"pointerover\", point);\n          nav?.addEventListener(\"focusin\", point);\n          nav?.addEventListener(\"pointerleave\", leave);\n          nav?.addEventListener(\"focusout\", blur);\n          const hoverObserver = new ResizeObserver(() => positionHighlight());\n          if (nav) hoverObserver.observe(nav);\n          const move = context.add(\"move\", (next: boolean) => {\n            if (reduced) {\n              gsap.set(sheet, { xPercent: 0, yPercent: 0 });\n              gsap.set(entries, { y: 0, opacity: 1 });\n              if (side) gsap.set(side, { y: 0, opacity: 1 });\n              gsap.set(overlay, { opacity: 1 });\n              if (!next) onExit();\n            } else if (next) timeline.play();\n            else timeline.timeScale(1.25).reverse();\n          });\n          motionRef.current = (next) => move(next);\n          if (enteredRef.current && openRef.current && !reduced)\n            timeline.progress(1);\n          else move(openRef.current);\n          return () => {\n            motionRef.current = null;\n            nav?.removeEventListener(\"pointerover\", point);\n            nav?.removeEventListener(\"focusin\", point);\n            nav?.removeEventListener(\"pointerleave\", leave);\n            nav?.removeEventListener(\"focusout\", blur);\n            hoverObserver.disconnect();\n          };\n        },\n      );\n      return () => media.revert();\n    },\n    {\n      scope: rootRef,\n      dependencies: [\n        mobile,\n        duration,\n        stiffness,\n        damping,\n        stagger,\n        items,\n        accent,\n        foreground,\n        onExit,\n      ],\n      revertOnUpdate: true,\n    },\n  );\n\n  useGSAP(\n    () => {\n      motionRef.current?.(open);\n    },\n    { scope: rootRef, dependencies: [open] },\n  );\n\n  return (\n    <Dialog.Content\n      ref={rootRef}\n      className={cn(\n        \"@container/fullnav inset-0 z-50 overflow-hidden outline-none pointer-events-auto\",\n        contained ? \"absolute\" : \"fixed\",\n      )}\n      style={{ color: foreground, \"--fullnav-accent\": accent } as CSSProperties}\n      onOpenAutoFocus={(event) => {\n        event.preventDefault();\n        closeRef.current?.focus({ preventScroll: true });\n      }}\n      onEscapeKeyDown={(event) => {\n        event.preventDefault();\n        onClose();\n      }}\n      onPointerDownOutside={(event) => event.preventDefault()}\n    >\n      <Dialog.Title className=\"sr-only\">{brand} navigation</Dialog.Title>\n      <Dialog.Description className=\"sr-only\">\n        Explore components, documentation, and community resources.\n      </Dialog.Description>\n      <div\n        ref={overlayRef}\n        aria-hidden=\"true\"\n        className=\"absolute inset-0 bg-black/25\"\n      />\n      <div ref={sheetRef} className=\"absolute inset-0\" style={{ background }}>\n        <div\n          aria-hidden=\"true\"\n          className=\"pointer-events-none absolute inset-0 bg-[radial-gradient(ellipse_at_top_left,rgba(0,0,0,0.04),transparent_65%)]\"\n        />\n        <div className=\"relative flex h-full flex-col\">\n          <div className=\"shrink-0 border-b border-current/8\">\n            <NavbarHeader\n              brand={brand}\n              brandHref={brandHref}\n              logo={logo}\n              actionLabel={actionLabel}\n              actionHref={actionHref}\n              onNavigate={onClose}\n              menuButton={\n                <button\n                  ref={closeRef}\n                  type=\"button\"\n                  aria-label=\"Close menu\"\n                  onClick={onClose}\n                  className={menuButtonClass}\n                >\n                  Close\n                  <X aria-hidden=\"true\" className=\"size-4\" />\n                </button>\n              }\n            />\n          </div>\n          <div className=\"min-h-0 flex-1 overflow-y-auto overscroll-contain px-5 py-7 scrollbar-thin @[760px]/fullnav:px-9 @[760px]/fullnav:py-9\">\n            <div className=\"mx-auto grid w-full max-w-7xl gap-10 @[760px]/fullnav:grid-cols-[1.45fr_1fr] @[760px]/fullnav:gap-10\">\n              <nav\n                data-fullnav-list\n                aria-label={`${brand} main navigation`}\n                className=\"relative isolate min-w-0\"\n                onPointerLeave={(event) => {\n                  if (\n                    !mobile &&\n                    event.pointerType === \"mouse\" &&\n                    !event.currentTarget.contains(document.activeElement)\n                  )\n                    setActive(null);\n                }}\n              >\n                <div\n                  data-fullnav-highlight\n                  aria-hidden=\"true\"\n                  className=\"pointer-events-none absolute left-0 top-0 z-0 rounded-xl bg-[color-mix(in_srgb,var(--fullnav-accent)_7%,transparent)] opacity-0\"\n                />\n                {items.map((item, index) => (\n                  <NavbarRow\n                    key={`${item.label}-${index}`}\n                    item={item}\n                    index={index}\n                    expanded={active === index}\n                    onExpand={() =>\n                      setActive((current) => (current === index ? null : index))\n                    }\n                    onNavigate={onClose}\n                    mobile={mobile}\n                    duration={duration}\n                    stiffness={stiffness}\n                    damping={damping}\n                    hoverDropdowns={hoverDropdowns}\n                  />\n                ))}\n              </nav>\n              <aside\n                data-fullnav-side\n                className=\"min-w-0 border-t border-current/10 pt-7 @[760px]/fullnav:border-l @[760px]/fullnav:border-t-0 @[760px]/fullnav:pl-8 @[760px]/fullnav:pt-0\"\n              >\n                {spotlight && (\n                  <div className=\"mb-7\">\n                    <p className=\"mb-4 text-[10px] font-medium uppercase tracking-[0.2em] opacity-40\">\n                      Spotlight\n                    </p>\n                    <a\n                      href={spotlight.href}\n                      onClick={onClose}\n                      className=\"group relative block aspect-16/10 overflow-hidden rounded-2xl bg-black/10 focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-(--fullnav-accent)\"\n                    >\n                      <img\n                        src={spotlight.image}\n                        alt=\"\"\n                        className=\"absolute inset-0 h-full w-full object-cover object-center\"\n                      />\n                      <div className=\"absolute inset-0 bg-linear-to-t from-black/90 via-black/20 to-transparent\" />\n                      {spotlight.tag && (\n                        <span className=\"absolute left-4 top-4 rounded-full border border-white/20 bg-white/15 px-2.5 py-1 text-[9px] uppercase tracking-wider text-white backdrop-blur-lg\">\n                          {spotlight.tag}\n                        </span>\n                      )}\n                      <div className=\"absolute bottom-4 left-4 right-4 text-white\">\n                        <h3 className=\"max-w-64 text-xl font-medium leading-tight tracking-tight\">\n                          {spotlight.title}\n                        </h3>\n                        <p className=\"mt-2 max-w-72 text-[11px] leading-relaxed text-white/65\">\n                          {spotlight.description}\n                        </p>\n                      </div>\n                      <ArrowUpRight\n                        aria-hidden=\"true\"\n                        className=\"absolute top-4 right-4 size-4 text-white/75\"\n                      />\n                    </a>\n                  </div>\n                )}\n                <div className=\"mb-7\">\n                  <p className=\"mb-3 text-[10px] font-medium uppercase tracking-[0.2em] opacity-40\">\n                    Connect\n                  </p>\n                  <div className=\"flex flex-wrap gap-2\">\n                    {connectLinks.map((link) => (\n                      <a\n                        key={`${link.label}-${link.href}`}\n                        href={link.href}\n                        onClick={onClose}\n                        target={\n                          link.href.startsWith(\"http\") ? \"_blank\" : undefined\n                        }\n                        rel={\n                          link.href.startsWith(\"http\")\n                            ? \"noopener noreferrer\"\n                            : undefined\n                        }\n                        className=\"flex min-h-9 items-center gap-2 rounded-full border border-current/12 px-3 text-[11px] hover:bg-current/5 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--fullnav-accent)\"\n                      >\n                        {link.label}\n                        <ArrowUpRight aria-hidden=\"true\" className=\"size-3\" />\n                      </a>\n                    ))}\n                  </div>\n                </div>\n                <p className=\"mb-2 text-[10px] font-medium uppercase tracking-[0.2em] opacity-40\">\n                  Build with us\n                </p>\n                <a\n                  href={contactHref}\n                  onClick={onClose}\n                  className=\"block w-fit max-w-full wrap-break-words rounded-md text-lg font-medium tracking-tight hover:text-(--fullnav-accent) focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-(--fullnav-accent)\"\n                >\n                  {contactLabel}\n                </a>\n                <a\n                  href={actionHref}\n                  onClick={onClose}\n                  className=\"mt-7 flex min-h-10 w-fit items-center gap-3 rounded-full bg-(--fullnav-accent) px-5 text-xs font-medium text-white focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-(--fullnav-accent)\"\n                >\n                  {actionLabel}\n                  <ArrowUpRight aria-hidden=\"true\" className=\"size-3.5\" />\n                </a>\n              </aside>\n            </div>\n            <p className=\"mx-auto mt-10 max-w-7xl border-t border-current/10 pt-4 text-[10px] tracking-wide opacity-40\">\n              {footerText}\n            </p>\n          </div>\n        </div>\n      </div>\n    </Dialog.Content>\n  );\n}\n\nexport function FullscreenNavbar({\n  brand = \"devclub\",\n  brandHref = \"/\",\n  logo,\n  items = FULLSCREEN_NAVBAR_ITEMS,\n  connectLinks = FULLSCREEN_NAVBAR_CONNECT_LINKS,\n  spotlight,\n  actionLabel = \"Start building\",\n  actionHref = \"/docs/installation\",\n  footerText = \"Built by Dev Club. Made for your next idea.\",\n  contactLabel = \"Let's build something together.\",\n  contactHref = \"https://github.com/devclub-nstru/component_library/issues\",\n  duration = 0.8,\n  stiffness = 220,\n  damping = 26,\n  stagger = 0.045,\n  background = \"#f7f5ef\",\n  foreground = \"#171717\",\n  accent = \"#d34b30\",\n  headerForeground = \"#ffffff\",\n  hoverDropdowns = true,\n  contained = false,\n  open: controlledOpen,\n  defaultOpen = false,\n  onOpenChange,\n  className,\n}: FullscreenNavbarProps) {\n  const [internalOpen, setInternalOpen] = useState(defaultOpen);\n  const open = controlledOpen ?? internalOpen;\n  const [present, setPresent] = useState(open);\n  const [host, setHost] = useState<HTMLDivElement | null>(null);\n  const [mobile, setMobile] = useState(false);\n  const finishExit = useCallback(() => setPresent(false), []);\n  if (open && !present) setPresent(true);\n  const changeOpen = (next: boolean) => {\n    if (controlledOpen === undefined) setInternalOpen(next);\n    onOpenChange?.(next);\n  };\n\n  useEffect(() => {\n    if (!host) return;\n    const observer = new ResizeObserver(() =>\n      setMobile(host.clientWidth < 760),\n    );\n    observer.observe(host);\n    return () => observer.disconnect();\n  }, [host]);\n\n  const settings = {\n    brand,\n    brandHref,\n    logo,\n    items,\n    connectLinks,\n    spotlight,\n    actionLabel,\n    actionHref,\n    footerText,\n    contactLabel,\n    contactHref,\n    duration: bounded(duration, 0.25, 1.5, 0.8),\n    stiffness: bounded(stiffness, 80, 500, 220),\n    damping: bounded(damping, 12, 50, 26),\n    stagger: bounded(stagger, 0, 0.12, 0.045),\n    background,\n    foreground,\n    accent,\n    hoverDropdowns,\n    contained,\n  };\n  return (\n    <div\n      ref={setHost}\n      data-slot=\"fullscreen-navbar\"\n      className={cn(\n        \"pointer-events-none inset-0 z-30\",\n        contained ? \"absolute\" : \"fixed\",\n        className,\n      )}\n    >\n      <Dialog.Root open={present} onOpenChange={changeOpen}>\n        <div\n          aria-hidden={present}\n          inert={present}\n          className=\"@container/fullnav pointer-events-auto absolute inset-x-0 top-0\"\n          style={\n            {\n              color: headerForeground,\n              \"--fullnav-accent\": accent,\n            } as CSSProperties\n          }\n        >\n          <NavbarHeader\n            brand={brand}\n            brandHref={brandHref}\n            logo={logo}\n            actionLabel={actionLabel}\n            actionHref={actionHref}\n            menuButton={\n              <Dialog.Trigger asChild>\n                <button\n                  type=\"button\"\n                  aria-label=\"Open menu\"\n                  className={menuButtonClass}\n                >\n                  Menu\n                  <Menu aria-hidden=\"true\" className=\"size-4\" />\n                </button>\n              </Dialog.Trigger>\n            }\n          />\n        </div>\n        <Dialog.Portal container={contained ? host : undefined}>\n          <FullscreenPanel\n            open={open}\n            onExit={finishExit}\n            onClose={() => changeOpen(false)}\n            mobile={mobile}\n            settings={settings}\n          />\n        </Dialog.Portal>\n      </Dialog.Root>\n    </div>\n  );\n}\n\nexport default FullscreenNavbar;\n",
      "type": "registry:ui",
      "target": "@ui/fullscreen-navbar.tsx"
    }
  ],
  "usage": "import { FullscreenNavbar } from \"@/components/ui/fullscreen-navbar\";\n\nexport function FullscreenNavbarDemo() {\n  return <FullscreenNavbar brand=\"devclub\" actionLabel=\"Start building\" actionHref=\"/docs\" />;\n}"
}
