{"version":3,"sources":["/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-BESOUYM7.cjs","../src/components/navigation/header.tsx","../src/components/navigation/mingo-ai-button.tsx","../src/components/navigation/mobile-nav-panel.tsx","../src/components/navigation/ticket-alerts-button.tsx","../src/components/navigation/top-navigation.tsx","../src/components/navigation/header-skeleton.tsx","../src/components/navigation/client-only-header.tsx","../src/components/navigation/sliding-sidebar.tsx","../src/components/navigation/navigation-sidebar.tsx","../src/components/navigation/navigation-sidebar-header.tsx","../src/components/navigation/navigation-sidebar-item.tsx","../src/components/navigation/navigation-sidebar-toggle.tsx","../src/components/navigation/app-header.tsx","../src/components/navigation/header-global-search.tsx","../src/components/navigation/header-mingo-button.tsx","../src/components/navigation/header-organization-filter.tsx","../src/components/navigation/app-layout.tsx","../src/components/navigation/mobile-burger-menu.tsx"],"names":["jsx","jsxs","usePreventScroll","Fragment","useEffect","useState","useCallback","useMemo"],"mappings":"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","file":"/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-BESOUYM7.cjs","sourcesContent":[null,"'use client';\n\nimport React, { useEffect, useRef, useState } from 'react';\nimport Link from '../../embed-shims/next-link';\nimport type { HeaderConfig, NavigationItem } from '../../types/navigation';\nimport { cn } from '../../utils';\nimport { Menu01Icon, XmarkIcon } from '../icons-v2-generated';\nimport { Button } from '../ui/button';\nimport { HeaderButton } from './header-button';\nimport { MingoAiButton } from './mingo-ai-button';\nimport { MOBILE_NAV_PANEL_ID } from './mobile-nav-panel';\nimport { TicketAlertsButton } from './ticket-alerts-button';\nimport { TopNavigation } from './top-navigation';\n\nexport interface HeaderProps {\n  config: HeaderConfig;\n  platform?: string;\n}\n\n// Re-export from types for convenience\nexport type { HeaderConfig } from '../../types/navigation';\n\n// Top-level nav-link typography (Figma 2936-6815): the compact h6 step\n// (DM Sans 500, 14/20) with 24px icons. Overrides the `font=\"regular\"` h4\n// label and the Button base's 20px svg cap via cn()'s tailwind-merge.\nconst NAV_ITEM_CLASSES = 'text-h6 [&_svg]:h-6 [&_svg]:w-6';\n\nexport function Header({ config }: HeaderProps) {\n  const [show, setShow] = useState(true);\n  // Not state: nothing renders the previous offset, and as `useState` it\n  // re-rendered the whole header on every scroll event just to store a number.\n  const lastScrollY = useRef(0);\n  const [openDropdowns, setOpenDropdowns] = useState<Record<string, boolean>>({});\n  const dropdownRefs = useRef<Record<string, HTMLDivElement | null>>({});\n  const triggerRefs = useRef<Record<string, HTMLButtonElement | null>>({});\n\n  // Handle click outside and escape key for custom dropdown\n  useEffect(() => {\n    const handleClickOutside = (event: MouseEvent) => {\n      const target = event.target as HTMLElement;\n      if (!target) return;\n\n      // Check if click is outside all dropdowns\n      const isOutsideAllDropdowns = Object.keys(openDropdowns).every(id => {\n        const dropdown = dropdownRefs.current[id];\n        const trigger = triggerRefs.current[id];\n\n        if (!dropdown || !trigger) return true;\n\n        return !dropdown.contains(target) && !trigger.contains(target);\n      });\n\n      if (isOutsideAllDropdowns) {\n        setOpenDropdowns({});\n      }\n    };\n\n    const handleEscapeKey = (event: KeyboardEvent) => {\n      if (event.key === 'Escape') {\n        setOpenDropdowns({});\n      }\n    };\n\n    // Only add listeners if any dropdown is open\n    const hasOpenDropdowns = Object.values(openDropdowns).some(Boolean);\n    if (hasOpenDropdowns) {\n      document.addEventListener('mousedown', handleClickOutside);\n      document.addEventListener('keydown', handleEscapeKey);\n    }\n\n    return () => {\n      document.removeEventListener('mousedown', handleClickOutside);\n      document.removeEventListener('keydown', handleEscapeKey);\n    };\n  }, [openDropdowns]);\n\n  // Force close all dropdowns and cleanup on unmount\n  useEffect(() => {\n    return () => {\n      // Close all dropdowns before unmounting to prevent focus errors\n      setOpenDropdowns({});\n      // Clear any stored refs\n      dropdownRefs.current = {};\n      triggerRefs.current = {};\n    };\n  }, []);\n\n  useEffect(() => {\n    // Only add scroll listener if autoHide is enabled. Nothing to reset here:\n    // `show` is masked by `config.autoHide` where it is read (see `headerShown`\n    // below), so turning auto-hide off can never leave the header stuck\n    // off-screen, and it does not cost the extra render pass that writing\n    // `true` back into state from this effect did.\n    if (!config.autoHide) return undefined;\n\n    const handleScroll = () => {\n      const currentScrollY = window.scrollY;\n\n      const prevScrollY = lastScrollY.current;\n      lastScrollY.current = currentScrollY;\n\n      // Determine if we should show or hide the header\n      const shouldHide = currentScrollY > prevScrollY && currentScrollY > 50;\n      const shouldShow = currentScrollY < prevScrollY || currentScrollY <= 10;\n\n      if (shouldHide) {\n        setShow(false);\n      } else if (shouldShow) {\n        setShow(true);\n      }\n    };\n\n    window.addEventListener('scroll', handleScroll, { passive: true });\n\n    return () => {\n      window.removeEventListener('scroll', handleScroll);\n    };\n  }, [config.autoHide]);\n\n  const renderNavigationItem = (item: NavigationItem) => {\n    // If custom element provided, use it\n    if (item.element) {\n      return <React.Fragment key={item.id}>{item.element}</React.Fragment>;\n    }\n\n    // If it has children, render as custom dropdown\n    if (item.children && item.children.length > 0) {\n      const isOpen = openDropdowns[item.id] || false;\n\n      return (\n        <div key={item.id} className=\"relative\">\n          <Button\n            ref={el => {\n              triggerRefs.current[item.id] = el;\n            }}\n            variant=\"transparent\"\n            leftIcon={item.icon}\n            rightIcon={item.badge}\n            onClick={() => {\n              // Single-open: opening a dropdown closes any other open one —\n              // only one nav dropdown may be expanded at a time.\n              setOpenDropdowns(prev => ({\n                [item.id]: !prev[item.id],\n              }));\n            }}\n            size=\"default\"\n            font=\"regular\"\n            className={cn(\n              // Top-level nav links (Figma 2936-6815): compact h6 label\n              // (DM Sans 500, 14/20) with 24px icons — overrides the\n              // font=\"regular\" h4 step and the button's 20px svg cap.\n              NAV_ITEM_CLASSES,\n              item.isActive && 'bg-ods-bg-hover', // Active items get subtle gray background\n              isOpen && 'bg-ods-bg-hover', // Open dropdowns get gray background\n              item.className,\n            )}\n          >\n            {item.label}\n          </Button>\n\n          {/* Always render dropdown in DOM so crawlers see child <a> links;\n              toggle visibility via CSS + `inert`.\n\n              Why `inert` (not `aria-hidden`):\n                - `aria-hidden=true` HIDES from screen readers but DOES NOT\n                  remove focusable descendants from the tab order. If a child\n                  retains focus (e.g., the dropdown closes while a child was\n                  hovered/focused), the browser correctly flags \"Blocked\n                  aria-hidden on an element because its descendant retained\n                  focus\" — a real WAI-ARIA violation.\n                - `inert` is the HTML-standard attribute that does BOTH:\n                  removes from a11y tree + blocks focus + prevents click +\n                  removes from tab order. Native React 19 support. */}\n          <div\n            ref={el => {\n              dropdownRefs.current[item.id] = el;\n            }}\n            inert={!isOpen}\n            className={cn(\n              'absolute left-0 top-full mt-1',\n              item.dropdownClassName ? '' : 'border border-ods-border bg-ods-card',\n              'z-[9999] rounded-lg shadow-xl',\n              item.id === 'community' ? 'min-w-[240px]' : 'min-w-[220px]',\n              'transition-opacity duration-150',\n              isOpen ? 'pointer-events-auto visible opacity-100' : 'pointer-events-none invisible opacity-0',\n              item.dropdownClassName || '',\n            )}\n          >\n            <div className=\"p-2\">\n              {item.children.map((child, index) => (\n                <Button\n                  key={child.id}\n                  variant=\"transparent\"\n                  size=\"small-legacy\"\n                  href={child.href} // Use href for navigation\n                  leftIcon={child.icon}\n                  rightIcon={child.badge}\n                  onClick={() => {\n                    // Always close dropdown when any item is clicked\n                    setOpenDropdowns(prev => ({ ...prev, [item.id]: false }));\n                    // If there's a custom onClick, call it too\n                    if (child.onClick) {\n                      child.onClick();\n                    }\n                  }}\n                  className={cn(\n                    'flex w-full justify-start',\n                    // Same caption step as the top-level nav links (designer:\n                    // dropdowns follow the h6 caption size/weight too). The\n                    // explicit font-medium beats small-legacy's font-bold via\n                    // tailwind-merge instead of stylesheet order.\n                    'font-medium text-h6',\n                    index < (item.children?.length ?? 0) - 1 && 'mb-1',\n                    'text-ods-text-primary', // All dropdown items use primary text color\n                    child.isActive && 'bg-ods-bg-hover', // Active dropdown items get gray background\n                  )}\n                  {...(child.isExternal && { isExternal: true })}\n                >\n                  {child.label}\n                </Button>\n              ))}\n            </div>\n            {item.dropdownContent && (\n              <>\n                {item.showDropdownDivider !== false && <div className=\"mx-2 my-2 h-px bg-ods-border\" />}\n                <div className=\"px-2 pb-2\">{item.dropdownContent}</div>\n              </>\n            )}\n          </div>\n        </div>\n      );\n    }\n\n    // Regular navigation item\n    if (item.href || item.onClick) {\n      return (\n        <Button\n          key={item.id}\n          variant=\"transparent\"\n          href={item.href} // Use href for navigation\n          onClick={item.onClick} // Only for non-navigation actions\n          leftIcon={item.icon}\n          rightIcon={item.badge}\n          size=\"default\"\n          font=\"regular\"\n          className={cn(\n            NAV_ITEM_CLASSES,\n            'hover:bg-ods-bg-hover focus:bg-ods-bg-hover',\n            'whitespace-nowrap',\n            'text-ods-text-primary', // All items use primary text color\n            item.isActive && 'bg-ods-bg-hover', // Active items get subtle gray background\n            item.className,\n          )}\n          {...(item.isExternal && { isExternal: true })}\n        >\n          {item.label}\n        </Button>\n      );\n    }\n\n    // Button with onClick\n    return (\n      <Button\n        key={item.id}\n        variant=\"transparent\"\n        onClick={item.onClick}\n        leftIcon={item.icon}\n        rightIcon={item.badge}\n        size=\"default\"\n        font=\"regular\"\n        className={cn(\n          NAV_ITEM_CLASSES,\n          'hover:bg-ods-bg-hover focus:bg-ods-bg-hover',\n          'whitespace-nowrap',\n          'text-ods-text-primary', // All items use primary text color\n          item.isActive && 'bg-ods-bg-hover', // Active items get gray background\n          item.className,\n        )}\n      >\n        {item.label}\n      </Button>\n    );\n  };\n\n  // `show` only means anything while auto-hide is on — with it off the header\n  // is always up, regardless of whatever the scroll listener last recorded.\n  const headerShown = !config.autoHide || show;\n\n  const hasNav = !!config.navigation && config.navigation.items.length > 0;\n  const hasCta =\n    !!config.actions?.right?.length || !!(config.actions?.persistent && config.actions.persistent.length > 0);\n\n  return (\n    <div\n      className={cn('sticky top-0 z-[50] w-full transition-transform duration-300 ease-in-out')}\n      style={{\n        transform: !headerShown ? 'translateY(-100%)' : 'translateY(0)',\n      }}\n    >\n      {/* Unified ODS top-navigation shell (Figma 2797-5978): 48px mobile /\n          56px md+, cell model with per-cell dividers. NOTE: no `backdrop-blur`\n          anywhere in this bar. Every platform ships an OPAQUE header\n          background (`backgroundColor` resolves to an opaque ODS token), so a\n          backdrop-filter would blur a backdrop that is then fully painted\n          over: zero visual effect, but the browser still re-rasterizes the\n          strip behind this sticky bar every scroll frame and content flickers\n          as it passes under the header. If a consumer ever wants a\n          translucent \"glass\" header, add the blur together with a translucent\n          `backgroundColor` deliberately. */}\n      <TopNavigation\n        // `relative` anchors the absolutely-centered nav (position 'center')\n        // to the bar itself, so the links sit at the true viewport center\n        // regardless of the asymmetric CTA/Mingo cluster on the right.\n        className={cn('relative', config.className)}\n        style={config.style}\n        backgroundClassName={config.backgroundColor}\n        centerBreakpoint=\"lg\"\n        size=\"big\"\n        leading={\n          <>\n            {/* Length-guarded: platform configs often pass `left: []`, and an\n                empty array is truthy — without the guard the cell would render\n                as a bare divider + padding. */}\n            {/* No padding on the wrapper: leading cells (HeaderButton-based\n                admin toggles) size themselves to the bar's square cell. */}\n            {!!config.actions?.left?.length && (\n              <div className=\"flex h-full items-center border-r border-ods-border\">{config.actions.left}</div>\n            )}\n            {/* Mobile/tablet menu toggle — a leading cell per the ODS spec\n              (banded with the nav breakpoint `lg` so the desktop nav and the\n              toggle never co-show). */}\n            {config.mobile?.enabled && (\n              <HeaderButton\n                className=\"border-r border-ods-border lg:hidden\"\n                onClick={() => {\n                  config.mobile?.onToggle?.();\n                }}\n                isActive={config.mobile?.isOpen ?? false}\n                aria-label={config.mobile?.isOpen ? 'Close menu' : 'Open menu'}\n                aria-expanded={config.mobile?.isOpen ?? false}\n                // Conditional: the panel unmounts when closed, so an unconditional\n                // reference would dangle (axe aria-valid-attr-value).\n                aria-controls={config.mobile?.isOpen ? MOBILE_NAV_PANEL_ID : undefined}\n                icon={\n                  config.mobile?.isOpen\n                    ? config.mobile?.closeIcon || <XmarkIcon className=\"h-6 w-6\" />\n                    : config.mobile?.menuIcon || <Menu01Icon className=\"h-6 w-6\" />\n                }\n              />\n            )}\n          </>\n        }\n        logo={\n          <Link href={config.logo.href} className=\"transition-opacity duration-200 hover:opacity-80\">\n            {config.logo.element}\n          </Link>\n        }\n        // Big-bar rule (Figma 2936-6812): 24px fixed left inset on the logo\n        // zone at every breakpoint — the same 24px also reads as the gap\n        // between a leading cell (burger / admin toggle) and the logo.\n        logoClassName=\"pl-[var(--spacing-system-lf)] md:pl-[var(--spacing-system-lf)] lg:pl-[var(--spacing-system-lf)]\"\n        center={\n          hasNav ? (\n            <nav\n              className={cn(\n                'flex items-center gap-2',\n                // True centering relative to the bar (not the leftover flex\n                // space) — same treatment the pre-unification header had.\n                config.navigation?.position === 'center' && 'absolute left-1/2 -translate-x-1/2',\n              )}\n              role=\"navigation\"\n              aria-label=\"Main navigation\"\n            >\n              {config.navigation?.items.map(renderNavigationItem)}\n            </nav>\n          ) : undefined\n        }\n        centerClassName={cn(\n          config.navigation?.position === 'left' && 'justify-start',\n          config.navigation?.position === 'right' && 'justify-end pr-[var(--spacing-system-m)]',\n        )}\n        cta={\n          hasCta ? (\n            <>\n              {/* Desktop actions — banded with the nav/burger breakpoint (lg)\n                  so the desktop right-cluster and the mobile toggle never\n                  co-show. */}\n              {!!config.actions?.right?.length && (\n                <div className=\"hidden items-center gap-3 lg:flex\">{config.actions.right}</div>\n              )}\n              {config.actions?.persistent && config.actions.persistent.length > 0 && (\n                <div className=\"flex items-center\">{config.actions.persistent}</div>\n              )}\n            </>\n          ) : undefined\n        }\n        ctaClassName=\"gap-3\"\n        sideActions={\n          config.tickets || config.mingo?.enabled ? (\n            <>\n              {/* Support-ticket alerts cell — before Mingo, flush cell row.\n                  Attention-only: renders nothing unless there are unread\n                  replies (and the host mounted <TicketLiveProvider>). */}\n              {config.tickets && (\n                <TicketAlertsButton\n                  href={config.tickets.href}\n                  onNavigate={config.tickets.onClick}\n                  className=\"border-l border-ods-border\"\n                />\n              )}\n              {config.mingo?.enabled && (\n                <MingoAiButton\n                  source={config.mingo.source}\n                  icon={config.mingo.icon}\n                  label={config.mingo.label}\n                  className={config.mingo.className}\n                />\n              )}\n            </>\n          ) : undefined\n        }\n      />\n    </div>\n  );\n}\n","'use client';\n\nimport type React from 'react';\nimport { cn } from '../../utils';\nimport { MingoIcon } from '../icons';\n\nexport interface MingoAiButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n  source?: string;\n  /** The platform's Mingo identity glyph — pass the SAME server-configured\n   *  icon the chat panel renders (host-side: `EntityIcon` fed by the admin\n   *  `assistantIcon`), so the launcher and the panel can never diverge.\n   *  Falls back to the packaged Mingo mark when the server has none. */\n  icon?: React.ReactNode;\n  /** The launcher's wordmark + aria-label — pass the server-configured\n   *  assistant name (same `assistantName` the chat panel shows) so the\n   *  launcher never hardcodes an identity the admin has renamed. */\n  label?: string;\n}\n\n/**\n * Marketing-header Mingo AI launcher: the flush, full-height button rendered\n * at the far right edge of the public `Header` (`config.mingo`). Stateless:\n * clicking dispatches an `ask-ai:open` CustomEvent (source-filtered) that the\n * mounted `EmbeddableChat` panel listens for.\n *\n * Distinct from `header-mingo-button.tsx` (`HeaderMingoButton`), the\n * dashboard/AppHeader controlled toggle; different surface and contract, do\n * not merge them.\n *\n * Deliberately a raw `<button>` rather than the ui-kit `Button`: it needs\n * full-height flush cell layout, an absolutely-positioned animated ring, and\n * icon-only collapse that `Button` cannot express (same precedent as\n * `header-mingo-button.tsx`).\n */\nconst MINGO_ACCENT = 'var(--ods-flamingo-cyan-base)';\n\nexport function MingoAiButton({ source, icon, label = 'Mingo AI', className, onClick, ...props }: MingoAiButtonProps) {\n  return (\n    <button\n      {...props}\n      type=\"button\"\n      aria-label={label}\n      onClick={e => {\n        // Coalesce to '' so a source-less mount still matches EmbeddableChat's\n        // own `runtime.source ?? ''` comparison (undefined !== '' would make\n        // the panel silently ignore the event).\n        window.dispatchEvent(new CustomEvent('ask-ai:open', { detail: { source: source ?? '' } }));\n        onClick?.(e);\n      }}\n      className={cn(\n        // Unified ODS top-navigation cell (Figma 2797-6808 desktop /\n        // 2797-7275 mobile): full-height cell with a leading divider,\n        // transparent at rest so it inherits the bar's background.\n        // No hover background on the cell: the animated ring + shimmer are\n        // the ONLY hover treatment (a bg flip on top read as a double\n        // animation).\n        'group/mingo relative flex h-full shrink-0 items-center border-l border-ods-border bg-transparent px-[var(--spacing-system-m)] focus:outline-none focus-visible:ring-2 focus-visible:ring-ods-accent',\n        className,\n      )}\n    >\n      {/* Inner box = 1:1 twin of the DEFAULT-size Button geometry (rounded-md,\n          h-11 md:h-12, px-m) — the same footprint as the header's \"Try for\n          Free\" CTA — so the animated ring traces exactly where a full button\n          border would sit inside the 72px bar. */}\n      <span className=\"relative flex h-11 items-center gap-[var(--spacing-system-xsf)] rounded-md px-[var(--spacing-system-m)] md:h-12\">\n        {/* AI edge light (Apple-Intelligence-style): a rotating accent-\n            gradient arc clipped to a 3px ring on the box outline via CSS\n            mask (.mingo-edge-frame) — NO opaque cover, so the launcher\n            inherits whatever background sits behind it. Platform-tinted via\n            the accent token. */}\n        <span aria-hidden=\"true\" className=\"mingo-edge-frame pointer-events-none absolute inset-0 rounded-md\">\n          <span className=\"mingo-edge\" />\n        </span>\n        {/* One-shot light-streak shimmer on hover, clipped to the box. */}\n        <span aria-hidden=\"true\" className=\"pointer-events-none absolute inset-0 overflow-hidden rounded-md\">\n          <span\n            className=\"mingo-shimmer absolute inset-y-0 left-0 w-1/2\"\n            style={{\n              background:\n                'linear-gradient(105deg, transparent, color-mix(in srgb, var(--ods-system-greys-white) 12%, transparent), transparent)',\n            }}\n          />\n        </span>\n        {icon ? (\n          <span className=\"relative inline-flex size-6 shrink-0 items-center justify-center\">{icon}</span>\n        ) : (\n          <MingoIcon\n            color=\"currentColor\"\n            eyesColor={MINGO_ACCENT}\n            cornerColor={MINGO_ACCENT}\n            className=\"relative h-6 w-6 shrink-0 text-ods-text-primary\"\n          />\n        )}\n        {/* Wordmark collapses below md — the mobile cell is icon-only per\n            spec. h3 bold, matching the default-size CTA label next to it\n            (Figma 2936-6825). */}\n        {/* Bare `text-h3` — the exact composite the default-size Button label\n            uses (bold 700 and -0.02em tracking are built into the utility). */}\n        <span className=\"relative hidden whitespace-nowrap text-ods-text-primary text-h3 md:inline\">{label}</span>\n      </span>\n    </button>\n  );\n}\n\nexport default MingoAiButton;\n","'use client';\n\nimport { usePreventScroll } from '@react-aria/overlays';\nimport { X } from 'lucide-react';\nimport type React from 'react';\nimport { useRef } from 'react';\nimport { useFocusTrap } from '../../hooks/ui/use-focus-trap';\nimport { useHeaderHeight } from '../../hooks/ui/use-header-height';\nimport type { MobileNavConfig, NavigationItem } from '../../types/navigation';\nimport { cn } from '../../utils';\nimport { Button } from '../ui/button';\nimport { OVERLAY_BACKDROP_CLASS } from '../ui/drawer';\n\n/** DOM id of the panel — referenced by the header hamburger's `aria-controls`. */\nexport const MOBILE_NAV_PANEL_ID = 'mobile-nav-panel';\n\nexport interface MobileNavPanelProps {\n  isOpen: boolean;\n  config: MobileNavConfig;\n}\n\nexport function MobileNavPanel({ isOpen, config }: MobileNavPanelProps) {\n  const panelRef = useRef<HTMLDivElement>(null);\n\n  // Shared ref-counted, iOS-aware scroll lock (react-aria) — one counter with\n  // the modals and chat overlays, restores prior styles on release.\n  usePreventScroll({ isDisabled: !isOpen });\n  // Initial focus, Tab containment, Escape-to-close, guarded focus restore.\n  useFocusTrap(panelRef, isOpen, { onEscape: config.onClose });\n  // The panel is layout-mounted on every page — observers only while open.\n  const headerHeight = useHeaderHeight(64, { enabled: isOpen });\n\n  if (!isOpen) return null;\n\n  const renderNavigationItem = (item: NavigationItem) => {\n    if (item.element) {\n      return <div key={item.id}>{item.element}</div>;\n    }\n\n    const handleClick = () => {\n      if (item.onClick) {\n        item.onClick();\n      }\n      config.onClose?.();\n    };\n\n    if (item.href) {\n      return (\n        <Button\n          key={item.id}\n          variant=\"outline\"\n          size=\"small-legacy\"\n          href={item.href}\n          onClick={handleClick}\n          leftIcon={item.icon}\n          rightIcon={item.badge}\n          className={cn(\n            'h-12 justify-start gap-3 rounded-md border-none bg-transparent px-4 text-ods-text-primary transition-colors hover:bg-ods-bg-hover',\n            item.isActive ? 'bg-ods-bg-hover' : '',\n          )}\n        >\n          {item.label}\n        </Button>\n      );\n    }\n\n    return (\n      <Button\n        key={item.id}\n        variant=\"outline\"\n        size=\"small-legacy\"\n        onClick={handleClick}\n        leftIcon={item.icon}\n        className={cn(\n          'h-12 justify-start gap-3 rounded-md border-none bg-transparent px-4 text-ods-text-primary transition-colors hover:bg-ods-bg-hover',\n          item.isActive ? 'bg-ods-bg-hover' : '',\n        )}\n      >\n        <span className=\"flex-1 text-left\">{item.label}</span>\n        {item.badge !== undefined && <span className=\"ml-auto\">{item.badge}</span>}\n      </Button>\n    );\n  };\n\n  return (\n    <>\n      {/* Backdrop - closes nav when clicked outside */}\n      <div className={cn('fixed inset-0 z-[9998]', OVERLAY_BACKDROP_CLASS)} onClick={config.onClose} />\n\n      {/* Navigation Panel - top-anchored floating card */}\n      <div\n        ref={panelRef}\n        id={MOBILE_NAV_PANEL_ID}\n        role=\"dialog\"\n        aria-modal=\"true\"\n        aria-label=\"Navigation menu\"\n        tabIndex={-1}\n        style={{ '--mobile-nav-top': `${headerHeight + 16}px` } as React.CSSProperties}\n        className={cn(\n          'fixed z-[9999] rounded-lg shadow-xl',\n          config.className ? '' : 'border border-ods-border bg-ods-card',\n          // Responsive positioning and sizing — LEFT-anchored from md, flush\n          // under the burger cell at the left edge of the unified\n          // top-navigation (a larger inset read as a stray gap between the\n          // burger and its panel).\n          'left-2 right-2 md:right-auto',\n          'md:left-2 md:w-[400px] md:max-w-[calc(100vw-1rem)]',\n          // Small-viewport (svh) sizing anchored below the measured header —\n          // 100vh overflowed under mobile browser chrome, leaving the bottom\n          // entries and footer unreachable while the body was scroll-locked.\n          'top-[var(--mobile-nav-top)]',\n          'max-h-[calc(100svh-var(--mobile-nav-top)-50px)] md:max-h-[calc(100svh-var(--mobile-nav-top)-8px)]',\n          'flex flex-col',\n          config.className || '',\n        )}\n        onClick={e => e.stopPropagation()} // Prevent closing when clicking inside nav\n      >\n        {/* Header with close button - fixed at top */}\n        <div className=\"flex flex-shrink-0 justify-end border-b border-ods-border p-2\">\n          <Button\n            aria-label=\"Close menu\"\n            size=\"icon\"\n            variant=\"transparent\"\n            leftIcon={<X className=\"h-4 w-4 text-ods-text-primary\" />}\n            onClick={config.onClose}\n            className=\"hover:bg-ods-bg-hover\"\n          />\n        </div>\n\n        {/* Scrollable content area */}\n        <div className=\"flex-1 overflow-y-auto overflow-x-hidden overscroll-contain px-2 py-2\">\n          <div className=\"flex flex-col space-y-1\">\n            {config.sections.map((section, index) => (\n              <div key={index}>\n                {section.title && (\n                  <div className=\"px-4 pb-1 pt-2 font-semibold uppercase text-ods-text-secondary text-h6\">\n                    {section.title}\n                  </div>\n                )}\n                <div className=\"flex flex-col gap-1\">{section.items.map(renderNavigationItem)}</div>\n              </div>\n            ))}\n          </div>\n        </div>\n\n        {/* Footer with action button - fixed at bottom, clears the iOS home indicator */}\n        {config.footer && (\n          <div className=\"flex-shrink-0 border-t border-ods-border p-4 pb-[max(1rem,env(safe-area-inset-bottom))]\">\n            {config.footer}\n          </div>\n        )}\n      </div>\n    </>\n  );\n}\n","'use client';\n\n/**\n * TicketAlertsButton — the unified header affordance for support-ticket\n * unread indication. Mounted by BOTH header shells (hub `Header` via\n * `HeaderConfig.tickets`, console `AppHeader` via `showTicketAlerts`).\n *\n * ATTENTION-ONLY element, deliberately unlike the always-on header cells:\n *   - renders NOTHING unless there are unread support replies (and a\n *     `TicketLiveProvider` is mounted and the viewer is signed in) —\n *     appearing IS the indication, and it disappears once everything\n *     is read;\n *   - standard header-cell glyph coloring (matches the notifications /\n *     time-tracker cells) + accent count pill (2 updates → \"2\", the\n *     sidebar count-pill treatment);\n *   - clicking routes to the ticket with the NEWEST unread reply via the\n *     SSOT deep link (`buildTicketOpenHref` → `<href>?ticket=<id>`): the\n *     `?ticket=` param is the ONE open-drawer source of truth in\n *     `HelpCenterList`, and opening the drawer already smooth-scrolls\n *     the row. With multiple updates the pill stays up with the\n *     remaining count and the next click routes to the next-newest\n *     unread ticket.\n *\n * Design note: the console's NATS-backed `NotificationsProvider` bell\n * covers OpenFrame-internal events; Help Center replies come from the\n * HubSpot mirror domain and must ALSO surface on the hub, which mounts\n * no notifications system — so the affordance both hosts can share is\n * this dedicated cell, not the bell.\n */\n\nimport { cn } from '../../utils/cn';\nimport { LifeBuoyIcon } from '../icons-v2-generated/interface/life-buoy-icon';\nimport { useOptionalTicketLive } from '../tickets/ticket-live-provider';\nimport { buildTicketOpenHref } from '../tickets/types';\nimport { HeaderButton } from './header-button';\nimport { UnreadCountBadge } from './unread-dot';\n\nexport interface TicketAlertsButtonProps {\n  /** BASE path of the host's tickets surface — may carry ANY nesting\n   *  prefix (hub `/tickets`, console `/help-center/tickets`, an\n   *  embedder's `/support/portal/tickets`). The deep link is built by\n   *  the SSOT `buildTicketOpenHref` (`<base>?ticket=<id>` — the same\n   *  param `HelpCenterList` derives its drawer state from; opening the\n   *  drawer also smooth-scrolls the row). */\n  href: string;\n  /** Host navigation (router push) — receives the FULL computed href.\n   *  Defaults to `window.location.assign`. */\n  onNavigate?: (href: string) => void;\n  disabled?: boolean;\n  className?: string;\n}\n\nexport function TicketAlertsButton({ href, onNavigate, disabled, className }: TicketAlertsButtonProps) {\n  const live = useOptionalTicketLive();\n  // Attention-only: no provider, signed out, or nothing unread → no cell.\n  if (!live || !live.authed || live.unreadTotal === 0) return null;\n\n  const targetId = live.nextUnreadTicketId;\n  const target = targetId ? buildTicketOpenHref(href, targetId) : href;\n\n  const handleClick = () => {\n    if (onNavigate) onNavigate(target);\n    else window.location.assign(target);\n  };\n\n  return (\n    <HeaderButton\n      icon={\n        // Glyph inherits HeaderButton's standard cell coloring (secondary at\n        // rest, primary on active) — SAME treatment as the notifications\n        // bell and time-tracker cells. The indication is carried by the\n        // count pill (sidebar count-pill precedent) and by the cell only\n        // existing while updates are unread — never by tinting the glyph.\n        <span className=\"relative inline-flex\">\n          <LifeBuoyIcon className=\"h-6 w-6\" />\n          <UnreadCountBadge count={live.unreadTotal} />\n        </span>\n      }\n      aria-label={`Support tickets — ${live.unreadTotal} unread ${live.unreadTotal === 1 ? 'update' : 'updates'}`}\n      onClick={handleClick}\n      disabled={disabled}\n      className={cn(className)}\n    />\n  );\n}\n\nexport default TicketAlertsButton;\n","'use client';\n\nimport type React from 'react';\nimport { cn } from '../../utils/cn';\n\nexport type TopNavigationCenterBreakpoint = 'md' | 'lg';\n\nexport type TopNavigationSize = 'small' | 'big';\n\n// Bar height + square-cell width per size (Figma 2797-5978 `size` axis).\n// Heights are FIXED across breakpoints. `--top-nav-cell` is consumed by\n// `HeaderButton` (and any custom cell) so cell widths follow the size without\n// each cell knowing about it.\nconst SIZE_CLASSES: Record<TopNavigationSize, string> = {\n  small: 'h-14 [--top-nav-cell:3.5rem]',\n  big: 'h-[72px] [--top-nav-cell:4.5rem]',\n};\n\n// Literal class maps — Tailwind's scanner needs the full class strings.\nconst CENTER_VISIBILITY: Record<TopNavigationCenterBreakpoint, string> = {\n  md: 'hidden md:flex',\n  lg: 'hidden lg:flex',\n};\nconst LOGO_GROW: Record<TopNavigationCenterBreakpoint, string> = {\n  md: 'flex-1 md:flex-none',\n  lg: 'flex-1 lg:flex-none',\n};\n\nexport interface TopNavigationProps extends React.HTMLAttributes<HTMLElement> {\n  /** Leading cells (burger toggle, admin-sidebar toggle). Rendered raw, first\n   *  in the row — cells carry their own trailing divider\n   *  (`border-r border-ods-border`), matching the ODS spec where dividers\n   *  belong to the cells, not the bar. */\n  leading?: React.ReactNode;\n  /** Logo zone content. Grows (`flex-1`) below `centerBreakpoint`, shrinks to\n   *  content above it. Wrap in a `<Link>` on the consumer side if clickable. */\n  logo?: React.ReactNode;\n  /** Extra classes for the logo zone. Defaults follow the ODS spec paddings:\n   *  `p-m` on mobile, `pl-l` (24px) from md, `pl-xxl` (80px) from lg — the\n   *  desktop inset mirrors the leading menu-cell width so centered nav links\n   *  sit visually centered (Figma 2797-6808 / 2805-6194). */\n  logoClassName?: string;\n  /** Center zone (marketing nav links, console global search). Hidden below\n   *  `centerBreakpoint`; above it, it is the `flex-1` region that pushes the\n   *  CTA and side actions to the right edge. Rendered even when empty so the\n   *  bar always has its spacer. */\n  center?: React.ReactNode;\n  centerClassName?: string;\n  /** Breakpoint at which the center zone appears (and the logo zone stops\n   *  growing). ODS spec default is `lg` (nav links are desktop-only);\n   *  the console header uses `md` for its global search. */\n  centerBreakpoint?: TopNavigationCenterBreakpoint;\n  /** CTA zone — right-aligned, padded, no dividers (per ODS spec). */\n  cta?: React.ReactNode;\n  ctaClassName?: string;\n  /** Trailing action cells (time tracker, notifications, avatar, Mingo).\n   *  Rendered raw — each cell carries its own leading divider\n   *  (`border-l border-ods-border`). */\n  sideActions?: React.ReactNode;\n  /** Opaque ODS background class. Defaults to `bg-ods-card`. Keep it opaque —\n   *  translucent backgrounds under a sticky bar need a deliberate\n   *  backdrop-blur treatment (see the note in `header.tsx`). */\n  backgroundClassName?: string;\n  /** Top border on mobile (the ODS spec bar shows it below md). Default true. */\n  mobileTopBorder?: boolean;\n  /** Bar size (Figma 2797-5978 `size` axis): `small` = 56px (console),\n   *  `big` = 72px (marketing/hub sites). Fixed across breakpoints. */\n  size?: TopNavigationSize;\n}\n\nconst hasContent = (node: React.ReactNode): boolean => node !== null && node !== undefined && node !== false;\n\n/**\n * Unified ODS top-navigation bar (Figma `[UPD] top-navigation`, node\n * 2797-5978): the single 48px/56px (mobile / md+) cell-based header shell\n * shared by the console `AppHeader` and the marketing `Header` across all\n * platforms.\n *\n * The shell owns the bar geometry only — height, borders, background, and the\n * zone layout `[leading][logo][center][cta][sideActions]`. What goes inside\n * the zones (and each cell's divider) is the consumer's job, so platforms can\n * differ in background and in which elements they mount without forking the\n * bar itself.\n */\nexport function TopNavigation({\n  leading,\n  logo,\n  logoClassName,\n  center,\n  centerClassName,\n  centerBreakpoint = 'lg',\n  cta,\n  ctaClassName,\n  sideActions,\n  backgroundClassName,\n  mobileTopBorder = true,\n  size = 'small',\n  className,\n  children,\n  ...props\n}: TopNavigationProps) {\n  return (\n    <header\n      className={cn(\n        'flex w-full items-center border-b border-ods-border',\n        SIZE_CLASSES[size],\n        mobileTopBorder && 'border-t md:border-t-0',\n        backgroundClassName ?? 'bg-ods-card',\n        className,\n      )}\n      {...props}\n    >\n      {leading}\n      {hasContent(logo) && (\n        <div\n          className={cn(\n            'flex h-full min-w-0 items-center',\n            LOGO_GROW[centerBreakpoint],\n            'p-[var(--spacing-system-m)] md:py-0 md:pl-[var(--spacing-system-l)] md:pr-0 lg:pl-[var(--spacing-system-xxl)]',\n            logoClassName,\n          )}\n        >\n          {logo}\n        </div>\n      )}\n      <div\n        className={cn(\n          CENTER_VISIBILITY[centerBreakpoint],\n          'h-full min-w-0 flex-1 items-center justify-center',\n          centerClassName,\n        )}\n      >\n        {center}\n      </div>\n      {hasContent(cta) && (\n        <div className={cn('flex h-full shrink-0 items-center px-[var(--spacing-system-m)]', ctaClassName)}>{cta}</div>\n      )}\n      {sideActions}\n      {children}\n    </header>\n  );\n}\n\nexport default TopNavigation;\n","import type { HeaderConfig } from '../../types/navigation';\nimport { cn } from '../../utils';\n\nexport interface HeaderSkeletonProps {\n  config?: HeaderConfig;\n}\n\n/**\n * Loading skeleton mirroring the unified ODS top-navigation geometry\n * (`TopNavigation`, Figma 2797-5978): 48px mobile / 56px md+, cell model.\n * Keep in sync with `header.tsx` — a diverging skeleton makes the\n * skeleton→live handoff jump.\n */\nexport function HeaderSkeleton({ config }: HeaderSkeletonProps) {\n  const showNavigation = config?.navigation && config.navigation.items.length > 0;\n  const showActions = config?.actions?.right && config.actions.right.length > 0;\n  const showMobileMenu = config?.mobile?.enabled;\n  const showLeftActions = !!config?.actions?.left;\n\n  return (\n    <div className=\"sticky top-0 z-[50] w-full\">\n      <header\n        className={cn(\n          'flex h-[72px] w-full items-center border-b border-t border-ods-border md:border-t-0',\n          config?.backgroundColor || 'bg-ods-card',\n          config?.className,\n        )}\n      >\n        {/* Leading cells: admin toggle / burger */}\n        {showLeftActions && (\n          <div className=\"flex h-full w-[72px] items-center justify-center border-r border-ods-border\">\n            <div className=\"h-6 w-6 animate-pulse rounded bg-ods-border\" />\n          </div>\n        )}\n        {showMobileMenu && (\n          <div className=\"flex h-full w-[72px] items-center justify-center border-r border-ods-border lg:hidden\">\n            <div className=\"h-6 w-6 animate-pulse rounded bg-ods-border\" />\n          </div>\n        )}\n\n        {/* Logo zone */}\n        <div className=\"flex h-full flex-1 items-center gap-2 p-[var(--spacing-system-m)] md:pl-[var(--spacing-system-l)] lg:flex-none lg:pl-[var(--spacing-system-xxl)]\">\n          <div className=\"h-6 w-6 animate-pulse rounded bg-ods-border\" />\n          <div className=\"h-5 w-24 animate-pulse rounded bg-ods-border\" />\n        </div>\n\n        {/* Center: navigation links (desktop only) */}\n        <div className=\"hidden h-full min-w-0 flex-1 items-center justify-center gap-2 lg:flex\">\n          {showNavigation && (\n            <>\n              <div className=\"h-8 w-20 animate-pulse rounded bg-ods-border\" />\n              <div className=\"h-8 w-28 animate-pulse rounded bg-ods-border\" />\n              <div className=\"h-8 w-24 animate-pulse rounded bg-ods-border\" />\n            </>\n          )}\n        </div>\n\n        {/* CTA zone */}\n        {showActions && (\n          <div className=\"hidden h-full items-center px-[var(--spacing-system-m)] lg:flex\">\n            <div className=\"h-8 w-24 animate-pulse rounded bg-ods-border\" />\n          </div>\n        )}\n\n        {/* Mingo cell */}\n        {config?.mingo?.enabled && (\n          <div className=\"flex h-full items-center border-l border-ods-border px-[var(--spacing-system-l)]\">\n            <div className=\"h-8 w-8 animate-pulse rounded bg-ods-border md:w-28\" />\n          </div>\n        )}\n      </header>\n    </div>\n  );\n}\n","'use client';\n\nimport type { ReactNode } from 'react';\nimport { useIsHydrated } from '../../hooks/ui/use-is-hydrated';\nimport { Header, type HeaderConfig } from './header';\nimport { HeaderSkeleton } from './header-skeleton';\n\nexport interface ClientOnlyHeaderProps {\n  config: HeaderConfig;\n  skeleton?: ReactNode;\n}\n\nexport function ClientOnlyHeader({ config, skeleton }: ClientOnlyHeaderProps) {\n  const isClient = useIsHydrated();\n\n  if (!isClient) {\n    // Return custom skeleton or default skeleton while client-side JavaScript loads\n    return skeleton || <HeaderSkeleton config={config} />;\n  }\n\n  return <Header config={config} />;\n}\n","'use client';\n\nimport { usePreventScroll } from '@react-aria/overlays';\nimport { motion, AnimatePresence, useReducedMotion } from 'framer-motion';\nimport type React from 'react';\nimport { useState, useRef } from 'react';\nimport { useHeaderHeight } from '../../hooks/ui';\nimport { useFocusTrap } from '../../hooks/ui/use-focus-trap';\nimport { useIsHydrated } from '../../hooks/ui/use-is-hydrated';\nimport type { SlidingSidebarConfig, NavigationItem } from '../../types/navigation';\nimport { cn } from '../../utils';\nimport { Button } from '../ui/button';\nimport { OVERLAY_BACKDROP_CLASS } from '../ui/drawer';\n\nexport interface SlidingSidebarProps {\n  config: SlidingSidebarConfig;\n}\n\nexport function SlidingSidebar({ config }: SlidingSidebarProps) {\n  const [expandedItems, setExpandedItems] = useState<Set<string>>(new Set());\n  const mounted = useIsHydrated();\n  const headerHeight = useHeaderHeight();\n  const asideRef = useRef<HTMLElement>(null);\n  const prefersReducedMotion = useReducedMotion();\n\n  // Shared ref-counted scroll lock (react-aria) while the drawer is open.\n  usePreventScroll({ isDisabled: !config.isOpen });\n  // Escape + initial focus + guarded restore. `contain: false`: the z-50\n  // header stays visible and clickable ABOVE the open drawer (see the z-tier\n  // comment below), so this is a NON-modal dialog — Tab may leave freely.\n  useFocusTrap(asideRef, config.isOpen, { onEscape: config.onClose, contain: false });\n\n  const toggleExpanded = (itemId: string) => {\n    const newExpanded = new Set(expandedItems);\n    if (newExpanded.has(itemId)) {\n      newExpanded.delete(itemId);\n    } else {\n      newExpanded.add(itemId);\n    }\n    setExpandedItems(newExpanded);\n  };\n\n  const renderMenuItem = (item: NavigationItem, level = 0): React.ReactNode => {\n    // If custom element provided, render it\n    if (item.element) {\n      return <div key={item.id}>{item.element}</div>;\n    }\n\n    // Bind the (optional) child list once so the branch below maps a real\n    // array instead of asserting `item.children` back into existence.\n    const children = item.children ?? [];\n    const hasChildren = children.length > 0;\n    const isExpanded = expandedItems.has(item.id);\n\n    if (hasChildren) {\n      const chevronIcon = (\n        <svg\n          className={cn('h-4 w-4 transition-transform', isExpanded && 'rotate-90')}\n          fill=\"none\"\n          stroke=\"currentColor\"\n          viewBox=\"0 0 24 24\"\n        >\n          <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M9 5l7 7-7 7\" />\n        </svg>\n      );\n\n      return (\n        <div key={item.id} className=\"space-y-1\">\n          <Button\n            variant=\"transparent\"\n            size=\"default\"\n            onClick={() => toggleExpanded(item.id)}\n            leftIcon={item.icon}\n            rightIcon={chevronIcon}\n            className={cn(\n              '!w-full overflow-hidden rounded-lg transition-colors md:!w-full',\n              level === 0 ? 'pl-3' : 'pl-6',\n              item.isActive\n                ? 'bg-ods-accent text-ods-text-on-accent hover:bg-ods-accent/90'\n                : 'text-ods-text-primary hover:bg-ods-border',\n            )}\n          >\n            <span className=\"truncate\">{item.label}</span>\n            {item.badge !== undefined && <span className=\"ml-auto mr-2 flex-shrink-0\">{item.badge}</span>}\n          </Button>\n\n          <AnimatePresence>\n            {isExpanded && (\n              <motion.div\n                initial={{ height: 0, opacity: 0 }}\n                animate={{ height: 'auto', opacity: 1 }}\n                exit={{ height: 0, opacity: 0 }}\n                transition={{\n                  duration: 0.15,\n                  ease: 'easeOut',\n                  height: { duration: 0.2 },\n                  opacity: { duration: 0.1 },\n                }}\n                className=\"overflow-hidden\"\n              >\n                <div className=\"space-y-1 pl-4\">{children.map(child => renderMenuItem(child, level + 1))}</div>\n              </motion.div>\n            )}\n          </AnimatePresence>\n        </div>\n      );\n    }\n\n    // Leaf node - either link or button\n    if (item.href) {\n      // For Next.js apps, onClick handler should handle navigation\n      return (\n        <Button\n          key={item.id}\n          variant=\"transparent\"\n          size=\"default\"\n          onClick={() => {\n            // Let the parent handle navigation\n            item.onClick?.();\n            config.onClose();\n          }}\n          leftIcon={item.icon}\n          className={cn(\n            '!w-full overflow-hidden rounded-lg transition-colors md:!w-full',\n            level === 0 ? 'pl-3' : 'pl-6',\n            item.isActive\n              ? 'bg-ods-accent text-ods-text-on-accent hover:bg-ods-accent/90'\n              : 'text-ods-text-primary hover:bg-ods-border',\n          )}\n        >\n          <span className=\"truncate\">{item.label}</span>\n          {item.badge !== undefined && <span className=\"ml-auto flex-shrink-0\">{item.badge}</span>}\n        </Button>\n      );\n    }\n\n    return (\n      <Button\n        key={item.id}\n        variant=\"transparent\"\n        size=\"default\"\n        onClick={() => {\n          item.onClick?.();\n          config.onClose();\n        }}\n        leftIcon={item.icon}\n        className={cn(\n          '!w-full overflow-hidden rounded-lg transition-colors md:!w-full',\n          level === 0 ? 'pl-3' : 'pl-6',\n          item.isActive\n            ? 'bg-ods-accent text-ods-text-on-accent hover:bg-ods-accent/90'\n            : 'text-ods-text-primary hover:bg-ods-border',\n        )}\n      >\n        <span className=\"truncate\">{item.label}</span>\n        {item.badge !== undefined && <span className=\"ml-auto flex-shrink-0\">{item.badge}</span>}\n      </Button>\n    );\n  };\n\n  if (!mounted) {\n    return null;\n  }\n\n  return (\n    <>\n      {/* Overlay — admin-drawer tier (overlay z-[45] / panel z-[46]): ABOVE the\n          footer (lowered to z-[44]) so the open drawer covers it, but BEHIND the\n          header (z-[50]) so the header stays visible on top (the panel has a\n          header spacer below). Kept under z-50 so it also stays beneath modals/\n          dialogs (z-50+). See the z-index hierarchy in ODS_TOKEN_RULES.md. */}\n      <AnimatePresence>\n        {config.isOpen && (\n          <motion.div\n            initial={{ opacity: 0 }}\n            animate={{ opacity: 1 }}\n            exit={{ opacity: 0 }}\n            transition={{\n              duration: 0.3,\n              ease: [0.4, 0, 0.2, 1],\n            }}\n            className={cn('fixed inset-0 z-[45]', OVERLAY_BACKDROP_CLASS)}\n            onClick={() => config.onClose()}\n          />\n        )}\n      </AnimatePresence>\n\n      {/* Sliding Sidebar */}\n      <motion.aside\n        ref={asideRef}\n        initial={false}\n        animate={{\n          x: config.isOpen ? 0 : config.position === 'right' ? '100%' : '-100%',\n        }}\n        transition={\n          prefersReducedMotion\n            ? { duration: 0 }\n            : {\n                type: 'spring',\n                damping: 25,\n                stiffness: 300,\n                mass: 0.8,\n                restDelta: 0.01,\n                velocity: config.isOpen ? 5 : -5,\n              }\n        }\n        tabIndex={-1}\n        // Stays mounted translated off-screen — inert keeps its items out of\n        // the tab order while \"closed\". Non-modal dialog: no aria-modal (the\n        // header above remains reachable by design).\n        inert={!config.isOpen || undefined}\n        role={config.isOpen ? 'dialog' : undefined}\n        aria-label={config.isOpen ? 'Navigation sidebar' : undefined}\n        className={cn(\n          'fixed bottom-0 top-0 z-[46] flex w-72 flex-col border-ods-border bg-ods-card shadow-xl',\n          config.position === 'right' ? 'right-0 border-l' : 'left-0 border-r',\n          config.className,\n        )}\n      >\n        {/* Header spacer - dynamic height */}\n        <div className=\"flex-shrink-0\" style={{ height: `${headerHeight}px` }} />\n\n        {/* Navigation */}\n        <nav className=\"flex-1 space-y-2 overflow-y-auto overscroll-contain p-4\">\n          {config.items.map(item => renderMenuItem(item))}\n        </nav>\n\n        {/* Footer - bottom padding clears the iOS home indicator */}\n        {config.footer && (\n          <div className=\"border-t border-ods-border p-4 pb-[max(1rem,env(safe-area-inset-bottom))]\">\n            {config.footer}\n          </div>\n        )}\n      </motion.aside>\n    </>\n  );\n}\n","'use client';\n\nimport { useCallback, useEffect, useMemo, useState } from 'react';\nimport type { MouseEvent } from 'react';\nimport { useIsHydrated } from '../../hooks/ui/use-is-hydrated';\nimport { useIsomorphicLayoutEffect } from '../../hooks/ui/use-isomorphic-layout-effect';\nimport { useLocalStorage } from '../../hooks/ui/use-local-storage';\nimport { useLgUp, useMdUp } from '../../hooks/ui/use-media-query';\nimport type { NavigationSidebarConfig, NavigationSidebarItem } from '../../types/navigation';\nimport { cn } from '../../utils';\nimport { NavigationSidebarHeader } from './navigation-sidebar-header';\nimport { NavigationSidebarItemButton, NavigationSidebarItemSkeleton } from './navigation-sidebar-item';\nimport { NavigationSidebarToggle } from './navigation-sidebar-toggle';\n\nconst MINIMIZED_WIDTH = 56; // 3.5rem = 56px\nconst EXPANDED_WIDTH = 224; // 14rem = 224px\nconst STORAGE_KEY = 'of.navigationSidebar.minimized';\n\n/**\n * The desktop width, as a custom property on `:root`. Read by the `lg:` rule in\n * {@link SIDEBAR_GEOMETRY_CLASSES} and written by this component whenever the\n * user toggles — but NEVER during render.\n *\n * It has to be a global variable rather than an inline style because of WHEN the\n * value is knowable. The width follows a preference in `localStorage`, which the\n * server cannot read: any markup derived from it differs between the server's\n * HTML and the client's hydration render, and React tears the tree down and\n * regenerates it. Seeding this property from a tiny script in `<head>` moves the\n * preference OUT of React's rendered output — the markup becomes identical on\n * both sides, while the width is already correct on the very first paint.\n *\n * Consumers are expected to seed it before first paint; unseeded, the fallback\n * in the class below paints the expanded width until this component's effect\n * catches up, which is the flash the seed exists to remove.\n */\nexport const NAVIGATION_SIDEBAR_WIDTH_VAR = '--of-navigation-sidebar-width';\n\n/**\n * The rail as a query container, so the parts of the sidebar that follow its\n * collapsed state can read that state off the rail's own width — currently the\n * collapse chevron's direction, in `navigation-sidebar-toggle.tsx`.\n *\n * There is no second source of truth for \"is it collapsed\", and that is the\n * point: the width already has to be correct on the first paint and at every\n * breakpoint, so anything derived from it is too — the seeded desktop\n * preference, the tablet rail and an overlay opened on tablet all come along for\n * free. A parallel copy would have to be kept in step with each of the three\n * separately, and the custom property that used to live here fell out of step on\n * the last one: it could not see the tablet rail, so that case got its own media\n * query, which in turn could not see an open overlay and pointed the chevron\n * backwards under it.\n *\n * Queried as `@[140px]/of-nav-sidebar:` — 140px is the midpoint between the 56px\n * rail and the 224px expanded sidebar, so the width only crosses it mid-toggle.\n * Container queries are min-width, so a follower styles its COLLAPSED look as the\n * default and lets the query take it back. Written out literally at each use\n * rather than interpolated from a constant: Tailwind scans source text, and a\n * class it cannot see spelled out is a class it never generates.\n */\nconst SIDEBAR_CONTAINER = '@container/of-nav-sidebar';\n\n/**\n * Where the sidebar sits and how wide it is, per breakpoint — as literal classes,\n * because this cannot be decided in JavaScript in time.\n *\n * `useMdUp`/`useLgUp` answer `undefined` until an effect has run, so `isTablet`\n * is false on the first render — the SERVER's render included. A tablet\n * therefore received the desktop branch in its HTML (`relative`, `width:224px`,\n * an inline style no class could outrank) and only snapped to the 56px rail once\n * hydration and an effect had both completed. That is the wide sidebar that\n * flashed on every load, and no amount of JS could fix it: the server has no\n * viewport to consult, so the answer has to be deferred to the one consumer that\n * always knows — the browser's own media evaluation.\n *\n * The state that a viewport CANNOT determine still lives outside the render: the\n * persisted desktop preference reaches the `lg:` rule through\n * {@link NAVIGATION_SIDEBAR_WIDTH_VAR}. An inline width would be simpler than a\n * custom property, but it would also override the tablet rail on the very first\n * paint, which is the bug being fixed.\n *\n * The tablet WIDTH is deliberately not here — see {@link SIDEBAR_TABLET_WIDTH}.\n *\n * Widths are `w-14`/`w-56` — the class equivalents of `MINIMIZED_WIDTH` and\n * `EXPANDED_WIDTH`, as is the `14rem` fallback below. Keep them in step; a class\n * string cannot interpolate a constant.\n */\nconst SIDEBAR_GEOMETRY_CLASSES = [\n  // Tablet: float over the content, anchored to the layout row (`absolute`\n  // within AppLayout's `relative` row) — NOT the viewport — so an optional\n  // `topBar` above the row is not overlapped. With no topBar the row spans the\n  // full viewport, so this is visually identical to a viewport-fixed sidebar.\n  'md:absolute md:inset-y-0 md:left-0 md:z-[45]',\n  // Desktop: back in the flex flow, width from the persisted preference. The\n  // fallback IS the default, so the property needs no global declaration and an\n  // unseeded consumer gets the full sidebar rather than a rail with no labels.\n  'lg:relative lg:inset-auto lg:z-auto lg:h-full lg:w-[var(--of-navigation-sidebar-width,14rem)]',\n].join(' ');\n\n/**\n * The tablet width, as ONE `md:` class rather than a rail plus an override.\n *\n * Tablet is a 56px rail by design and only widens for an overlay the user has\n * opened — state no viewport can imply, so it has to come from JS. Written as\n * `md:w-14` + `max-lg:w-56` the two rules both match between md and lg at equal\n * specificity, and Tailwind emits the `max-lg` block BEFORE the `md` one, so\n * source order hands the tablet straight back to the rail. Picking one class\n * removes the contest instead of trying to win it — and keeps the `!important`\n * that winning it would need out of the tree.\n *\n * `lg:` still governs desktop either way: it is emitted after `md:`, so an\n * overlay left open while the window widens does not leak into the desktop width.\n */\nconst SIDEBAR_TABLET_WIDTH = { rail: 'md:w-14', overlay: 'md:w-56' } as const;\n\n/** A click the browser will resolve itself — new tab, new window, middle button. */\nconst isModifiedClick = (event?: MouseEvent): boolean =>\n  !!event && (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey || event.button !== 0);\n\nexport interface NavigationSidebarProps {\n  config: NavigationSidebarConfig;\n  /**\n   * When true, all navigation items are disabled and visually dimmed.\n   * The collapse/expand toggle button remains interactive.\n   */\n  disabled?: boolean;\n}\n\nexport function NavigationSidebar({ config, disabled = false }: NavigationSidebarProps) {\n  const isMdUp = useMdUp() ?? false;\n  const isLgUp = useLgUp() ?? false;\n\n  // Tablet = md viewport but not lg. On tablet the sidebar floats over the\n  // content area (overlay) instead of pushing it like on desktop.\n  const isTablet = isMdUp && !isLgUp;\n\n  // Desktop preference persists across sessions. Tablet state is in-memory\n  // only so entering tablet always starts minimized without clobbering the\n  // user's desktop choice.\n  const [desktopMinimized, setDesktopMinimized] = useLocalStorage<boolean>(STORAGE_KEY, config.minimized ?? false);\n  const [tabletMinimized, setTabletMinimized] = useState(true);\n\n  // Entering tablet always starts minimized. Adjusted while rendering — React's\n  // documented pattern for a reset driven by a value the render already has —\n  // rather than from an effect, which committed the crossover frame with the\n  // sidebar still expanded over the content and collapsed it on the pass after.\n  const [tabletFor, setTabletFor] = useState(isTablet);\n  if (tabletFor !== isTablet) {\n    setTabletFor(isTablet);\n    if (isTablet) setTabletMinimized(true);\n  }\n\n  // `useLocalStorage` reads the store SYNCHRONOUSLY in its initializer, so on the\n  // hydration render it already knows a preference the server could not. Anything\n  // derived from it — labels, `aria-hidden`, the chevron, the placeholder rows —\n  // then differs from the server's HTML, and React throws out the whole tree and\n  // regenerates it. So until hydration is done, this renders the server's answer\n  // and nothing else.\n  //\n  // That costs nothing visually. The width is already correct at that point (it\n  // comes from the seeded CSS var, not from here), and at the minimized width\n  // every label is a `flex-1` in a rail with no room to give — zero-wide whatever\n  // this says. What flips on the next commit is opacity and margin on boxes that\n  // were never visible.\n  const hydrated = useIsHydrated();\n\n  const minimized = hydrated ? (isTablet ? tabletMinimized : desktopMinimized) : (config.minimized ?? false);\n\n  // Enable transitions only after the correct width is painted\n  const [transitionsEnabled, setTransitionsEnabled] = useState(false);\n\n  const isOverlayOpen = isTablet && !minimized;\n\n  const showLabel = !minimized;\n\n  const handleToggle = useCallback(() => {\n    if (isTablet) {\n      setTabletMinimized(prev => !prev);\n    } else {\n      setDesktopMinimized(prev => !prev);\n    }\n    config.onToggleMinimized?.();\n  }, [isTablet, setDesktopMinimized, config]);\n\n  const closeOverlay = useCallback(() => {\n    setTabletMinimized(true);\n  }, []);\n\n  // Dismiss the tablet overlay with Escape so it behaves like a transient panel\n  useEffect(() => {\n    if (!isOverlayOpen) return undefined;\n    const handleKeyDown = (e: KeyboardEvent) => {\n      if (e.key === 'Escape') closeOverlay();\n    };\n    document.addEventListener('keydown', handleKeyDown);\n    return () => document.removeEventListener('keydown', handleKeyDown);\n  }, [isOverlayOpen, closeOverlay]);\n\n  // The entry the user just clicked, held until the route commits.\n  //\n  // PENDING IS NOT ACTIVE, and keeping the two apart is the whole point. Active\n  // means \"this is the page you are on\" — it carries the accent bar and\n  // `aria-current=\"page\"`, and it stays derived from the pathname, so it is\n  // never asserted before it is true. Pending only means \"this is the one you\n  // clicked\": a click deserves an answer, but the answer must not be a claim\n  // about where you are. Folding pending into active would light the accent on\n  // a section still loading and announce it as the current page to a screen\n  // reader — a statement that is simply false until the router says otherwise.\n  const [pendingItemId, setPendingItemId] = useState<string | null>(null);\n  const committedActiveId = useMemo(() => config.items.find(item => item.isActive)?.id ?? null, [config.items]);\n\n  // Any committed change clears it — including one that landed somewhere else\n  // entirely (a redirect, a link elsewhere on the page). A navigation that never\n  // commits leaves a faint hold on the row it started from; because pending is\n  // only ever a soft hint, that costs nothing but a stale hover-weight tint,\n  // where a stale ACTIVE state would have been a lie about the current page.\n  // Adjusted while rendering, not from an effect: the commit that lights the\n  // NEW row active is exactly the one that must stop hinting at the old one, and\n  // an effect left both marked for a frame.\n  const [pendingClearedFor, setPendingClearedFor] = useState(committedActiveId);\n  if (pendingClearedFor !== committedActiveId) {\n    setPendingClearedFor(committedActiveId);\n    setPendingItemId(null);\n  }\n\n  const handleItemClick = useCallback(\n    (item: NavigationSidebarItem, event?: MouseEvent) => {\n      event?.stopPropagation();\n\n      if (item.onClick) {\n        item.onClick();\n        if (isTablet) setTabletMinimized(true);\n        return;\n      }\n\n      if (!item.path) return;\n\n      // ⌘/Ctrl/Shift-click and middle-click open the link somewhere else. THIS\n      // window is not going anywhere, so the browser is left to it: no\n      // preventDefault, no optimistic highlight, no closing the tablet overlay.\n      // Entries render as real anchors precisely so those gestures work at all.\n      if (isModifiedClick(event)) return;\n\n      // A plain click stays the host's to perform, through `onNavigate` exactly\n      // as before — the anchor is here for the href (Next prefetches links in the\n      // viewport) and for the browser affordances, not to take over routing.\n      //\n      // `onNavigate` is optional, though, and with no host router there is nothing\n      // to hand the click to: swallowing it would leave a real anchor, with a real\n      // href, that does nothing at all. So the guard is on having somewhere to\n      // send it — otherwise the anchor navigates on its own and we only close the\n      // overlay behind it.\n      if (config.onNavigate) {\n        event?.preventDefault();\n        // Re-clicking the page you are already on starts no navigation, so there\n        // is nothing to mark — and nothing would ever clear it, since the\n        // committed active id is not about to change.\n        if (item.id !== committedActiveId) setPendingItemId(item.id);\n        config.onNavigate(item.path);\n      }\n\n      if (isTablet) setTabletMinimized(true);\n    },\n    [config, isTablet, committedActiveId],\n  );\n\n  const { primaryItems, secondaryItems } = useMemo(\n    () => ({\n      primaryItems: config.items.filter(item => item.section !== 'secondary'),\n      secondaryItems: config.items.filter(item => item.section === 'secondary'),\n    }),\n    [config.items],\n  );\n\n  // Placeholder rows while the host cannot know its entries yet — see\n  // `NavigationSidebarConfig.loading`. Only the ROWS are stood in for: the header,\n  // the collapse toggle, the widths and the tablet overlay all behave exactly as\n  // when loaded, so this is the loaded rail with unknown contents rather than a\n  // separate skeleton sidebar to keep in sync.\n  const loadingRows = useMemo(() => {\n    if (!config.loading) return null;\n    const primary = config.loadingRows?.primary ?? 7;\n    const secondary = config.loadingRows?.secondary ?? 2;\n    return {\n      primary: Array.from({ length: Math.max(0, primary) }, (_, i) => `primary-${i}`),\n      secondary: Array.from({ length: Math.max(0, secondary) }, (_, i) => `secondary-${i}`),\n    };\n  }, [config.loading, config.loadingRows?.primary, config.loadingRows?.secondary]);\n\n  const sidebarWidth = useMemo(() => (minimized ? `${MINIMIZED_WIDTH}px` : `${EXPANDED_WIDTH}px`), [minimized]);\n\n  // Published to CSS from an EFFECT, never from render. Before hydration the\n  // width belongs to whatever seeded `NAVIGATION_SIDEBAR_WIDTH_VAR` in `<head>`\n  // — put it in the markup instead and it becomes part of what React hydrates,\n  // which is precisely what cannot agree across the server boundary. Afterwards\n  // this is what a toggle moves.\n  useIsomorphicLayoutEffect(() => {\n    document.documentElement.style.setProperty(NAVIGATION_SIDEBAR_WIDTH_VAR, sidebarWidth);\n  }, [sidebarWidth]);\n\n  // There used to be an `isHydrated` gate here — `isMdUp !== undefined && ...`\n  // — meant to hold the sidebar's contents back until the media queries\n  // resolved. It could never be false: the `?? false` above had already\n  // swallowed the `undefined` it tested for. Removed rather than repaired,\n  // because repairing it would be the regression: the contents are server-\n  // rendered today, and gating them behind a client-only media query would\n  // trade a first paint of the real navigation for an empty rail.\n  useIsomorphicLayoutEffect(() => {\n    if (transitionsEnabled) return undefined;\n    const id = requestAnimationFrame(() => {\n      setTransitionsEnabled(true);\n    });\n    return () => cancelAnimationFrame(id);\n  }, [transitionsEnabled]);\n\n  return (\n    <>\n      {/* Backdrop scrim — only visible on tablet while the overlay is open */}\n      <div\n        className={cn(\n          'fixed inset-0 z-[40] bg-ods-overlay',\n          'hidden md:block lg:hidden',\n          'transition-opacity duration-300',\n          isOverlayOpen ? 'opacity-100' : 'pointer-events-none opacity-0',\n        )}\n        onClick={closeOverlay}\n        aria-hidden=\"true\"\n      />\n\n      {/* Flex-flow placeholder — reserves the collapsed 56px slot on tablet so\n          the main content keeps its position while the sidebar floats above it.\n          Rendered unconditionally and scoped by classes for the same reason the\n          geometry is: gated on `isTablet`, it was missing from the server HTML\n          and from the first client paint — exactly when the sidebar it\n          compensates for has already gone `absolute`. */}\n      <div className=\"hidden h-full w-14 flex-shrink-0 md:block lg:hidden\" aria-hidden=\"true\" />\n\n      <aside\n        className={cn(\n          SIDEBAR_CONTAINER,\n          'hidden flex-shrink-0 flex-col md:flex',\n          'border-r border-ods-border bg-ods-card',\n          SIDEBAR_GEOMETRY_CLASSES,\n          // The one width a viewport cannot imply: an overlay the user opened on\n          // tablet. Only ever true after a click, so the first paint always gets\n          // the rail.\n          isOverlayOpen ? SIDEBAR_TABLET_WIDTH.overlay : SIDEBAR_TABLET_WIDTH.rail,\n          transitionsEnabled && 'transition-[width] duration-300',\n          config.className,\n        )}\n        aria-label=\"Main navigation sidebar\"\n      >\n        <NavigationSidebarHeader minimized={minimized} />\n\n        <div className=\"flex flex-1 flex-col justify-between overflow-y-auto py-4\">\n          {/* The top slot and the primary nav are ONE `justify-between` child.\n              As siblings they would be three, and the spare space would open\n              between the slot and the nav instead of between the two navs. */}\n          <div className=\"flex flex-col\">\n            {config.topSlot?.({ minimized })}\n\n            {/* `aria-busy` on the nav, not the rows: the region is what is loading,\n                and each placeholder row is already `aria-hidden`. */}\n            <nav className=\"flex flex-col\" aria-label=\"Primary navigation\" aria-busy={!!loadingRows}>\n              {loadingRows\n                ? loadingRows.primary.map(key => <NavigationSidebarItemSkeleton key={key} showLabel={showLabel} />)\n                : primaryItems.map(item => (\n                    <NavigationSidebarItemButton\n                      key={item.id}\n                      item={item}\n                      isActive={item.id === committedActiveId}\n                      isPending={item.id === pendingItemId}\n                      showLabel={showLabel}\n                      disabled={disabled}\n                      onClick={handleItemClick}\n                    />\n                  ))}\n            </nav>\n          </div>\n\n          {(loadingRows ? loadingRows.secondary.length > 0 : secondaryItems.length > 0) && (\n            <nav className=\"flex flex-col\" aria-label=\"Secondary navigation\" aria-busy={!!loadingRows}>\n              {loadingRows\n                ? loadingRows.secondary.map(key => <NavigationSidebarItemSkeleton key={key} showLabel={showLabel} />)\n                : secondaryItems.map(item => (\n                    <NavigationSidebarItemButton\n                      key={item.id}\n                      item={item}\n                      isActive={item.id === committedActiveId}\n                      isPending={item.id === pendingItemId}\n                      showLabel={showLabel}\n                      disabled={disabled}\n                      onClick={handleItemClick}\n                    />\n                  ))}\n            </nav>\n          )}\n        </div>\n\n        <NavigationSidebarToggle minimized={minimized} showLabel={showLabel} onToggle={handleToggle} />\n      </aside>\n    </>\n  );\n}\n","'use client';\n\nimport { cn } from '../../utils';\nimport { OpenFrameLogo, OpenFrameText } from '../icons';\n\nexport interface NavigationSidebarHeaderProps {\n  minimized: boolean;\n}\n\nexport function NavigationSidebarHeader({ minimized }: NavigationSidebarHeaderProps) {\n  return (\n    <div className=\"flex h-14 items-center justify-start border-b border-ods-border p-[var(--spacing-system-m)]\">\n      <div className=\"flex-shrink-0\">\n        <OpenFrameLogo\n          className=\"h-6 w-6\"\n          upperPathColor=\"var(--color-text-primary)\"\n          lowerPathColor=\"var(--color-accent-primary)\"\n        />\n      </div>\n\n      <div\n        className={cn(\n          'min-w-0 flex-1 overflow-hidden transition-[opacity,margin-left] duration-300',\n          minimized ? 'ml-0 opacity-0' : 'ml-[var(--spacing-system-xs)] opacity-100',\n        )}\n        aria-hidden={minimized}\n      >\n        <OpenFrameText textColor=\"var(--color-text-primary)\" />\n      </div>\n    </div>\n  );\n}\n","'use client';\n\nimport { cloneElement, memo, type ReactElement, type ReactNode, type MouseEvent } from 'react';\nimport Link from '../../embed-shims/next-link';\nimport type { NavigationSidebarItem } from '../../types/navigation';\nimport { cn } from '../../utils';\nimport { UnreadDot } from './unread-dot';\n\ninterface IconProps {\n  color?: string;\n  className?: string;\n}\n\nexport interface NavigationSidebarItemButtonProps {\n  item: NavigationSidebarItem;\n  /** \"You are on this page\" — pathname-derived, and the only thing that carries `aria-current`. */\n  isActive: boolean;\n  /**\n   * \"You clicked this one and it has not arrived yet\" — deliberately a separate,\n   * weaker state. Never promoted to active: the accent belongs to the page you\n   * are actually on.\n   */\n  isPending: boolean;\n  showLabel: boolean;\n  disabled: boolean;\n  onClick: (item: NavigationSidebarItem, event?: MouseEvent) => void;\n}\n\n/**\n * One sidebar entry.\n *\n * Rendered as a real anchor whenever it has a `path` — which is what buys the\n * two things a `<button>` + `router.push` cannot: Next prefetches links that\n * are in the viewport (so the section is usually already loaded by the time it\n * is clicked, instead of every first visit paying a cold segment fetch), and\n * the browser's own affordances work — middle-click, ⌘/Ctrl-click, \"open in new\n * tab\", copy link, and a status-bar URL on hover.\n *\n * Falls back to a `<button>` for entries that only carry an `onClick` (no\n * destination to link to) and for the disabled state, which an anchor cannot\n * express.\n */\nexport const NavigationSidebarItemButton = memo(function NavigationSidebarItemButtonImpl({\n  item,\n  isActive,\n  isPending,\n  showLabel,\n  disabled,\n  onClick,\n}: NavigationSidebarItemButtonProps) {\n  const unreadCount = item.unreadCount ?? 0;\n  const hasUnread = unreadCount > 0;\n\n  const className = cn(\n    // `isolate` scopes the hover layer's negative z-index to this row, so it\n    // paints over the row's own background and under its content instead of\n    // sliding behind the sidebar.\n    'relative isolate flex w-full items-center justify-start',\n    'h-14 p-[var(--spacing-system-m)]',\n    // The hover/pending wash is its own LAYER, and that separation is the whole\n    // point. Hover and active both want the row's background-color, so a\n    // `transition-colors` that smooths hover necessarily smooths the accent too\n    // — and it runs on the OUTGOING row as well as the incoming one. Measured on\n    // a real navigation: for 150ms two rows carried the accent background AND\n    // the accent bar, mid-fade, with nothing to say which one was the page you\n    // were actually on. Shortening it (300ms -> 150ms) only shortened the lie.\n    //\n    // Split apart, each gets the timing its meaning calls for: the wash fades,\n    // because it is decoration reacting to a cursor; the accent switches in a\n    // single frame, because it is a statement of fact — exactly one row is the\n    // current page, at every instant, including the instant the route commits.\n    \"after:pointer-events-none after:absolute after:inset-0 after:-z-10 after:bg-ods-bg-hover after:opacity-0 after:transition-opacity after:duration-150 after:content-['']\",\n    \"before:absolute before:bottom-0 before:left-0 before:top-0 before:w-1 before:content-['']\",\n    // Pending borrows the HOVER weight and nothing else — no accent bar, no\n    // accent text, no `aria-current`. It says \"this row is working\", not \"this\n    // is where you are\", and it reads as the row staying pressed under the\n    // cursor, which is exactly what happened.\n    !isActive && !disabled && 'text-ods-text-primary hover:after:opacity-100 [&_svg]:fill-ods-text-secondary',\n    !isActive && !disabled && isPending && 'after:opacity-100',\n    !isActive && disabled && 'text-ods-text-secondary [&_svg]:fill-ods-text-secondary',\n    isActive &&\n      !disabled && [\n        'bg-[var(--ods-open-yellow-light)] text-ods-accent',\n        '[&_svg]:fill-ods-accent',\n        'before:bg-ods-accent',\n      ],\n    isActive && disabled && 'text-ods-text-secondary [&_svg]:fill-ods-text-secondary',\n    disabled && 'cursor-not-allowed opacity-50',\n  );\n\n  const content: ReactNode = (\n    <>\n      <div className=\"relative flex flex-shrink-0 items-center justify-center\">\n        {cloneElement(item.icon as ReactElement<IconProps>, {\n          color: isActive && !disabled ? 'text-ods-accent' : 'text-ods-text-secondary',\n        })}\n        {hasUnread && !showLabel && <UnreadDot size=\"fixed\" />}\n      </div>\n\n      <span\n        className={cn(\n          'flex-1 truncate text-left transition-[opacity,margin-left] duration-300 text-h4',\n          showLabel ? 'ml-[var(--spacing-system-xs)] opacity-100' : 'ml-0 opacity-0',\n        )}\n        aria-hidden={!showLabel}\n      >\n        {item.label}\n      </span>\n\n      {hasUnread && showLabel && (\n        <span className=\"flex size-6 flex-shrink-0 items-center justify-center rounded-md bg-ods-accent p-2\">\n          <span className=\"text-ods-text-on-accent text-h5\">{unreadCount > 99 ? '99+' : unreadCount}</span>\n        </span>\n      )}\n    </>\n  );\n\n  const shared = {\n    className,\n    title: !showLabel ? item.label : undefined,\n    'aria-label': item.label,\n    'aria-current': isActive ? ('page' as const) : undefined,\n  };\n\n  // An entry with a custom onClick has no destination of its own, and a\n  // disabled one must not be followable — both stay buttons.\n  if (disabled || !item.path || item.onClick) {\n    return (\n      <button type=\"button\" onClick={event => onClick(item, event)} disabled={disabled} {...shared}>\n        {content}\n      </button>\n    );\n  }\n\n  return (\n    <Link href={item.path} onClick={event => onClick(item, event)} {...shared}>\n      {content}\n    </Link>\n  );\n});\n\n/**\n * One placeholder entry, for `NavigationSidebarConfig.loading`.\n *\n * Deliberately in this file, beside `NavigationSidebarItemButton`: the two must\n * agree on `h-14`, the `p-[var(--spacing-system-m)]` inset, the 24px icon box and\n * the label column, or the nav resizes on the handoff. Same reason\n * `header-skeleton.tsx` sits beside `header.tsx`.\n *\n * `showLabel` is the sidebar's own minimized state, so the caller passes what it\n * already passes the real row — no separate width handling.\n */\nexport function NavigationSidebarItemSkeleton({ showLabel }: { showLabel: boolean }) {\n  return (\n    <div className=\"flex h-14 w-full items-center justify-start p-[var(--spacing-system-m)]\" aria-hidden=\"true\">\n      <div className=\"relative flex flex-shrink-0 items-center justify-center\">\n        <div className=\"h-6 w-6 animate-pulse rounded bg-ods-border\" />\n      </div>\n      {showLabel && <div className=\"ml-[var(--spacing-system-xs)] h-4 flex-1 animate-pulse rounded bg-ods-border\" />}\n    </div>\n  );\n}\n","'use client';\n\nimport { cn } from '../../utils';\nimport { Chevrons03LeftIcon } from '../icons-v2-generated';\n\nexport interface NavigationSidebarToggleProps {\n  minimized: boolean;\n  showLabel: boolean;\n  onToggle: () => void;\n}\n\nexport function NavigationSidebarToggle({ minimized, showLabel, onToggle }: NavigationSidebarToggleProps) {\n  return (\n    <div className=\"border-t border-ods-border\">\n      <button\n        onClick={onToggle}\n        className={cn(\n          'relative flex w-full items-center justify-start',\n          'h-14 p-[var(--spacing-system-m)]',\n          'transition-colors duration-300',\n          '[&_svg]:transition-colors [&_svg]:duration-300',\n          'text-ods-text-primary hover:bg-ods-bg-hover',\n        )}\n        title={minimized ? 'Hide Menu' : undefined}\n        aria-label={'Hide Menu'}\n      >\n        <div className=\"flex flex-shrink-0 items-center justify-center\">\n          {/* Direction follows the rail's measured width (the `of-nav-sidebar`\n              container declared in `navigation-sidebar.tsx`), not `minimized`:\n              before hydration that prop can only carry the server's answer, and\n              a chevron pointing the wrong way in a 56px rail is the one piece of\n              sidebar state a user actually notices arriving late.\n\n              Collapsed is the default because container queries are min-width —\n              the rail is narrower than every threshold, so it needs no query of\n              its own. */}\n          <Chevrons03LeftIcon className=\"h-6 w-6 rotate-180 text-ods-text-secondary transition-transform duration-300 @[140px]/of-nav-sidebar:rotate-0\" />\n        </div>\n\n        <span\n          className={cn(\n            'flex-1 truncate text-left transition-[opacity,margin-left] duration-300 text-h4',\n            showLabel ? 'ml-[var(--spacing-system-xs)] opacity-100' : 'ml-0 opacity-0',\n          )}\n          aria-hidden={!showLabel}\n        >\n          Hide Menu\n        </span>\n      </button>\n    </div>\n  );\n}\n","'use client';\n\nimport React from 'react';\nimport { useMdUp } from '../../hooks/ui/use-media-query';\nimport { cn } from '../../utils/cn';\nimport { useOptionalNotifications } from '../features/notifications/notifications-context';\nimport { TimeTrackerHeaderButton } from '../features/time-tracker';\nimport { LogOutIcon, OpenFrameLogo, OpenFrameText, UserIcon } from '../icons';\nimport { Menu01Icon, SearchIcon, XmarkIcon } from '../icons-v2-generated';\nimport { BellIcon } from '../icons-v2-generated/interface/bell-icon';\nimport { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, SquareAvatar } from '../ui';\nimport { HeaderButton } from './header-button';\nimport { HeaderGlobalSearch } from './header-global-search';\nimport { HeaderMingoButton } from './header-mingo-button';\nimport { HeaderOrganizationFilter } from './header-organization-filter';\nimport { TicketAlertsButton } from './ticket-alerts-button';\nimport { TopNavigation } from './top-navigation';\n\nexport interface AppHeaderProps {\n  showSearch?: boolean;\n  onSearch?: (query: string) => void;\n  showOrganizations?: boolean;\n  organizations?: { id: string; name: string }[];\n  selectedOrgId?: string;\n  onOrgChange?: (id: string) => void;\n  showNotifications?: boolean;\n  unreadCount?: number;\n  /** Render the support-ticket alerts cell (`TicketAlertsButton`).\n   *  Attention-only: even when true it renders nothing unless a\n   *  `<TicketLiveProvider>` is mounted, the viewer is signed in, AND\n   *  there are unread support replies. */\n  showTicketAlerts?: boolean;\n  /** BASE path of the tickets surface (any nesting prefix allowed —\n   *  '/help-center/tickets', '/support/portal/tickets'). The cell builds\n   *  the SSOT deep link `<href>?ticket=<id>` for the newest-unread\n   *  ticket before navigating. */\n  ticketAlertsHref?: string;\n  /** Host navigation (router push) — receives the FULL computed href.\n   *  Defaults to `window.location.assign`. */\n  onTicketAlerts?: (href: string) => void;\n  /** Render the time-tracker button + popup. Requires wrapping the app in `<TimeTrackerProvider>`. */\n  showTimeTracker?: boolean;\n  /** Render the \"Mingo AI\" launcher button (drawer-style trigger for an\n   *  in-layout `AppLayoutDrawer` hosting the chat panel). Defaults to off. */\n  showMingoAI?: boolean;\n  /** Click handler for the Mingo AI button — typically toggles the drawer. */\n  onMingoAI?: () => void;\n  /** Whether the Mingo drawer is currently open (visually pressed state). */\n  isMingoAIActive?: boolean;\n  // User block\n  showUser?: boolean;\n  userName?: string;\n  userEmail?: string;\n  userAvatarUrl?: string | null;\n  onProfile?: () => void;\n  onLogout?: () => void;\n  className?: string;\n  /** Whether the mobile menu is open */\n  isMobileMenuOpen: boolean;\n  /** Callback to toggle mobile menu */\n  onToggleMobileMenu?: () => void;\n  /**\n   * When true, all header controls are disabled and visually dimmed\n   * EXCEPT the mobile burger menu toggle, which remains interactive.\n   */\n  disabled?: boolean;\n  /**\n   * Draw placeholder cells instead of the live controls.\n   *\n   * Solves a problem this header cannot solve on its own: its mobile/desktop split\n   * is decided by `useMdUp()`, which answers `undefined` until an effect has run —\n   * and `?? false` turns that into \"mobile\". The first render is therefore always\n   * the phone header (burger + wordmark, no side actions), which on a desktop load\n   * is a visible flash before the real layout appears. It is also what a host has\n   * to show while the user and the action flags are still loading.\n   *\n   * The placeholder decides mobile vs desktop in CSS instead, so it is correct at\n   * every width on the very first paint, server-rendered included.\n   */\n  loading?: boolean;\n  /**\n   * Shape of the trailing action cells `loading` reserves.\n   *\n   * Needed because the `show*` props are usually the very thing the host is still\n   * waiting on — they are typically driven by feature flags or permissions, so during\n   * `loading` they all read `false` and the placeholder collapses to whatever is\n   * hardcoded on (often just the avatar), which looks nothing like the loaded header.\n   *\n   * Pass the cells the header settles on, in order — see `HeaderLoadingCell` for the\n   * footprints. A bare number is accepted as shorthand for all-`'icon'`.\n   *\n   * Unlike the sidebar's rows, a cell that turns out not to exist is cheap here: the\n   * cells are a right-aligned cluster in otherwise empty space, so one disappearing\n   * shifts nothing else on the page.\n   *\n   * Defaults to the shape implied by the `show*` props, which is right for a host whose\n   * header composition is known up front.\n   */\n  loadingActionCells?: number | ReadonlyArray<HeaderLoadingCell>;\n}\n\n/**\n * A trailing header cell's footprint while loading — see `loadingActionCells`.\n *\n * The breakpoint-scoped members exist because several live cells are themselves\n * responsive, and a placeholder that ignores that is wider than the header it hands\n * off to on exactly the viewports where the cluster is tightest:\n *\n * - `'icon'` — a fixed 48/56px cell at every width (time tracker, notifications).\n * - `'icon-md'` — the same cell, from md only (the avatar: `isMdUp && showUser`).\n * - `'icon-lg'` — the same cell, from lg only (the organization filter, which the\n *   live header renders `hidden lg:flex`).\n * - `'wide'` — the Mingo launcher: a 48px square below md, where the live button is\n *   `iconOnly`, and the fixed 140px labelled cell from md.\n */\nexport type HeaderLoadingCell = 'icon' | 'icon-md' | 'icon-lg' | 'wide';\n\nexport const AppHeader = React.memo(function AppHeaderImpl({\n  showSearch,\n  onSearch,\n  showOrganizations,\n  organizations = [],\n  selectedOrgId,\n  onOrgChange,\n  showNotifications,\n  unreadCount = 0,\n  showTicketAlerts = false,\n  ticketAlertsHref,\n  onTicketAlerts,\n  showTimeTracker = false,\n  showMingoAI = false,\n  onMingoAI,\n  isMingoAIActive = false,\n  showUser,\n  userName,\n  userEmail,\n  userAvatarUrl,\n  onProfile,\n  onLogout,\n  className,\n  isMobileMenuOpen,\n  onToggleMobileMenu,\n  disabled = false,\n  loading = false,\n  loadingActionCells,\n}: AppHeaderProps) {\n  const isMdUp = useMdUp() ?? false;\n\n  // After the only hook above, so the hook order is identical in both branches.\n  if (loading) {\n    return (\n      <AppHeaderSkeleton\n        showSearch={showSearch}\n        actionCells={\n          loadingActionCells ??\n          // Fallback: what the `show*` props imply, in the order the live cells are\n          // mounted below. Only correct for a host that knows its header composition\n          // before it starts loading. The cell types carry each control's own\n          // breakpoint — the organization filter is `hidden lg:flex`, the avatar is\n          // gated on `isMdUp`, and Mingo is `iconOnly` below md.\n          ([\n            showOrganizations && 'icon-lg',\n            showTimeTracker && 'icon',\n            showNotifications && 'icon',\n            showTicketAlerts && 'icon',\n            showUser && 'icon-md',\n            showMingoAI && 'wide',\n          ].filter(Boolean) as HeaderLoadingCell[])\n        }\n        className={className}\n      />\n    );\n  }\n\n  const dimmedClass = disabled ? 'pointer-events-none opacity-50' : '';\n  // Cells carry their own dividers in the unified TopNavigation model\n  // (the shell no longer applies divide-x).\n  const cellDivider = 'border-l border-ods-border';\n\n  return (\n    <TopNavigation\n      className={cn('sticky top-0 z-40', className)}\n      centerBreakpoint=\"md\"\n      leading={\n        // Mobile: Burger Menu Button\n        !isMdUp && (\n          <HeaderButton\n            onClick={onToggleMobileMenu}\n            isActive={isMobileMenuOpen}\n            icon={isMobileMenuOpen ? <XmarkIcon className=\"h-6 w-6\" /> : <Menu01Icon className=\"h-6 w-6\" />}\n            aria-label={isMobileMenuOpen ? 'Close menu' : 'Open menu'}\n            aria-expanded={isMobileMenuOpen}\n            className=\"border-r border-ods-border\"\n          />\n        )\n      }\n      logo={\n        !isMdUp && (\n          <>\n            <OpenFrameLogo\n              className=\"h-6 w-6 shrink-0\"\n              upperPathColor=\"var(--color-text-primary)\"\n              lowerPathColor=\"var(--color-accent-primary)\"\n            />\n            <OpenFrameText textColor=\"var(--color-text-primary)\" className=\"h-4\" />\n          </>\n        )\n      }\n      logoClassName=\"gap-2\"\n      center={showSearch ? <HeaderGlobalSearch onSubmit={onSearch} className={cn('w-full', dimmedClass)} /> : undefined}\n      sideActions={\n        <>\n          {/* Mobile: Search button */}\n          {showSearch && (\n            <HeaderButton\n              icon={<SearchIcon className=\"h-6 w-6\" />}\n              aria-label=\"Search\"\n              className={cn('md:hidden', cellDivider, dimmedClass)}\n              disabled={disabled}\n            />\n          )}\n\n          {/* Desktop: Organizations filter (carries its own border-l) */}\n          {showOrganizations && (\n            <HeaderOrganizationFilter\n              organizations={organizations}\n              selectedOrgId={selectedOrgId}\n              onOrgChange={onOrgChange}\n              className={cn('hidden lg:flex', dimmedClass)}\n            />\n          )}\n\n          {/* Time tracker button */}\n          {showTimeTracker && <TimeTrackerHeaderButton disabled={disabled} className={cn(cellDivider, dimmedClass)} />}\n\n          {/* Notifications button */}\n          {showNotifications && (\n            <NotificationsHeaderButton\n              fallbackUnreadCount={unreadCount}\n              disabled={disabled}\n              dimmedClass={cn(cellDivider, dimmedClass)}\n            />\n          )}\n\n          {/* Support-ticket alerts (Help Center) — attention-only: renders\n              nothing unless there are unread replies (and a\n              <TicketLiveProvider> is mounted). */}\n          {showTicketAlerts && (\n            <TicketAlertsButton\n              href={ticketAlertsHref ?? '/tickets'}\n              onNavigate={onTicketAlerts}\n              disabled={disabled}\n              className={cn(cellDivider, dimmedClass)}\n            />\n          )}\n\n          {isMdUp && showUser && (\n            <DropdownMenu>\n              <DropdownMenuTrigger asChild disabled={disabled}>\n                <HeaderButton\n                  icon={\n                    <SquareAvatar\n                      src={userAvatarUrl || undefined}\n                      alt={userName || 'User'}\n                      size=\"sm\"\n                      variant=\"round\"\n                      className=\"h-8 w-8 shrink-0 md:h-10 md:w-10\"\n                    />\n                  }\n                  aria-label=\"User\"\n                  disabled={disabled}\n                  className={cn(\n                    'outline-none ring-0 focus:outline-none focus:ring-0 focus-visible:outline-none focus-visible:ring-0',\n                    cellDivider,\n                    dimmedClass,\n                  )}\n                />\n              </DropdownMenuTrigger>\n              <DropdownMenuContent\n                align=\"end\"\n                // z-[104] — above the in-layout AppLayoutDrawer panel (z-[103]) so the\n                // user menu stays clickable while the Mingo AI drawer is open\n                className=\"z-[104] w-[280px] overflow-hidden rounded-[6px] border-ods-border bg-ods-bg p-0\"\n              >\n                {/* User info header section */}\n                <div className=\"flex items-center gap-2 border-b border-ods-border bg-ods-card p-3\">\n                  <SquareAvatar\n                    src={userAvatarUrl || undefined}\n                    alt={userName || 'User'}\n                    size=\"sm\"\n                    variant=\"round\"\n                    className=\"shrink-0\"\n                  />\n                  <div className=\"min-w-0 flex-1\">\n                    {userName && <div className=\"truncate text-ods-text-primary text-h4\">{userName}</div>}\n                    {userEmail && <div className=\"truncate text-ods-text-secondary text-h6\">{userEmail}</div>}\n                  </div>\n                </div>\n\n                {/* Menu items */}\n                <DropdownMenuItem\n                  onClick={onProfile}\n                  className=\"cursor-pointer rounded-none border-b border-ods-border bg-ods-card px-3 py-3 hover:bg-ods-card/80 focus:bg-ods-card/80\"\n                >\n                  <div className=\"flex items-center gap-2\">\n                    <UserIcon className=\"h-6 w-6 shrink-0 text-ods-text-primary\" />\n                    <span className=\"text-ods-text-primary text-h4\">Profile Settings</span>\n                  </div>\n                </DropdownMenuItem>\n\n                <DropdownMenuItem\n                  onClick={onLogout}\n                  className=\"cursor-pointer rounded-none bg-ods-card px-3 py-3 hover:bg-ods-card/80 focus:bg-ods-card/80\"\n                >\n                  <div className=\"flex items-center gap-2\">\n                    <LogOutIcon className=\"shrink-0 text-ods-error\" size={24} />\n                    <span className=\"text-ods-text-primary text-h4\">Log Out</span>\n                  </div>\n                </DropdownMenuItem>\n              </DropdownMenuContent>\n            </DropdownMenu>\n          )}\n\n          {/* Mingo AI launcher — anchored at the very end of the header. On mobile\n          it collapses to an icon-only affordance (no \"Mingo AI\" wordmark). */}\n          {showMingoAI && (\n            <HeaderMingoButton\n              onClick={onMingoAI}\n              isActive={isMingoAIActive}\n              iconOnly={!isMdUp}\n              disabled={disabled || !onMingoAI}\n              className={cn(cellDivider, dimmedClass)}\n            />\n          )}\n        </>\n      }\n    />\n  );\n});\n\ninterface NotificationsHeaderButtonProps {\n  fallbackUnreadCount: number;\n  disabled: boolean;\n  dimmedClass: string;\n}\n\nfunction NotificationsHeaderButton({ fallbackUnreadCount, disabled, dimmedClass }: NotificationsHeaderButtonProps) {\n  const ctx = useOptionalNotifications();\n  const hasUnread = (ctx?.unreadCount ?? fallbackUnreadCount) > 0;\n  const isActive = ctx?.isOpen ?? false;\n  const onClick = ctx?.toggle;\n\n  return (\n    <HeaderButton\n      icon={isActive ? <XmarkIcon className=\"h-6 w-6\" /> : <BellIcon className=\"h-6 w-6\" />}\n      // Shared dot primitive on the cell — same markup every indicator\n      // cell renders (see HeaderButton.showUnreadDot / <UnreadDot>).\n      showUnreadDot={!isActive && hasUnread}\n      aria-label={isActive ? 'Close notifications' : 'Notifications'}\n      onClick={onClick}\n      isActive={isActive}\n      disabled={disabled || !onClick}\n      className={dimmedClass}\n    />\n  );\n}\n\nexport default AppHeader;\n\n// Literal class strings — Tailwind's scanner needs to see them spelled out.\nconst CELL_VISIBILITY: Record<Exclude<HeaderLoadingCell, 'wide'>, string | undefined> = {\n  icon: undefined,\n  'icon-md': 'hidden md:flex',\n  'icon-lg': 'hidden lg:flex',\n};\n\n/** One trailing action cell placeholder — mirrors the live cells' 48/56px box. */\nfunction HeaderCellSkeleton({ className }: { className?: string }) {\n  return (\n    <div className={cn('flex h-full w-14 shrink-0 items-center justify-center border-l border-ods-border', className)}>\n      <div className=\"h-6 w-6 animate-pulse rounded bg-ods-border\" />\n    </div>\n  );\n}\n\n/**\n * Wide cell placeholder — mirrors `HeaderMingoButton` at both widths, which are\n * two different shapes: the live button gets `iconOnly={!isMdUp}`, so below md it\n * drops its wordmark and collapses to the same 48px square as any other cell, and\n * from md it is the fixed 140px labelled cell. Reserving 140px-worth of icon,\n * label, gap and padding on a phone would make the placeholder cluster ~70px\n * wider than the header it hands off to.\n */\nfunction HeaderWideCellSkeleton() {\n  return (\n    <div className=\"flex h-full w-14 shrink-0 items-center justify-center gap-2 border-l border-ods-border md:w-[140px] md:px-4\">\n      <div className=\"h-6 w-6 shrink-0 animate-pulse rounded bg-ods-border\" />\n      <div className=\"hidden h-5 animate-pulse rounded bg-ods-border md:block md:w-[72px]\" />\n    </div>\n  );\n}\n\n/**\n * `AppHeader`'s loading state — see `AppHeaderProps.loading`.\n *\n * Keep in sync with `AppHeader` above; a diverging placeholder makes the handoff\n * jump. Every breakpoint decision here is a Tailwind variant on purpose: this is the\n * one thing the live header cannot do (its split runs through `useMdUp()`), and it\n * is the whole reason this branch exists.\n */\nfunction AppHeaderSkeleton({\n  showSearch,\n  actionCells,\n  className,\n}: {\n  showSearch?: boolean;\n  actionCells: number | ReadonlyArray<HeaderLoadingCell>;\n  className?: string;\n}) {\n  const cells: ReadonlyArray<HeaderLoadingCell> =\n    typeof actionCells === 'number'\n      ? Array.from({ length: Math.max(0, actionCells) }, () => 'icon' as const)\n      : actionCells;\n  return (\n    <TopNavigation\n      className={cn('sticky top-0 z-40', className)}\n      centerBreakpoint=\"md\"\n      aria-busy=\"true\"\n      leading={\n        // Burger cell: mobile only, in CSS.\n        <div className=\"flex h-full w-14 shrink-0 items-center justify-center border-r border-ods-border md:hidden\">\n          <div className=\"h-6 w-6 animate-pulse rounded bg-ods-border\" />\n        </div>\n      }\n      logo={\n        <>\n          <div className=\"h-6 w-6 shrink-0 animate-pulse rounded bg-ods-border\" />\n          <div className=\"h-4 w-24 animate-pulse rounded bg-ods-border\" />\n        </>\n      }\n      // `md:hidden` belongs on the WRAPPER, not the placeholder inside it:\n      // `TopNavigation` builds its padded logo zone whenever `logo` is truthy, and\n      // that padding runs to 24px at md and 80px at lg. The live header passes\n      // `false` here from md up, so the zone does not exist at all — leaving the\n      // wrapper visible would inset the search field by 80px on desktop and shift\n      // it back on handoff, which is the exact jump this branch removes.\n      logoClassName=\"gap-2 md:hidden\"\n      // `TopNavigation` already hides the center zone below `centerBreakpoint`.\n      center={showSearch ? <div className=\"h-10 w-full animate-pulse rounded-md bg-ods-border\" /> : undefined}\n      sideActions={\n        <>\n          {/* Mobile-only search trigger; from md the search lives in the center zone. */}\n          {showSearch && <HeaderCellSkeleton className=\"md:hidden\" />}\n          {cells.map((cell, i) =>\n            cell === 'wide' ? (\n              <HeaderWideCellSkeleton key={i} />\n            ) : (\n              <HeaderCellSkeleton key={i} className={CELL_VISIBILITY[cell]} />\n            ),\n          )}\n        </>\n      }\n    />\n  );\n}\n","'use client';\n\nimport React from 'react';\nimport { cn } from '../../utils/cn';\nimport { HUBSPOT_DO_NOT_COLLECT_FORM_PROPS } from '../../utils/hubspot-collected-forms';\nimport { SearchIcon } from '../icons-v2-generated';\n\nexport interface HeaderGlobalSearchProps {\n  /** Current search value */\n  value?: string;\n  /** Callback when search value changes */\n  onChange?: (value: string) => void;\n  /** Callback when search is submitted */\n  onSubmit?: (value: string) => void;\n  /** Placeholder text */\n  placeholder?: string;\n  /** Additional class names */\n  className?: string;\n}\n\nexport function HeaderGlobalSearch({\n  value = '',\n  onChange,\n  onSubmit,\n  placeholder = 'Global Search',\n  className,\n}: HeaderGlobalSearchProps) {\n  const [internalValue, setInternalValue] = React.useState(value);\n\n  const currentValue = onChange ? value : internalValue;\n\n  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n    const newValue = e.target.value;\n    if (onChange) {\n      onChange(newValue);\n    } else {\n      setInternalValue(newValue);\n    }\n  };\n\n  const handleSubmit = (e: React.FormEvent) => {\n    e.preventDefault();\n    onSubmit?.(currentValue);\n  };\n\n  const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {\n    if (e.key === 'Enter') {\n      onSubmit?.(currentValue);\n    }\n  };\n\n  return (\n    <form\n      onSubmit={handleSubmit}\n      {...HUBSPOT_DO_NOT_COLLECT_FORM_PROPS}\n      className={cn(\n        'flex h-full flex-1 items-center gap-2 px-3',\n        // Transparent so the cell inherits the bar's background\n        // (TopNavigation `backgroundClassName` can differ per platform).\n        'bg-transparent',\n        className,\n      )}\n    >\n      <SearchIcon className=\"h-6 w-6 shrink-0 text-ods-text-secondary\" />\n      <input\n        type=\"text\"\n        value={currentValue}\n        onChange={handleChange}\n        onKeyDown={handleKeyDown}\n        placeholder={placeholder}\n        className={cn(\n          'min-w-0 flex-1 bg-transparent',\n          'text-h4',\n          'text-ods-text-primary placeholder:text-ods-text-secondary',\n          'border-none outline-none',\n        )}\n      />\n    </form>\n  );\n}\n\nexport default HeaderGlobalSearch;\n","'use client';\n\nimport type React from 'react';\nimport { cn } from '../../utils/cn';\nimport { MingoIcon } from '../icons';\nimport { XmarkIcon } from '../icons-v2-generated';\n\nexport interface HeaderMingoButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n  /** Active/pressed state — set to `true` while the Mingo drawer is open. */\n  isActive?: boolean;\n  /** When true, hides the \"Mingo AI\" label and renders only the icon (so the\n   *  control collapses to a square `HeaderButton`-sized affordance on narrow\n   *  viewports). Defaults to `false`. */\n  iconOnly?: boolean;\n  className?: string;\n}\n\n/**\n * \"Mingo AI\" launcher button for `AppHeader`. Mirrors the `HeaderButton`\n * visual contract (sticky header height, `ods-card` rest / `ods-bg-hover`\n * hover, divider via `AppHeader`'s `divide-x`), but carries both the Mingo\n * logo and the bold \"Mingo AI\" wordmark. The open/active state is exposed via\n * `aria-pressed` (and, in `iconOnly` mode, by swapping the logo for a close\n * \"X\"); there is no distinct active background.\n *\n * Figma: 7532:222103 — `button-full`.\n */\nexport function HeaderMingoButton({ isActive = false, iconOnly = false, className, ...props }: HeaderMingoButtonProps) {\n  return (\n    <button\n      type=\"button\"\n      aria-label={iconOnly && isActive ? 'Close Mingo AI' : 'Mingo AI'}\n      aria-pressed={isActive}\n      className={cn(\n        'flex h-full shrink-0 items-center gap-2 px-4',\n        // Fixed 140px cell from md, so the header's right-hand cluster has a\n        // known width and a loading placeholder can reserve it exactly\n        // (`AppHeaderProps.loadingActionCells` → `'wide'`). Content-width below\n        // md, and in `iconOnly` mode, where the control is a square affordance.\n        !iconOnly && 'md:w-[140px] md:justify-center',\n        'transition-colors duration-200',\n        'focus:outline-none focus-visible:ring-2 focus-visible:ring-ods-accent',\n        // Transparent at rest so the cell inherits the bar's background\n        // (TopNavigation `backgroundClassName` can differ per platform).\n        'bg-transparent text-ods-text-primary hover:bg-ods-bg-hover',\n        className,\n      )}\n      {...props}\n    >\n      {iconOnly && isActive ? (\n        // Mobile, drawer open: the icon-only button doubles as the close\n        // affordance, so swap the Mingo logo for an X. Match the other header\n        // icons (e.g. notifications): secondary color, w-4 h-4 → md:w-6 h-6.\n        <XmarkIcon className=\"h-6 w-6 shrink-0 text-ods-text-secondary\" />\n      ) : (\n        // Outer frame follows the button's text color (currentColor); the eyes\n        // and corner block are ODS cyan.\n        <MingoIcon\n          color=\"currentColor\"\n          eyesColor=\"var(--ods-flamingo-cyan-base)\"\n          cornerColor=\"var(--ods-flamingo-cyan-base)\"\n          className=\"h-6 w-6 shrink-0\"\n        />\n      )}\n      {!iconOnly && <span className=\"whitespace-nowrap font-bold tracking-[-0.36px] text-h3\">Mingo AI</span>}\n    </button>\n  );\n}\n\nexport default HeaderMingoButton;\n","'use client';\n\nimport { cn } from '../../utils/cn';\nimport { Filter02Icon } from '../icons-v2-generated';\nimport { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '../ui';\n\nexport interface HeaderOrganizationFilterOrganization {\n  id: string;\n  name: string;\n  deviceCount?: number;\n}\n\nexport interface HeaderOrganizationFilterProps {\n  /** List of organizations */\n  organizations?: HeaderOrganizationFilterOrganization[];\n  /** Currently selected organization ID */\n  selectedOrgId?: string;\n  /** Callback when organization changes */\n  onOrgChange?: (id: string) => void;\n  /** Total device count (shown when \"All Organizations\" is selected) */\n  totalDeviceCount?: number;\n  /** Additional class names */\n  className?: string;\n}\n\nexport function HeaderOrganizationFilter({\n  organizations = [],\n  selectedOrgId,\n  onOrgChange,\n  totalDeviceCount,\n  className,\n}: HeaderOrganizationFilterProps) {\n  const selectedOrg = organizations.find(o => o.id === selectedOrgId);\n  const displayName = selectedOrg?.name || 'All Organizations';\n  const deviceCount = selectedOrg?.deviceCount ?? totalDeviceCount;\n\n  return (\n    <DropdownMenu>\n      <DropdownMenuTrigger asChild>\n        <button\n          className={cn(\n            'flex h-full items-center gap-4 px-4',\n            // Transparent so the cell inherits the bar's background.\n            'border-l border-ods-border bg-transparent',\n            'w-[240px] shrink-0',\n            'transition-colors hover:bg-ods-bg-hover',\n            className,\n          )}\n        >\n          <Filter02Icon className=\"h-4 w-4 shrink-0 text-ods-text-secondary\" />\n          <div className=\"flex min-w-0 flex-col items-start justify-center\">\n            <span className=\"truncate text-ods-text-primary text-h5\" title={displayName}>\n              {displayName}\n            </span>\n            {deviceCount !== undefined && (\n              <span className=\"truncate text-ods-text-secondary text-h6\">{deviceCount.toLocaleString()} Devices</span>\n            )}\n          </div>\n        </button>\n      </DropdownMenuTrigger>\n      <DropdownMenuContent align=\"end\" className=\"min-w-[240px]\">\n        <DropdownMenuItem onClick={() => onOrgChange?.('')}>All Organizations</DropdownMenuItem>\n        {organizations.map(org => (\n          <DropdownMenuItem key={org.id} onClick={() => onOrgChange?.(org.id)}>\n            {org.name}\n          </DropdownMenuItem>\n        ))}\n      </DropdownMenuContent>\n    </DropdownMenu>\n  );\n}\n\nexport default HeaderOrganizationFilter;\n","'use client';\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport type { ReactNode } from 'react';\nimport type { NavigationSidebarConfig } from '../../types/navigation';\nimport { cn } from '../../utils';\nimport { NotificationDrawer } from '../features/notifications/notification-drawer';\nimport { AppHeader, type AppHeaderProps } from './app-header';\nimport {\n  AppLayoutDrawerContainerContext,\n  type AppLayoutDrawerCoordination,\n  AppLayoutDrawerCoordinationContext,\n  type AppLayoutDrawerHandle,\n} from './app-layout-context';\nimport { MobileBurgerMenu, type MobileBurgerMenuProps } from './mobile-burger-menu';\nimport { NavigationSidebar } from './navigation-sidebar';\n\nexport { useAppLayoutDrawerContainer, useAppLayoutDrawerCoordination } from './app-layout-context';\nexport type { AppLayoutDrawerHandle } from './app-layout-context';\n\nexport interface AppLayoutProps {\n  children: ReactNode;\n  sidebarConfig: NavigationSidebarConfig;\n  headerProps: Omit<AppHeaderProps, 'isMobileMenuOpen' | 'onToggleMobileMenu' | 'disabled'>;\n  mainClassName?: string;\n  className?: string;\n  mobileBurgerMenuProps: Omit<MobileBurgerMenuProps, 'isOpen' | 'onClose' | 'config' | 'disabled'>;\n  /**\n   * When true, disables navigation/header chrome interactions except the mobile\n   * burger menu toggle and the sidebar collapse/expand button. Main content\n   * (`children`) is not affected and stays fully interactive.\n   */\n  disabled?: boolean;\n  /**\n   * Slot for an in-layout drawer (typically an `AppLayoutDrawer` tree). Rendered\n   * inside the layout's drawer-container context so the drawer can portal into\n   * the main-area container. Keeping it separate from `children` clarifies that\n   * the drawer is part of the layout chrome, not page content.\n   */\n  drawer?: ReactNode;\n  /**\n   * Full-width banner rendered ABOVE both the sidebar and the header, spanning\n   * the entire viewport width and pinned to the top of the layout. Optional —\n   * when omitted the layout is unchanged. Used for global, cross-page callouts\n   * (e.g. an onboarding \"complete your setup\" bar). The sidebar + header + main\n   * area occupy the remaining height below it.\n   */\n  topBar?: ReactNode;\n}\n\nexport function AppLayout({\n  children,\n  sidebarConfig,\n  headerProps,\n  mainClassName,\n  className,\n  mobileBurgerMenuProps,\n  disabled = false,\n  drawer,\n  topBar,\n}: AppLayoutProps) {\n  const [mobileMenuOpen, setMobileMenuOpen] = useState(false);\n  const [drawerContainer, setDrawerContainer] = useState<HTMLDivElement | null>(null);\n\n  // Mirrors `mobileMenuOpen` so the toggle callback can stay identity-stable.\n  // Refreshed after every commit rather than in the render body: the only\n  // reader is the burger click, which is always past a commit.\n  const mobileMenuOpenRef = useRef(mobileMenuOpen);\n  useEffect(() => {\n    mobileMenuOpenRef.current = mobileMenuOpen;\n  });\n\n  const drawerHandlesRef = useRef(new Set<AppLayoutDrawerHandle>());\n\n  const handleToggleMobileMenu = useCallback(() => {\n    const opening = !mobileMenuOpenRef.current;\n    // Opening the menu closes any open in-layout drawer — otherwise the menu\n    // would open invisibly underneath it (the drawer renders above the menu).\n    if (opening) {\n      for (const handle of drawerHandlesRef.current) handle.close();\n    }\n    setMobileMenuOpen(opening);\n  }, []);\n\n  const handleCloseMobileMenu = useCallback(() => {\n    setMobileMenuOpen(false);\n  }, []);\n\n  const drawerCoordination = useMemo<AppLayoutDrawerCoordination>(\n    () => ({\n      notifyDrawerDidOpen: self => {\n        setMobileMenuOpen(false);\n        // Only one in-layout panel may be open at a time — each dims the main\n        // area, so stacking them reads as broken.\n        for (const handle of drawerHandlesRef.current) {\n          if (handle !== self) handle.close();\n        }\n      },\n      registerDrawer: handle => {\n        drawerHandlesRef.current.add(handle);\n        return () => drawerHandlesRef.current.delete(handle);\n      },\n    }),\n    [],\n  );\n\n  return (\n    <AppLayoutDrawerContainerContext.Provider value={drawerContainer}>\n      <AppLayoutDrawerCoordinationContext.Provider value={drawerCoordination}>\n        <div className={cn('flex h-screen flex-col bg-ods-bg', className)}>\n          {/* Full-width top banner above sidebar + header (optional) */}\n          {topBar}\n          {/* Sidebar + header + main occupy the remaining height below the banner.\n            `relative` so the tablet sidebar (position:absolute) anchors to this\n            row — below the topBar — instead of the viewport. */}\n          <div className=\"relative flex min-h-0 flex-1\">\n            <NavigationSidebar config={sidebarConfig} disabled={disabled} />\n            {/* Mobile Burger Menu - opens below header */}\n            <MobileBurgerMenu\n              {...mobileBurgerMenuProps}\n              isOpen={mobileMenuOpen}\n              onClose={handleCloseMobileMenu}\n              config={sidebarConfig}\n              disabled={disabled}\n            />\n\n            {/* Main Content Area */}\n            <div className=\"flex flex-1 flex-col overflow-hidden\">\n              <AppHeader\n                {...headerProps}\n                isMobileMenuOpen={mobileMenuOpen}\n                onToggleMobileMenu={handleToggleMobileMenu}\n                disabled={disabled}\n              />\n              <NotificationDrawer />\n\n              {/* Main + AppLayoutDrawer portal target. `relative` so the drawer\n              can absolutely position within just this area (not over\n              header/sidebar); `overflow-hidden` clips the drawer's slide-in\n              animation visually AND contains layout overflow so it doesn't\n              propagate up to <html>. (Scroll-snap-back below handles the\n              browser's programmatic scroll-on-focus side effect.) */}\n              <div ref={setDrawerContainer} className=\"relative flex flex-1 flex-col overflow-hidden\">\n                <main className={cn('flex-1 overflow-y-auto', mainClassName)}>{children}</main>\n                {/* `drawer` slot — rendered here so it sits inside the\n                AppLayoutDrawerContainerContext and can portal into this exact\n                container. Mount location is irrelevant for visual placement\n                (Radix Portal handles that), but keeping it close to the target\n                makes the React tree match the visual nesting. */}\n                {drawer}\n              </div>\n            </div>\n          </div>\n        </div>\n      </AppLayoutDrawerCoordinationContext.Provider>\n    </AppLayoutDrawerContainerContext.Provider>\n  );\n}\n","'use client';\n\nimport { usePreventScroll } from '@react-aria/overlays';\nimport React, { useCallback, useRef } from 'react';\nimport { useFocusTrap } from '../../hooks/ui/use-focus-trap';\nimport type { NavigationSidebarConfig, NavigationSidebarItem } from '../../types/navigation';\nimport { cn } from '../../utils';\nimport { Logout02Icon, PenEditIcon, UserSearchIcon } from '../icons-v2-generated';\nimport { Button, SquareAvatar } from '../ui';\nimport { OVERLAY_BACKDROP_CLASS } from '../ui/drawer';\n\n// Header height constant — the unified top-navigation `small` bar (56px on all screens)\nconst HEADER_HEIGHT = 56;\n\nexport interface MobileBurgerMenuProps {\n  /** Whether the menu is open */\n  isOpen: boolean;\n  /** Callback to close the menu */\n  onClose: () => void;\n  /** Sidebar configuration */\n  config: NavigationSidebarConfig;\n  /** User info for the header card */\n  user?: {\n    userName?: string;\n    userEmail?: string;\n    userAvatarUrl?: string | null;\n    userRole?: string;\n  };\n  /** Callback when search user button is clicked */\n  onSearchUser?: () => void;\n  /** Callback when edit profile button is clicked */\n  onEditProfile?: () => void;\n  /** Callback when logout button is clicked */\n  onLogout?: () => void;\n  /**\n   * When true, all interactive items inside the menu (nav items, action buttons,\n   * logout) are disabled. The burger toggle and backdrop click to close still work.\n   */\n  disabled?: boolean;\n}\n\nexport const MobileBurgerMenu = React.memo(function MobileBurgerMenuImpl({\n  isOpen,\n  onClose,\n  config,\n  user,\n  onSearchUser,\n  onEditProfile,\n  onLogout,\n  disabled = false,\n}: MobileBurgerMenuProps) {\n  const panelRef = useRef<HTMLDivElement>(null);\n\n  // Shared ref-counted, iOS-aware scroll lock (react-aria) while open.\n  usePreventScroll({ isDisabled: !isOpen });\n  // Initial focus, Tab containment, Escape-to-close, guarded focus restore.\n  useFocusTrap(panelRef, isOpen, { onEscape: onClose });\n\n  const handleItemClick = useCallback(\n    (item: NavigationSidebarItem) => {\n      if (item.onClick) {\n        item.onClick();\n      } else if (item.path) {\n        config.onNavigate?.(item.path);\n      }\n      onClose();\n    },\n    [config, onClose],\n  );\n\n  // Separate primary and secondary items\n  const primaryItems = config.items.filter(item => item.section !== 'secondary');\n  const secondaryItems = config.items.filter(item => item.section === 'secondary');\n\n  const renderNavigationItem = (item: NavigationSidebarItem, isGridItem = false) => {\n    const isActive = item.isActive ?? false;\n\n    return (\n      <button\n        key={item.id}\n        onClick={() => handleItemClick(item)}\n        disabled={disabled}\n        className={cn(\n          'relative flex items-center gap-1 p-3',\n          'focus:outline-none focus-visible:outline-none',\n          'transition-colors duration-200',\n          'rounded-md border border-ods-border bg-ods-card',\n          !disabled && 'hover:bg-ods-bg-hover',\n          isGridItem ? 'w-full min-w-0' : 'w-full',\n          // Active state\n          isActive && !disabled && 'border-ods-accent',\n          // Disabled state\n          disabled && 'cursor-not-allowed opacity-50',\n        )}\n        aria-current={isActive ? 'page' : undefined}\n      >\n        {/* Icon */}\n        {item.icon && (\n          <div className=\"flex size-4 flex-shrink-0 items-center justify-center\">\n            {React.cloneElement(item.icon as React.ReactElement<{ size?: number; color?: string }>, {\n              size: 16,\n              color: isActive && !disabled ? 'var(--color-accent-primary)' : 'var(--color-text-secondary)',\n            })}\n          </div>\n        )}\n\n        {/* Label */}\n        <span\n          className={cn(\n            'flex-1 truncate text-left text-h6',\n            isActive && !disabled ? 'text-ods-accent' : 'text-ods-text-primary',\n          )}\n        >\n          {item.label}\n        </span>\n      </button>\n    );\n  };\n\n  // Render grid of navigation items (2 columns). CSS grid keeps a lone last-row\n  // item exactly one column wide (aligned with the column above) instead of\n  // stretching to fill the row.\n  const renderNavigationGrid = (items: NavigationSidebarItem[]) => (\n    <div className=\"grid grid-cols-2 gap-3\">\n      {items.map(item => (\n        <React.Fragment key={item.id}>{renderNavigationItem(item, true)}</React.Fragment>\n      ))}\n    </div>\n  );\n\n  return (\n    <>\n      {/* Dim backdrop (no blur, unified with Drawer) - positioned below header.\n          `absolute` (not `fixed`) so it anchors to the AppLayout row it renders in\n          — the row that sits BELOW the optional `topBar` banner — rather than the\n          viewport. This keeps `top: HEADER_HEIGHT` measured from just under the\n          header even when a top banner pushes the header down. With no banner the\n          row fills the viewport, so this is identical to the previous behavior. */}\n      <div\n        className={cn(\n          'absolute inset-0 z-[100] md:hidden',\n          OVERLAY_BACKDROP_CLASS,\n          'transition-all duration-300 motion-reduce:transition-none',\n          isOpen ? 'opacity-100' : 'pointer-events-none opacity-0',\n        )}\n        style={{ top: HEADER_HEIGHT }}\n        onClick={onClose}\n        aria-hidden=\"true\"\n      />\n\n      {/* Menu Panel - positioned below header with slide-down animation. `absolute`\n          for the same reason as the backdrop above (anchors below the topBar). */}\n      <div\n        ref={panelRef}\n        className={cn(\n          'absolute left-0 right-0 z-[101] md:hidden',\n          'flex flex-col',\n          'border-b border-ods-border bg-ods-bg',\n          'transition-all duration-300 ease-out motion-reduce:transition-none',\n          'overflow-hidden',\n          isOpen ? 'translate-y-0 opacity-100' : 'pointer-events-none -translate-y-4 opacity-0',\n        )}\n        style={{\n          top: HEADER_HEIGHT,\n          maxHeight: `calc(100% - ${HEADER_HEIGHT}px)`,\n        }}\n        role=\"dialog\"\n        aria-modal=\"true\"\n        aria-label=\"Mobile navigation menu\"\n        tabIndex={-1}\n        // Stays mounted while hidden (opacity-0) — inert keeps its items out\n        // of the tab order when closed.\n        inert={!isOpen || undefined}\n      >\n        {/* Scrollable Content */}\n        <div className=\"flex flex-1 flex-col gap-4 overflow-y-auto overflow-x-hidden overscroll-contain p-4\">\n          {/* User Card */}\n          {user && (\n            <div\n              className={cn(\n                'flex items-center gap-3 rounded-md border border-ods-border bg-ods-card p-3',\n                disabled && 'pointer-events-none opacity-50',\n              )}\n              aria-disabled={disabled || undefined}\n            >\n              <SquareAvatar\n                src={user.userAvatarUrl || undefined}\n                alt={user.userName || 'User'}\n                fallback={user.userName}\n                size=\"lg\"\n                variant=\"round\"\n                className=\"h-14 w-14 shrink-0\"\n              />\n\n              {/* User Info */}\n              <div className=\"flex min-w-0 flex-1 flex-col\">\n                <div className=\"flex items-center gap-2\">\n                  <span className=\"truncate text-ods-text-primary text-h6\">{user.userName || 'User'}</span>\n                  {user.userRole && (\n                    <span className=\"shrink-0 rounded-md border border-ods-border bg-ods-card px-2 py-0.5 uppercase text-ods-text-primary text-h6\">\n                      {user.userRole}\n                    </span>\n                  )}\n                </div>\n                {user.userEmail && <span className=\"truncate text-ods-text-secondary text-h6\">{user.userEmail}</span>}\n              </div>\n\n              {/* Action Buttons */}\n              <div className=\"flex shrink-0 items-center gap-2\">\n                {onSearchUser && (\n                  <Button size=\"icon\" onClick={onSearchUser} variant=\"outline\" aria-label=\"Search users\">\n                    <UserSearchIcon className=\"size-4 text-ods-text-primary\" />\n                  </Button>\n                )}\n                {onEditProfile && (\n                  <Button size=\"icon\" onClick={onEditProfile} variant=\"outline\" aria-label=\"Edit profile\">\n                    <PenEditIcon className=\"size-4 text-ods-text-primary\" />\n                  </Button>\n                )}\n              </div>\n            </div>\n          )}\n\n          {/* Host-owned action above the nav — the same slot the sidebar\n              renders, so a host passes `topSlot` once and both surfaces get it.\n              Never minimized: this panel is always the full width of the phone. */}\n          {config.topSlot?.({ minimized: false })}\n\n          {/* Primary Navigation Items - Grid Layout */}\n          <nav aria-label=\"Primary navigation\">{renderNavigationGrid(primaryItems)}</nav>\n\n          {/* Secondary Navigation Items - Full Width */}\n          {secondaryItems.length > 0 && (\n            <nav aria-label=\"Secondary navigation\" className=\"flex flex-col gap-3\">\n              {secondaryItems.map(item => (\n                <React.Fragment key={item.id}>{renderNavigationItem(item, false)}</React.Fragment>\n              ))}\n            </nav>\n          )}\n\n          {/* Logout Button */}\n          {onLogout && (\n            <button\n              onClick={onLogout}\n              disabled={disabled}\n              className={cn(\n                'flex w-full items-center gap-1 rounded-md border border-ods-border bg-ods-card p-3 transition-colors',\n                !disabled && 'hover:bg-ods-bg-hover',\n                disabled && 'cursor-not-allowed opacity-50',\n              )}\n            >\n              <Logout02Icon className=\"size-4 text-ods-error\" />\n              <span className=\"flex-1 text-left text-ods-text-primary text-h6\">Log Out</span>\n            </button>\n          )}\n        </div>\n      </div>\n    </>\n  );\n});\n"]}