// Prevent browser scroll restoration so every page load starts at the top
if ('scrollRestoration' in history) { history.scrollRestoration = 'manual'; }
window.scrollTo(0, 0);

/**
 * TuneVault Navigation Component
 * Single source of truth for nav across all pages.
 *
 * Auth app nav:   Dashboard · Connections ▾ · DB Ops · EBS Ops · Tune · Vault · Fleet · TuneOps · [Account]
 * Public nav:     Features ▾ · Pricing · Trust · Resources ▾ · About · [Sign in] [Start Free]
 * Resources ▾:    Blog · Compare Tools · TuneVault vs OEM · Setup Guide
 * Post-login:     Pricing link stays visible; button labels change dynamically based on plan
 * EBS Ops link is always clickable; the /ebs-ops page shows an empty state when no EBS exists.
 * Mobile: hamburger collapses all links into a stacked menu.
 *
 * Usage:
 * 1. Add <nav id="nav-root"></nav> to your page
 * 2. Call window.tvNav.init(type) where type is one of:
 *    'main' | 'pricing' | 'features' | 'auth' | 'dashboard' | 'admin' | 'report' |
 *    'ebs-deep' | 'ebs-middleware' | 'fleet' | 'sql-tuning' | 'security' | 'vault' | 'db-ops' | 'ebs-ops'
 * 3. Nav updates automatically based on auth state via /api/auth/me
 * 4. For report pages, register a callback with tvNav.onReady(fn) — fires after every auth-driven rebuild
 */

/* ── Shared modal theme ──────────────────────────────────────────────────────
 *
 * Injected from here because 35 of the 36 pages that build a modal already load
 * nav-component.js, and the app pages do NOT share a stylesheet — each carries its
 * own inline <style> with its own :root. This is the one file they have in common.
 *
 * Every modal in the app was hand-built with inline styles, which is why they
 * drifted: each one re-invented its surface, border and buttons, and none of them
 * picked up the gold accent the rest of the UI uses. Inline styles also beat
 * stylesheet rules, so these classes only take effect where the markup has been
 * migrated to use them — deliberately, rather than forcing it with !important and
 * leaving a trap for the next person.
 *
 * Uses the palette variables every app page already defines (--surface, --border,
 * --accent, --text, --text-dim), so it inherits each page's theme automatically.
 */
(function injectModalTheme() {
  if (document.getElementById('tv-modal-theme')) return;
  var css = document.createElement('style');
  css.id = 'tv-modal-theme';
  css.textContent = [
    '.tv-modal-overlay{position:fixed;inset:0;background:rgba(0,0,0,.7);z-index:10000;',
      'display:flex;align-items:center;justify-content:center;padding:16px;',
      'backdrop-filter:blur(3px);animation:tvModalFade .12s ease-out}',
    '@keyframes tvModalFade{from{opacity:0}to{opacity:1}}',
    '@keyframes tvModalRise{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}',

    '.tv-modal{background:var(--surface,#111114);border:1px solid var(--border,#2a2a30);',
      'border-top:2px solid var(--accent,#f0a830);',
      'border-radius:12px;padding:24px;width:460px;max-width:100%;',
      'box-shadow:0 24px 70px rgba(0,0,0,.6);animation:tvModalRise .14s ease-out}',

    '.tv-modal-title{font-size:1.05rem;font-weight:600;color:var(--text,#e8e8ed);margin-bottom:6px;',
      'display:flex;align-items:center;gap:8px}',
    '.tv-modal-title::before{content:"";width:3px;height:16px;border-radius:2px;',
      'background:var(--accent,#f0a830);flex:none}',
    '.tv-modal-desc{font-size:.82rem;color:var(--text-dim,#b8b8c8);margin-bottom:18px;line-height:1.55}',

    '.tv-modal-field{margin-bottom:12px}',
    '.tv-modal-label{font-size:.76rem;color:var(--text-dim,#b8b8c8);display:block;margin-bottom:5px;',
      'text-transform:uppercase;letter-spacing:.04em;font-weight:500}',
    '.tv-modal-input{width:100%;background:var(--bg,#0a0a0c);border:1px solid var(--border,#2a2a30);',
      'border-radius:7px;padding:9px 11px;font-size:.86rem;color:var(--text,#e8e8ed);',
      'box-sizing:border-box;transition:border-color .15s,box-shadow .15s;font-family:inherit}',
    '.tv-modal-input:focus{outline:none;border-color:var(--accent,#f0a830);',
      'box-shadow:0 0 0 3px rgba(240,168,48,.13)}',
    '.tv-modal-input::placeholder{color:var(--text-dim,#b8b8c8);opacity:.5}',

    /* The consent row is the one element that should read as a warning, so it
       carries the accent rather than the whole dialog shouting. */
    '.tv-modal-consent{display:flex;align-items:center;gap:10px;font-size:.82rem;cursor:pointer;',
      'padding:11px 12px;background:rgba(240,168,48,.07);border:1px solid rgba(240,168,48,.28);',
      'border-radius:8px;color:var(--text,#e8e8ed);line-height:1.45}',
    '.tv-modal-consent input[type=checkbox]{accent-color:var(--accent,#f0a830);width:15px;height:15px;flex:none;cursor:pointer}',

    '.tv-modal-error{color:var(--red,#f87171);font-size:.8rem;margin-bottom:12px;padding:8px 11px;',
      'background:rgba(248,113,113,.1);border:1px solid rgba(248,113,113,.25);border-radius:7px}',

    '.tv-modal-actions{display:flex;gap:9px;justify-content:flex-end;margin-top:18px}',
    '.tv-modal-btn{font-size:.83rem;padding:8px 18px;border-radius:7px;cursor:pointer;',
      'font-family:inherit;font-weight:500;transition:all .15s;border:1px solid transparent}',
    '.tv-modal-btn-cancel{background:transparent;color:var(--text-dim,#b8b8c8);',
      'border-color:var(--border,#2a2a30)}',
    '.tv-modal-btn-cancel:hover{color:var(--text,#e8e8ed);border-color:var(--text-dim,#b8b8c8)}',
    '.tv-modal-btn-confirm{background:var(--accent,#f0a830);color:#0a0a0c;font-weight:600}',
    '.tv-modal-btn-confirm:hover:not(:disabled){background:#e09820;transform:translateY(-1px)}',
    '.tv-modal-btn-confirm:disabled{opacity:.4;cursor:not-allowed;transform:none}',
    /* Destructive confirms stay gold, not red: the action is authorised, and a red
       primary button reads as "error" rather than "this is the button you want". */
  ].join('');
  document.head.appendChild(css);
})();

window.tvNav = (function() {

  var _readyCallbacks = [];

  // RBAC state — populated after auth via /api/me/role
  var _permissions = null;  // null = not yet loaded
  var _teamRole    = null;  // null = individual account (full access)

  // --- Reusable inline style fragments ---
  var linkStyle = 'font-size:13px;color:var(--text);text-decoration:none;padding:6px 12px;border-radius:6px;transition:all 0.2s;white-space:nowrap;';
  var linkStyleCompact = 'font-size:11px;color:var(--text);text-decoration:none;padding:4px 10px;border-radius:6px;transition:all 0.2s;white-space:nowrap;';
  var linkHover = "this.style.color='var(--accent)'";
  var linkOut   = "this.style.color='var(--text)'";

  var btnStyle = 'font-size:13px;color:var(--text);text-decoration:none;padding:6px 14px;border:1px solid rgba(255,255,255,0.15);border-radius:6px;transition:all 0.2s;white-space:nowrap;';
  var btnStyleCompact = 'font-size:11px;color:var(--text);text-decoration:none;padding:4px 10px;border:1px solid rgba(255,255,255,0.15);border-radius:6px;transition:all 0.2s;white-space:nowrap;';
  var btnHover = "this.style.color='var(--accent)';this.style.borderColor='var(--accent)'";
  var btnOut   = "this.style.color='var(--text)';this.style.borderColor='rgba(255,255,255,0.15)'";

  var goldOutlineStyle = 'font-size:13px;color:var(--accent);text-decoration:none;padding:6px 16px;border:1px solid var(--accent);border-radius:6px;font-weight:500;transition:all 0.2s;white-space:nowrap;';
  var goldOutlineHover = "this.style.background='var(--accent)';this.style.color='#0a0a0c'";
  var goldOutlineOut   = "this.style.background='transparent';this.style.color='var(--accent)'";

  var ctaStyle = 'display:inline-flex;align-items:center;gap:6px;padding:8px 20px;background:var(--accent);color:#0a0a0c;border-radius:6px;font-size:13px;font-weight:600;text-decoration:none;transition:all 0.2s;white-space:nowrap;';
  var ctaHover = "this.style.background='#e09820';this.style.transform='translateY(-1px)'";
  var ctaOut   = "this.style.background='var(--accent)';this.style.transform='translateY(0)'";

  var reportBtnStyle = 'font-size:13px;color:var(--text);text-decoration:none;padding:6px 14px;border:1px solid var(--accent);border-radius:6px;transition:all 0.2s;white-space:nowrap;';
  var reportBtnHover = "this.style.background='var(--accent)';this.style.color='#0a0a0c'";
  var reportBtnOut   = "this.style.background='transparent';this.style.color='var(--text)'";

  // Dropdown container shared styles
  var dropdownWrapStyle = 'position:relative;display:inline-block;';
  // z-index:9999 beats the grain overlay (999), tunebot panel (9000), and any
  // backdrop-filter stacking context created by sticky nav wrappers on app pages.
  var dropdownMenuStyle = 'position:absolute;top:calc(100% + 6px);left:0;min-width:220px;background:#111114;border:1px solid #2a2a30;border-radius:8px;padding:6px 0;z-index:9999;box-shadow:0 8px 24px rgba(0,0,0,0.5);';

  function buildDropdownItem(href, label, desc) {
    var d = desc ? '<div style="font-size:11px;color:#9898a8;margin-top:2px;line-height:1.3;">' + desc + '</div>' : '';
    return '<a href="' + href + '" style="display:block;padding:10px 16px;text-decoration:none;color:#e8e8ed;transition:background 0.15s;" ' +
      'onmouseover="this.style.background=\'rgba(240,168,48,0.08)\'" onmouseout="this.style.background=\'transparent\'">' +
      '<div style="font-size:13px;font-weight:500;">' + label + '</div>' + d +
      '</a>';
  }

  // Builds the Connections dropdown — DB Connections list + Connection Targets (agent management)
  function buildConnectionsDropdown(currentType, compact) {
    var lStyle = compact ? linkStyleCompact : linkStyle;
    var isActive = (currentType === 'connections' || currentType === 'connection-targets');
    var activeColor = isActive ? 'color:var(--accent);border-color:var(--accent);' : '';
    return '<div style="' + dropdownWrapStyle + '" class="tv-dropdown">' +
      '<button style="background:none;border:none;cursor:pointer;' + lStyle + activeColor + 'display:inline-flex;align-items:center;gap:4px;" ' +
        'onclick="tvNavToggleDropdown(this)" ' +
        'onmouseover="this.style.color=\'var(--accent)\'" ' +
        'onmouseout="this.style.color=\'' + (isActive ? 'var(--accent)' : 'var(--text)') + '\'">' +
        'Servers' +
      '</button>' +
      '<div id="nav-connections-menu" class="tv-dropdown-menu" role="menu" style="' + dropdownMenuStyle + '">' +
        buildDropdownItem('/connections', '🖥️ Servers', 'All connected Oracle servers with health status') +
        buildDropdownItem('/connections/new', '＋ Add Server', 'Connect a new Oracle server via agent') +
        buildDropdownItem('/sql-console', '🖥️ SQL Console', 'Run SQL queries against any connected Oracle database') +
        buildDropdownItem('/agent', '🕵️ DBA Agent', 'Autonomous spy-themed DBA agent — investigates issues live (Professional+)') +
        buildDropdownItem('/terminal', '📟 SSH Terminal', 'SSH terminal access to connected servers') +
      '</div>' +
    '</div>';
  }

  // Builds the DB Ops nav link (single link → /db-ops hub page)
  function buildDbOpsLink(currentType, compact) {
    var lStyle = compact ? linkStyleCompact : linkStyle;
    var isActive = (currentType === 'db-ops');
    var activeColor = isActive ? 'color:var(--accent);' : '';
    var hoverIn  = isActive ? "this.style.opacity='0.75'" : linkHover;
    var hoverOut = isActive ? "this.style.opacity='1'"   : linkOut;
    return '<a href="/db-ops" style="' + lStyle + activeColor + '" onmouseover="' + hoverIn + '" onmouseout="' + hoverOut + '">DB Ops</a>';
  }

  // Builds the EBS Ops nav link (single link → /ebs-ops hub page)
  // Always clickable — the /ebs-ops page shows an empty state when no EBS connections exist.
  function buildEbsLink(currentType, hasEbs, compact) {
    var lStyle = compact ? linkStyleCompact : linkStyle;
    var isActive = (currentType === 'ebs-ops' || currentType === 'ebs-deep' || currentType === 'ebs-middleware' || currentType === 'ebs-concurrent' || currentType === 'sanity-check' || currentType === 'ebs-12-2-checks');
    var activeColor = isActive ? 'color:var(--accent);' : '';
    var hoverIn  = isActive ? "this.style.opacity='0.75'" : linkHover;
    var hoverOut = isActive ? "this.style.opacity='1'"   : linkOut;
    return '<a href="/ebs-ops" style="' + lStyle + activeColor + '" onmouseover="' + hoverIn + '" onmouseout="' + hoverOut + '">EBS Ops</a>';
  }

  // Builds the Admin ▾ dropdown for admin-only nav entries.
  // Contains all /admin/* pages reachable from the top nav.
  // Used in both the authenticated app nav (isAdmin block) and the dedicated admin nav.
  function buildAdminDropdown(compact) {
    var bStyle = compact ? btnStyleCompact : btnStyle;
    return '<div style="' + dropdownWrapStyle + '" class="tv-dropdown">' +
      '<button style="background:none;border:1px solid rgba(255,255,255,0.15);cursor:pointer;' + bStyle + 'display:inline-flex;align-items:center;gap:4px;" ' +
        'onclick="tvNavToggleDropdown(this)" ' +
        'onmouseover="this.style.color=\'var(--accent)\';this.style.borderColor=\'var(--accent)\'" ' +
        'onmouseout="this.style.color=\'var(--text)\';this.style.borderColor=\'rgba(255,255,255,0.15)\'">' +
        'Admin &#9662;' +
      '</button>' +
      '<div class="tv-dropdown-menu" role="menu" style="' + dropdownMenuStyle + '">' +
        buildDropdownItem('/admin/agents', '🤖 Agents', 'Live agent fleet and installer health') +
        buildDropdownItem('/admin/ssh-targets', '🔑 SSH Targets', 'Admin SSH credential vault') +
        buildDropdownItem('/admin/users', '👤 Users', 'Manage user accounts') +
        buildDropdownItem('/admin/requests', '📋 Requests', 'Pending access requests') +
        buildDropdownItem('/admin/outreach-lock', '🔒 Outreach Lock', 'Hard-lock control panel') +
        buildDropdownItem('/admin/proxy-self-test', '🧪 Proxy Self-Test', 'Synthetic EBS pipeline validator') +
        buildDropdownItem('/admin/ebs-validation', '✅ EBS Validation', 'EBS smoke tests') +
        buildDropdownItem('/admin/schedules', '📅 Schedules', 'Monitoring schedule overview') +
        buildDropdownItem('/admin/funnel', '📊 Funnel', 'Conversion funnel analytics dashboard') +
      '</div>' +
    '</div>';
  }

  // Builds the Resources dropdown for public nav — Blog, Compare, vs OEM, Setup Guide, Runbooks
  function buildResourcesDropdown() {
    return '<div style="' + dropdownWrapStyle + '" class="tv-dropdown">' +
      '<button style="background:none;border:none;cursor:pointer;' + linkStyle + '" ' +
        'onclick="tvNavToggleDropdown(this)">' +
        'Resources &#9662;' +
      '</button>' +
      '<div class="tv-dropdown-menu" style="' + dropdownMenuStyle + '">' +
        buildDropdownItem('/blog', '📝 Blog', 'Oracle DBA field notes — 20 years of production lessons') +
        buildDropdownItem('/resources/runbooks', '🛠️ Runbooks', 'Step-by-step Oracle emergency recovery guides') +
        buildDropdownItem('/compare', '⚖️ Compare Tools', 'TuneVault vs Datadog, OEM, Grafana & more') +
        buildDropdownItem('/vs-oem', '🔄 TuneVault vs OEM', 'Head-to-head with Oracle Enterprise Manager') +
        buildDropdownItem('/download', '⬇️ Self-Hosted', 'Download and run TuneVault on your own infrastructure') +
        buildDropdownItem('/docs', '📖 Setup Guide', 'Get your Oracle DB connected in minutes') +
        buildDropdownItem('/docs', '📚 Docs', 'Getting started, health reports, and security model') +
        buildDropdownItem('/changelog', '🗒️ Release Notes', 'What shipped, month by month') +
      '</div>' +
    '</div>';
  }

  // Builds the Features dropdown for public nav.
  // isLoggedIn: when true, Fleet and SQL Tuning link directly to the in-app dashboards
  // (/fleet, /sql-tuning) so logged-in users skip the marketing landing pages.
  function buildProductDropdown(isLoggedIn) {
    var sqlHref   = isLoggedIn ? '/sql-tuning'   : '/features/sql-tuning';
    var sqlDesc   = isLoggedIn ? 'Go to your SQL tuning dashboard' : 'AI-powered query analysis';
    return '<div style="' + dropdownWrapStyle + '" class="tv-dropdown">' +
      '<button style="background:none;border:none;cursor:pointer;' + linkStyle + '" ' +
        'onclick="tvNavToggleDropdown(this)">' +
        'Features &#9662;' +
      '</button>' +
      '<div class="tv-dropdown-menu" style="' + dropdownMenuStyle + '">' +
        buildDropdownItem('/vault', '🛡️ Vault', 'Security posture, resilience checks, CVE exposure scanner') +
        buildDropdownItem('/tune', '⚡ Tune', 'Performance lab, ADDM findings, patch advisor, SQL tuning') +
        buildDropdownItem('/features', 'Oracle DB Monitoring', '200+ health checks, AI analysis') +
        buildDropdownItem('/features#ebs-wrapper', 'Oracle EBS Operations', 'CM, Workflow, ADOP, Deep EBS') +
        buildDropdownItem(sqlHref,   'SQL Tuning',       sqlDesc) +
        buildDropdownItem('/clients', 'Fleet Management', isLoggedIn ? 'Multi-client Oracle fleet dashboard' : 'Multi-instance overview') +
        buildDropdownItem('/architecture', 'How It Works', 'Proxy, polling, SSE — full technical explainer') +
      '</div>' +
    '</div>';
  }

  function buildLogo(type) {
    var logoSize = '18px';
    var iconSize = '28px';
    var iconFs   = '13px';
    var gap      = '10px';
    return '<a href="/" class="nav-logo" style="display:flex;align-items:center;gap:' + gap + ';text-decoration:none;color:var(--text);font-weight:700;font-size:' + logoSize + ';flex-shrink:0;">' +
      '<div class="logo-icon" style="width:' + iconSize + ';height:' + iconSize + ';background:var(--accent);border-radius:6px;display:flex;align-items:center;justify-content:center;font-family:\'JetBrains Mono\',monospace;font-size:' + iconFs + ';color:var(--bg);font-weight:700;">TV</div>' +
      '<span>TuneVault</span>' +
    '</a>';
  }

  function buildHamburger() {
    return '<button id="tv-hamburger" aria-label="Menu" style="display:none;background:none;border:none;cursor:pointer;padding:6px;color:var(--text);flex-shrink:0;" onclick="tvNavToggleMobile()">' +
      '<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg>' +
    '</button>';
  }

  function buildMobileMenuStyle() {
    return '<style>' +
      '.tv-dropdown-menu{display:none;}' +
      '.tv-dropdown-menu.tv-open{display:block;}' +
      '@media(max-width:860px){' +
        '#tv-hamburger{display:block!important;}' +
        // Mobile menu panel: cap height to the viewport and scroll internally so an
        // expanded dropdown's links never fall below the fold (the panel is
        // position:absolute — removed from flow — so the page can't scroll to reveal
        // them; without this, expanded links render but are physically untappable).
        '.nav-right{display:none!important;flex-direction:column!important;position:absolute!important;top:calc(100% + 6px)!important;right:0!important;min-width:240px!important;max-width:calc(100vw - 32px)!important;background:#111114!important;border:1px solid #2a2a30!important;border-radius:10px!important;padding:6px 0!important;gap:0!important;z-index:9999!important;box-shadow:0 8px 28px rgba(0,0,0,0.55)!important;max-height:calc(100vh - 96px)!important;overflow-y:auto!important;overflow-x:hidden!important;-webkit-overflow-scrolling:touch!important;}' +
        // When the mobile menu opens, .nav-right becomes a fixed, self-contained
        // scrollable overlay pinned below the nav bar (standard mobile drawer). Fixed —
        // not absolute — keeps it in the viewport and makes its OWN overflow-y:auto the
        // scroll container, so ALL items (including bottom Admin ▾ / Account ▾ submenu
        // entries) are reachable by scrolling the panel, independent of page flow. The
        // previous absolute+uncapped approach put overflow content out of flow with
        // nothing to scroll. --tv-nav-top is set by tvNavToggleMobile to the measured
        // nav-bar height so the panel clears the header on every page layout (bare nav,
        // .nav-wrapper, <nav> — some create a fixed-containing-block via backdrop-filter,
        // which is why we anchor to the header, not the viewport).
        '.nav-right.tv-mobile-open{display:flex!important;position:fixed!important;top:var(--tv-nav-top,60px)!important;right:8px!important;left:auto!important;max-width:calc(100vw - 16px)!important;max-height:calc(100vh - var(--tv-nav-top,60px) - 12px)!important;overflow-y:auto!important;overflow-x:hidden!important;-webkit-overflow-scrolling:touch!important;}' +
        // ≥44px tap targets on every top-level link + dropdown trigger.
        '.nav-right>a,.nav-right>button{padding:12px 18px!important;min-height:44px!important;display:flex!important;align-items:center!important;width:100%!important;box-sizing:border-box!important;border-radius:0!important;border:none!important;text-align:left!important;}' +
        '.nav-right>.tv-dropdown{display:block!important;width:100%!important;position:static!important;}' +
        '.nav-right>.tv-dropdown>button{padding:12px 18px!important;min-height:44px!important;display:flex!important;align-items:center!important;justify-content:space-between!important;width:100%!important;box-sizing:border-box!important;border-radius:0!important;border:none!important;text-align:left!important;}' +
        '.tv-dropdown-menu{position:static!important;box-shadow:none!important;border:none!important;background:rgba(255,255,255,0.03)!important;padding-left:12px!important;border-radius:0!important;}' +
        // Reveal submenus via the SAME class the JS toggles (tvNavToggleDropdown adds
        // .tv-open). The old rule keyed off .tv-mobile-open, which is only ever set on
        // .nav-right — never on a submenu — so it was dead and the accordion worked
        // only incidentally through the global .tv-open rule.
        '.tv-dropdown-menu.tv-open{display:block!important;}' +
        // Submenu links get a full 44px tap target too.
        '.tv-dropdown-menu>a{min-height:44px!important;display:flex!important;flex-direction:column!important;justify-content:center!important;}' +
      '}' +
    '</style>';
  }

  function buildNav(type, user, isAdmin, hasEbs) {
    var isLoggedIn = !!user;
    var isReportPage = (type === 'report');
    var currentLinkStyle = linkStyle;
    var currentBtnStyle  = btnStyle;
    var currentGoldStyle = goldOutlineStyle;
    var itemGap = '12px';

    var html = buildMobileMenuStyle() + buildLogo(type) + buildHamburger();

    // Auth-only: minimal nav
    if (type === 'auth') return html;

    // Admin: dedicated nav
    if (type === 'admin') {
      html += '<div class="nav-right" style="display:flex;align-items:center;gap:8px;">' +
        '<a href="/" style="' + btnStyle + '" onmouseover="' + btnHover + '" onmouseout="' + btnOut + '">&#8592; Back</a>' +
        buildAdminDropdown(false) +
        '<span style="width:1px;height:18px;background:rgba(255,255,255,0.12);margin:0 4px;"></span>' +
        (isLoggedIn
          ? '<span style="font-size:12px;color:var(--text-dim);">' + (user.name || user.email || '') + '</span>' +
            '<button onclick="window.tvNav.signOut()" style="background:none;' + btnStyle + 'cursor:pointer;" onmouseover="' + btnHover + '" onmouseout="' + btnOut + '">Sign out</button>'
          : '<a href="/login?redirect=' + encodeURIComponent(window.location.pathname) + '" style="' + goldOutlineStyle + '" onmouseover="' + goldOutlineHover + '" onmouseout="' + goldOutlineOut + '">Sign in</a>'
        ) +
        '</div>';
      return html;
    }

    var rightHtml = '';

    // ── AUTHENTICATED APP NAV ──────────────────────────────────────────────────
    var AUTH_TYPES = { dashboard:1, 'ebs-deep':1, fleet:1, 'sql-tuning':1, report:1, 'db-ops':1, 'ebs-ops':1, 'ebs-concurrent':1, 'sanity-check':1, activity:1, manager:1, pricing:1, connections:1, settings:1, billing:1, 'connection-targets':1, 'ebs-clone':1, 'ebs-control-exec':1, security:1, vault:1, 'ebs-middleware':1, 'ebs-patches':1, 'ebs-status-sources':1, 'os-diagnostics':1, 'ebs-log-tail':1, fndload:1, performance:1, addm:1, patches:1, tuneops:1, tune:1, 'sql-console':1, terminal:1, 'mcp-setup':1, 'mcp-docs':1, 'settings-api':1, 'access-advisor':1, 'segment-advisor':1, 'memory-advisor':1, 'compression-advisor':1, 'index-advisor':1, 'space-advisor':1, 'stats-advisor':1, 'awr-advisor':1, 'ebs-12-2-checks':1, docs:1, clients:1, 'clients-detail':1, download:1, 'db-clone':1, agent:1 };
    if (isLoggedIn && AUTH_TYPES[type]) {
      // Dashboard
      rightHtml += '<a href="/dashboard" style="' + currentLinkStyle + (type === 'dashboard' ? 'color:var(--accent);' : '') + '" onmouseover="' + (type === 'dashboard' ? "this.style.opacity='0.75'" : linkHover) + '" onmouseout="' + (type === 'dashboard' ? "this.style.opacity='1'" : linkOut) + '">Dashboard</a>';

      // Connections dropdown — DB Connections + Connection Targets + Add Connection
      rightHtml += buildConnectionsDropdown(type, false);

      // DB Ops link
      rightHtml += buildDbOpsLink(type, false);

      // EBS Ops link
      rightHtml += buildEbsLink(type, hasEbs, false);

      // Tune hub — Performance Lab, ADDM, Patch Advisor, SQL Tuning, and all advisor sub-pages
      var _tunePages = {'tune':1,'access-advisor':1,'segment-advisor':1,'memory-advisor':1,'compression-advisor':1,'index-advisor':1,'space-advisor':1,'stats-advisor':1,'awr-advisor':1};
      var _isTune = !!_tunePages[type];
      rightHtml += '<a href="/tune" style="' + currentLinkStyle + (_isTune ? 'color:var(--accent);' : '') + '" onmouseover="' + (_isTune ? "this.style.opacity='0.75'" : linkHover) + '" onmouseout="' + (_isTune ? "this.style.opacity='1'" : linkOut) + '">Tune</a>';

      // Vault — DB Security, Vulnerability Exposure, Resilience
      rightHtml += '<a href="/vault" style="' + currentLinkStyle + (type === 'vault' ? 'color:var(--accent);' : '') + '" onmouseover="' + (type === 'vault' ? "this.style.opacity='0.75'" : linkHover) + '" onmouseout="' + (type === 'vault' ? "this.style.opacity='1'" : linkOut) + '">Vault</a>';

      // MCP — Claude integration
      var isMcpType = (type === 'mcp-setup' || type === 'mcp-docs');
      rightHtml += '<a href="/mcp/setup" style="' + currentLinkStyle + (isMcpType ? 'color:var(--accent);' : '') + '" onmouseover="' + (isMcpType ? "this.style.opacity='0.75'" : linkHover) + '" onmouseout="' + (isMcpType ? "this.style.opacity='1'" : linkOut) + '">MCP</a>';

      // TuneOps
      rightHtml += '<a href="/tuneops" style="' + currentLinkStyle + (type === 'tuneops' ? 'color:var(--accent);' : '') + '" onmouseover="' + (type === 'tuneops' ? "this.style.opacity='0.75'" : linkHover) + '" onmouseout="' + (type === 'tuneops' ? "this.style.opacity='1'" : linkOut) + '">TuneOps</a>';

      // Clients — multi-client fleet dashboard
      var isClientsType = (type === 'clients' || type === 'clients-detail');
      rightHtml += '<a href="/clients" style="' + currentLinkStyle + (isClientsType ? 'color:var(--accent);' : '') + '" onmouseover="' + (isClientsType ? "this.style.opacity='0.75'" : linkHover) + '" onmouseout="' + (isClientsType ? "this.style.opacity='1'" : linkOut) + '">🏢 Clients</a>';

      // Activity and Manager moved to Admin dropdown

      // User email + admin links
      if (isAdmin) {
        rightHtml += buildAdminDropdown(true);
      }
      // User account dropdown — replaces scattered Billing/Pricing/Security/SSO/Sign out buttons
      rightHtml += '<div style="' + dropdownWrapStyle + '" class="tv-dropdown">' +
        '<button id="nav-user-btn" style="background:none;border:none;cursor:pointer;display:inline-flex;align-items:center;gap:6px;font-size:13px;color:var(--text-dim);padding:4px 8px;border:1px solid rgba(255,255,255,0.15);border-radius:5px;white-space:nowrap;flex-shrink:0;transition:all 0.2s;" ' +
          'onclick="tvNavToggleDropdown(this)" ' +
          'onmouseover="this.style.color=\'var(--accent)\';this.style.borderColor=\'var(--accent)\'" ' +
          'onmouseout="this.style.color=\'var(--text-dim)\';this.style.borderColor=\'rgba(255,255,255,0.15)\'">' +
          '<span id="nav-user-email" style="font-size:13px;">Account</span> &#9662;' +
        '</button>' +
        '<div class="tv-dropdown-menu" role="menu" style="' + dropdownMenuStyle + 'right:0;left:auto;">' +
          buildDropdownItem('/settings', '⚙️ Settings', 'Account settings and preferences') +
          buildDropdownItem('/settings/billing', '💳 Billing', 'Subscription and usage') +
          // The ONLY route to the self-hosted download from inside the app. It is
          // otherwise reachable only from the marketing Resources dropdown, which
          // this nav replaces once you are on an app page — so a signed-in
          // customer, which is everyone who has a license key, had to leave the
          // product to find the zip.
          buildDropdownItem('/download', '⬇️ Self-Hosted', 'Download TuneVault to run on your own infrastructure') +
          buildDropdownItem('/pricing', '📋 Pricing', 'Plans and features') +
          buildDropdownItem('/settings/security', '🔒 Security', 'Password and 2FA') +
          buildDropdownItem('/settings/sso', '🏢 SSO', 'Single sign-on settings') +
          buildDropdownItem('/settings/api', '🔑 API Keys', 'Manage API keys for Claude and REST API access') +
          buildDropdownItem('/mcp/docs', '🤖 MCP Tools', '20 Oracle tools for Claude Desktop and Claude Code') +
          '<div style="border-top:1px solid rgba(255,255,255,0.1);margin:4px 0;"></div>' +
          '<button id="nav-signout-btn" onclick="window.tvNav.signOut()" style="width:100%;text-align:left;background:none;border:none;cursor:pointer;padding:10px 16px;font-size:13px;color:var(--text-dim);display:flex;align-items:center;gap:10px;transition:background 0.15s;" onmouseover="this.style.background=\'rgba(255,255,255,0.05)\'" onmouseout="this.style.background=\'none\'">🚪 Sign out</button>' +
        '</div>' +
      '</div>';

    } else if (isLoggedIn) {
      // Authenticated user on public/marketing pages — Pricing stays visible (dynamic buttons)
      // Pass true so Fleet + SQL Tuning links go directly to the in-app dashboards
      rightHtml += buildProductDropdown(true);
      rightHtml += '<a href="/pricing" style="' + currentLinkStyle + '" onmouseover="' + linkHover + '" onmouseout="' + linkOut + '">Pricing</a>';
      rightHtml += buildResourcesDropdown();
      rightHtml += '<a href="/trust" style="' + currentLinkStyle + '" onmouseover="' + linkHover + '" onmouseout="' + linkOut + '">Trust</a>';
      rightHtml += '<a href="/docs" style="' + currentLinkStyle + '" onmouseover="' + linkHover + '" onmouseout="' + linkOut + '">Docs</a>';
      rightHtml += '<a href="/about" style="' + currentLinkStyle + '" onmouseover="' + linkHover + '" onmouseout="' + linkOut + '">About</a>';
      rightHtml += '<a href="/sample-report" style="' + currentLinkStyle + '" onmouseover="' + linkHover + '" onmouseout="' + linkOut + '">Sample Report</a>';
      rightHtml += '<a href="/dashboard" style="' + currentGoldStyle + '" onmouseover="' + goldOutlineHover + '" onmouseout="' + goldOutlineOut + '">Dashboard</a>';
      rightHtml += '<button id="nav-signout-btn" onclick="window.tvNav.signOut()" style="background:none;' + currentBtnStyle + 'cursor:pointer;" onmouseover="' + btnHover + '" onmouseout="' + btnOut + '">Sign out</button>';

    } else {
      // ── PUBLIC/MARKETING NAV ──────────────────────────────────────────────────
      if (type !== 'auth') {
        // Product dropdown — pass false (not logged in) so links go to marketing pages
        rightHtml += buildProductDropdown(false);

        // Core marketing links
        rightHtml += '<a href="/pricing" style="' + currentLinkStyle + '" onmouseover="' + linkHover + '" onmouseout="' + linkOut + '">Pricing</a>';
        rightHtml += '<a href="/trust" style="' + currentLinkStyle + '" onmouseover="' + linkHover + '" onmouseout="' + linkOut + '">Trust</a>';
        rightHtml += '<a href="/docs" style="' + currentLinkStyle + '" onmouseover="' + linkHover + '" onmouseout="' + linkOut + '">Docs</a>';
        rightHtml += buildResourcesDropdown();
        rightHtml += '<a href="/about" style="' + currentLinkStyle + '" onmouseover="' + linkHover + '" onmouseout="' + linkOut + '">About</a>';
        rightHtml += '<a href="/sample-report" style="' + currentLinkStyle + '" onmouseover="' + linkHover + '" onmouseout="' + linkOut + '">Sample Report</a>';

        // Auth CTAs
        // Preserve current page as redirect target so user returns here after auth
        var returnPath = encodeURIComponent(window.location.pathname + window.location.search);
        rightHtml += '<a href="/signin?redirect=' + returnPath + '" style="' + currentGoldStyle + '" id="nav-signin" onmouseover="' + goldOutlineHover + '" onmouseout="' + goldOutlineOut + '" data-track="nav_signin">Sign in</a>';
        rightHtml += '<a href="/signup?path=health-check&redirect=/dashboard" class="nav-cta" id="nav-cta" style="' + ctaStyle + '" onmouseover="' + ctaHover + '" onmouseout="' + ctaOut + '" data-track="nav_signup">Start Free</a>';
      }
    }

    html += '<div class="nav-right" style="display:flex;align-items:center;gap:' + itemGap + ';flex-wrap:wrap;row-gap:8px;overflow:visible;">' + rightHtml + '</div>';
    return html;
  }

  function signOut() {
    fetch('/api/auth/logout', { method: 'POST', credentials: 'include' })
      .finally(function() { window.location.href = '/'; });
  }

  function fireReadyCallbacks() {
    for (var i = 0; i < _readyCallbacks.length; i++) {
      try { _readyCallbacks[i](); } catch (e) { /* silent */ }
    }
  }

  function revealNav(navRoot) {
    var navRight = navRoot && navRoot.querySelector('.nav-right');
    if (navRight) { navRight.style.opacity = '1'; }
  }

  // Checks if any connection has is_ebs=true; returns promise<boolean>
  function checkHasEbs() {
    return fetch('/api/connections', { credentials: 'include' })
      .then(function(r) { return r.ok ? r.json() : []; })
      .then(function(rows) { return rows.some(function(c) { return c.is_ebs; }); })
      .catch(function() { return false; });
  }

  function updateNavForAuth(type) {
    var navRoot = document.getElementById('nav-root');
    var authReq = fetch('/api/auth/me', { credentials: 'include' })
      .then(function(r) { return r.ok ? r.json() : null; });

    Promise.all([authReq]).then(function(results) {
      var data = results[0];
      var user = data ? data.user : null;
      var isAdmin = user ? user.is_admin : false;
      var managerRole = user ? user.manager_role : null;

      // Only fetch EBS state when authenticated (avoid 401s on public nav)
      var ebsCheck = user
        ? checkHasEbs()
        : Promise.resolve(false);

      // Fetch RBAC permissions for logged-in users (non-blocking, best-effort)
      var roleReq = user
        ? fetch('/api/me/role', { credentials: 'include' })
            .then(function(r) { return r.ok ? r.json() : null; })
            .catch(function() { return null; })
        : Promise.resolve(null);

      Promise.all([ebsCheck, roleReq]).then(function(ebsRoleResults) {
        var hasEbs = ebsRoleResults[0];
        var roleData = ebsRoleResults[1];

        // Store permissions globally — null = full access (individual account or load failure)
        if (roleData) {
          _permissions = roleData.permissions || null;
          _teamRole = roleData.role;  // null = individual account
        } else {
          _permissions = null;
          _teamRole = null;
        }

        if (navRoot) {
          navRoot.innerHTML = buildNav(type, user, isAdmin, hasEbs);
          revealNav(navRoot);
          // Dynamically inject per-connection quick-links into the Connections dropdown
          if (user) { loadNavConnections(); }
        }

        // Show Manager link for users with manager/sdm/admin role
        var managerRoles = { manager: true, sdm: true, admin: true };
        if (user && (isAdmin || managerRoles[managerRole])) {
          var mgrLink = document.getElementById('nav-manager');
          if (mgrLink) mgrLink.style.display = '';
        }

        // Apply RBAC gating to all elements with data-rbac-require attribute.
        // Disabled buttons get a tooltip and reduced opacity; they remain visible.
        applyRbacGating();

        if (user) {
          var emailEl = document.getElementById('nav-user-email');
          if (emailEl) {
            var displayName = user.name || user.email || '';
            // Show role badge in nav using already-fetched role data
            var role = _teamRole;
            if (role) {
              var roleColors = {
                admin: 'background:rgba(240,168,48,.15);color:#f0a830;border:1px solid rgba(240,168,48,.25);',
                senior_dba: 'background:rgba(96,165,250,.12);color:#60a5fa;border:1px solid rgba(96,165,250,.2);',
                junior_dba: 'background:rgba(52,211,153,.1);color:#34d399;border:1px solid rgba(52,211,153,.2);',
                viewer: 'background:rgba(255,255,255,.06);color:#9898a8;border:1px solid #2a2a30;'
              };
              var roleLabels = { admin: 'Admin', senior_dba: 'Senior DBA', junior_dba: 'Junior DBA', viewer: 'Viewer' };
              var badgeStyle = roleColors[role] || roleColors.viewer;
              emailEl.innerHTML = displayName +
                ' <span style="display:inline-flex;align-items:center;padding:1px 7px;border-radius:4px;font-size:10px;font-weight:700;letter-spacing:.04em;margin-left:4px;' + badgeStyle + '">' +
                (roleLabels[role] || role) + '</span>';
            } else {
              emailEl.textContent = displayName;
            }
          }

          // Update hero CTAs for logged-in users
          document.querySelectorAll('a.hero-cta').forEach(function(a) {
            if (a.href && a.href.includes('signup')) {
              if (a.href.includes('demo=1')) {
                a.href = '/dashboard';
                a.innerHTML = 'Open Dashboard \u2192';
              } else {
                a.href = '/dashboard';
                a.innerHTML = 'Run Health Check \u2192';
              }
            }
          });
          // demo-preview-cta links (View Sample Report, View Sample EBS Report) intentionally
          // keep their original href — they should go to the sample pages for all users.
          document.querySelectorAll('a.statement-cta').forEach(function(a) {
            if (a.href && a.href.includes('signup')) {
              a.href = '/dashboard';
              a.innerHTML = 'Open Dashboard \u2192';
              // Fix contrast: strip inline color overrides and hover handlers
              // that cause gold-on-gold text (invisible on hover)
              a.removeAttribute('onmouseover');
              a.removeAttribute('onmouseout');
              if (a.classList.contains('hero-cta-text')) {
                // Hero-level button: swap to .hero-cta to match sibling CTAs
                a.removeAttribute('style');
                a.className = 'hero-cta';
              }
            }
          });
        }

        document.querySelectorAll('[data-auth-guard]').forEach(function(el) {
          el.style.opacity = '1';
          el.style.transition = 'opacity 0.15s ease';
        });

        fireReadyCallbacks();
      });
    }).catch(function() {
      revealNav(navRoot);
      document.querySelectorAll('[data-auth-guard]').forEach(function(el) {
        el.style.opacity = '1';
      });
      fireReadyCallbacks();
    });
  }

  // Applies RBAC gating to elements with data-rbac-require="<permission>" attribute.
  // Disables buttons/links and shows tooltip for permissions the user lacks.
  // Safe to call multiple times — idempotent (checks already-disabled state).
  function applyRbacGating() {
    if (_permissions === null) return; // still loading or individual account — no gating needed
    document.querySelectorAll('[data-rbac-require]').forEach(function(el) {
      var perm = el.getAttribute('data-rbac-require');
      var allowed = _permissions[perm];
      if (allowed === false) {
        // Disable the element
        el.setAttribute('disabled', 'disabled');
        el.style.opacity = '0.4';
        el.style.cursor = 'not-allowed';
        el.style.pointerEvents = 'none';
        // Wrap in a span with a tooltip if not already wrapped
        if (!el.parentElement || !el.parentElement.classList.contains('tv-rbac-wrap')) {
          var roleNeeded = { run_health_checks: 'Junior DBA', export_reports: 'Junior DBA', run_sql_tuning: 'Junior DBA',
            execute_db_ops_read: 'Junior DBA', execute_db_ops_write: 'Senior DBA', execute_ebs_control: 'Senior DBA',
            kill_sessions: 'Senior DBA', execute_ssh: 'Senior DBA', manage_connections: 'Senior DBA',
            delete_connections: 'Admin', manage_ssh_targets: 'Senior DBA', configure_monitoring: 'Senior DBA',
            manage_team: 'Admin', manage_billing: 'Admin' };
          var needed = roleNeeded[perm] || 'higher';
          var wrap = document.createElement('span');
          wrap.className = 'tv-rbac-wrap';
          wrap.title = 'Requires ' + needed + ' role or above';
          wrap.style.cssText = 'display:inline-block;cursor:not-allowed;';
          el.parentNode.insertBefore(wrap, el);
          wrap.appendChild(el);
        }
      }
    });
  }

  return {
    init: function(type) {
      if (window.__TV_EMBED__) return;
      var navRoot = document.getElementById('nav-root');
      if (!navRoot) return;

      // Render logged-out shell first (prevents auth flash)
      navRoot.innerHTML = buildNav(type, null, false, false);
      navRoot.style.display = 'flex';
      navRoot.style.justifyContent = 'space-between';
      navRoot.style.alignItems = 'center';
      navRoot.style.position = 'relative';
      var hPad = window.innerWidth < 600 ? '16px' : window.innerWidth < 900 ? '24px' : '48px';
      var vPad = '16px';
      navRoot.style.padding = vPad + ' ' + hPad;
      navRoot.style.maxWidth = '1400px';
      navRoot.style.margin = '0 auto';

      var navRight = navRoot.querySelector('.nav-right');
      if (navRight) {
        navRight.style.opacity = '0';
        navRight.style.transition = 'opacity 0.15s ease';
      }

      updateNavForAuth(type);
    },
    signOut: signOut,
    onReady: function(fn) {
      if (typeof fn === 'function') _readyCallbacks.push(fn);
    },
    // Check if current user has a permission. Returns true for individual accounts (no team).
    can: function(permission) {
      if (_permissions === null) return true; // individual account = full access
      return _permissions[permission] === true;
    },
    // Returns the current user's team role. null = individual (full access).
    getRole: function() { return _teamRole; },
    // Re-run RBAC gating — call this after dynamically adding [data-rbac-require] elements
    refreshRbac: function() { applyRbacGating(); },
  };

  // ── Dynamic connection list in Connections dropdown ─────────────────────────
  // Fetches /api/connections + /api/health-checks and injects:
  //   • per-connection quick-links (with score pill) at the top of the dropdown
  //   • "Recent Reports" section (last 5 completed HCs) at the bottom
  function loadNavConnections() {
    var timeNavAgo = function(dateStr) {
      var diff = Date.now() - new Date(dateStr).getTime();
      var mins = Math.floor(diff / 60000);
      if (mins < 1) return 'just now';
      if (mins < 60) return mins + 'm ago';
      var hrs = Math.floor(mins / 60);
      if (hrs < 24) return hrs + 'h ago';
      return Math.floor(hrs / 24) + 'd ago';
    };

    var connsP = fetch('/api/connections', { credentials: 'include' })
      .then(function(r) { return r.ok ? r.json() : []; })
      .catch(function() { return []; });
    var hcsP = fetch('/api/health-checks', { credentials: 'include' })
      .then(function(r) { return r.ok ? r.json() : []; })
      .catch(function() { return []; });

    Promise.all([connsP, hcsP]).then(function(results) {
      var rows = results[0];
      var hcs  = results[1];
      if (!Array.isArray(rows)) return;
      var active = rows.filter(function(c) { return c.name && c.id; });
      if (!active.length) return;

      var connMenu = document.getElementById('nav-connections-menu');
      if (!connMenu) return;
      if (connMenu.querySelector('[data-nav-conn]')) return;

      var typeLabel = function(c) {
        if (c.server_type === 'apps' || c.server_type === 'both') return 'APP';
        if (c.server_type === 'db') return 'DB';
        if (c.is_ebs) return 'EBS';
        return 'DB';
      };

      // ── Per-connection quick-links (inserted before existing static items) ──
      var divider = document.createElement('div');
      divider.style.cssText = 'border-top:1px solid rgba(255,255,255,0.08);margin:4px 0;';
      divider.setAttribute('data-nav-conn', '1');
      connMenu.insertBefore(divider, connMenu.firstChild);

      for (var i = active.length - 1; i >= 0; i--) {
        var c = active[i];
        var a = document.createElement('a');
        a.href = c.latest_hc_id ? '/report/' + c.latest_hc_id : '/connections';
        a.setAttribute('data-nav-conn', '1');
        a.style.cssText = 'display:block;padding:8px 16px;text-decoration:none;color:#e8e8ed;transition:background 0.15s;';
        a.onmouseover = function() { this.style.background = 'rgba(240,168,48,0.08)'; };
        a.onmouseout  = function() { this.style.background = 'transparent'; };
        var scoreHtml = '';
        if (c.latest_hc_score !== null && c.latest_hc_score !== undefined) {
          var sc = c.latest_hc_score;
          var scColor = sc >= 80 ? '#34d399' : sc >= 60 ? '#fbbf24' : '#f87171';
          scoreHtml = '<span style="font-size:11px;color:' + scColor + ';font-weight:600;margin-left:auto;padding-left:8px;flex-shrink:0;">' + sc + '</span>';
        }
        a.innerHTML =
          '<div style="display:flex;align-items:center;gap:8px;font-size:13px;font-weight:500;">' +
            (c.name || 'Connection #' + c.id) +
            '<span style="font-size:9px;padding:1px 5px;border-radius:3px;background:rgba(255,255,255,0.08);color:#9898a8;font-family:monospace;flex-shrink:0">' +
              typeLabel(c) +
            '</span>' +
            scoreHtml +
          '</div>';
        connMenu.insertBefore(a, connMenu.firstChild);
      }

    }).catch(function() {}); // silent fail — nav still works without dynamic data
  }

})();

// ── Global helpers (outside the module so onclick attributes work) ──────────

// Close every open dropdown.
function tvNavCloseAllDropdowns() {
  document.querySelectorAll('.tv-dropdown-menu').forEach(function(m) {
    m.classList.remove('tv-open');
  });
}

function tvNavToggleDropdown(btn) {
  var wrap = btn.parentElement;
  var menu = wrap ? wrap.querySelector('.tv-dropdown-menu') : null;
  if (!menu) return;
  var isOpen = menu.classList.contains('tv-open');

  // Close all open dropdowns first
  tvNavCloseAllDropdowns();

  if (!isOpen) {
    menu.classList.add('tv-open');
    // Mobile: the open submenu lives inside the fixed, scrollable .nav-right overlay.
    // After the browser lays out the newly revealed items (next frame), scroll the
    // submenu's last item into view so bottom entries (Admin ▾ / Account ▾) are
    // reachable within the panel. Desktop (>860px, absolute overlay) is a no-op.
    if (window.innerWidth <= 860) {
      var scrollLast = function() {
        var last = menu.lastElementChild;
        if (last && last.scrollIntoView) {
          try { last.scrollIntoView({ block: 'nearest' }); } catch (e) { last.scrollIntoView(); }
        }
      };
      if (window.requestAnimationFrame) window.requestAnimationFrame(scrollLast);
      else scrollLast();
    }
  }
}

function tvNavToggleMobile() {
  var navRoot  = document.getElementById('nav-root');
  var navRight = navRoot ? navRoot.querySelector('.nav-right') : null;
  if (!navRight) return;
  var willOpen = !navRight.classList.contains('tv-mobile-open');
  if (willOpen && window.innerWidth <= 860) {
    // Pin the fixed overlay just below the nav bar. Measuring nav-root's height keeps
    // it accurate across page layouts (some wrap the nav in a backdrop-filter sticky
    // header that becomes the fixed-positioning containing block, so we anchor to the
    // header height rather than assuming the viewport top).
    var top = 60;
    try {
      // Viewport-relative bottom of the nav bar. On sticky/backdrop-filter headers the
      // containing block sits at the viewport top, so this bottom also equals the
      // containing-block-relative offset — clearing the header (and any wrapper padding).
      var b = Math.round(navRoot.getBoundingClientRect().bottom);
      if (b > 0) top = b;
    } catch (e) {}
    navRight.style.setProperty('--tv-nav-top', (top + 4) + 'px');
  }
  navRight.classList.toggle('tv-mobile-open');
  if (!willOpen) tvNavCloseAllDropdowns();
}

// Close dropdowns + mobile nav when clicking outside
document.addEventListener('click', function(e) {
  if (!e.target.closest('.tv-dropdown')) {
    tvNavCloseAllDropdowns();
  }
  if (!e.target.closest('#nav-root .nav-right') && !e.target.closest('#tv-hamburger')) {
    var navRight = document.querySelector('#nav-root .nav-right');
    if (navRight) navRight.classList.remove('tv-mobile-open');
  }
});

// Keyboard navigation: Escape closes open dropdown; arrow keys + Enter navigate items.
document.addEventListener('keydown', function(e) {
  var openMenu = document.querySelector('.tv-dropdown-menu.tv-open');

  if (e.key === 'Escape') {
    tvNavCloseAllDropdowns();
    return;
  }

  if (!openMenu) return;

  var items = Array.prototype.slice.call(openMenu.querySelectorAll('a'));
  if (!items.length) return;

  var focused = document.activeElement;
  var idx = items.indexOf(focused);

  if (e.key === 'ArrowDown') {
    e.preventDefault();
    var next = items[idx + 1] || items[0];
    next.focus();
  } else if (e.key === 'ArrowUp') {
    e.preventDefault();
    var prev = items[idx - 1] || items[items.length - 1];
    prev.focus();
  } else if (e.key === 'Enter' && idx >= 0) {
    // Let the default link navigation happen (no preventDefault)
  }
});

// ── Self-hosted origin rewrite (site-wide) ────────────────────────────────────
// On a self-hosted instance, rewrite hardcoded tunevault.app references so copy/
// paste install & verify commands and host labels point at THIS instance, not the
// cloud SaaS. On *.tunevault.app (cloud) this is a no-op, so cloud docs are unchanged.
// nav-component.js loads at body-bottom, so the DOM is ready by the time this runs —
// fine for rewriting rendered content (it cannot, however, prevent the head's font
// fetch; that stays a per-page concern).
(function () {
  function tvRewriteOrigin() {
    if (/(^|\.)tunevault\.app$/.test(location.hostname)) return; // cloud — keep tunevault.app
    var origin = location.origin;   // e.g. http://10.0.0.5:3000
    var host = location.host;       // e.g. 10.0.0.5:3000
    // 1. Full https://tunevault.app/... URLs inside command/code blocks → this origin.
    //    Leaf elements only (children.length === 0) so we never flatten highlighted markup.
    document.querySelectorAll('code, pre, .tv-origin-cmd').forEach(function (el) {
      if (el.children.length === 0 && el.textContent.indexOf('https://tunevault.app') !== -1) {
        el.textContent = el.textContent.replace(/https:\/\/tunevault\.app/g, origin);
      }
    });
    // 2. Copy buttons: rewrite the command that actually gets copied.
    document.querySelectorAll('[data-copy]').forEach(function (el) {
      if (el.dataset.copy && el.dataset.copy.indexOf('https://tunevault.app') !== -1) {
        el.dataset.copy = el.dataset.copy.replace(/https:\/\/tunevault\.app/g, origin);
      }
    });
    // 3. Bare hostname labels — OPT-IN via .tv-origin-host only (generic bare-hostname
    //    rewrites are unsafe: they'd hit emails, prose, etc.).
    document.querySelectorAll('.tv-origin-host').forEach(function (el) {
      if (el.children.length === 0) {
        el.textContent = el.textContent.replace(/tunevault\.app/g, host);
      }
    });
  }
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', tvRewriteOrigin);
  } else {
    tvRewriteOrigin();
  }
})();
