/**
 * Zeq Side Panel — Main Controller
 * Wires together CKO, Auth, HITE, ZeqText, Vault, Skills, HulyaPulse, and Chat.
 * Unified auth in header dropdown — one Zeq ID powers all tabs.
 *
 * @version 1.287.5
 * @license CC BY 4.0 — Zeq. H / Zeq 1.287 Hz Limited
 */

(() => {
  'use strict';

  // ── Constants (charter-exact; ZeqConfig is the source of truth) ──────────
  const HZ        = (self.ZeqConfig && ZeqConfig.HZ) || 1.287;
  const ZEQOND    = (self.ZeqConfig && ZeqConfig.TAU_S) || 0.777000777;
  const ALPHA     = (self.ZeqConfig && ZeqConfig.ALPHA_KO42) || 0.001;
  const API_URL   = 'https://zeqstate.com/api';

  const PLATFORMS = [
    { id: 'chatgpt',    label: 'ChatGPT' },
    { id: 'claude',     label: 'Claude' },
    { id: 'deepseek',   label: 'DeepSeek' },
    { id: 'grok',       label: 'Grok' },
    { id: 'groq',       label: 'Groq' },
    { id: 'perplexity', label: 'Perplexity' },
    { id: 'poe',        label: 'POE' },
    { id: 'gemini',     label: 'Gemini' },
  ];

  const SKILL_TEMPLATES = [
    { id: 'healthcare',    label: 'Healthcare',        icon: '🏥' },
    { id: 'aerospace',     label: 'Aerospace',         icon: '🚀' },
    { id: 'quantum',       label: 'Quantum Computing', icon: '⚛️' },
    { id: 'aiml',          label: 'AI / ML',           icon: '🤖' },
    { id: 'finance',       label: 'Finance',           icon: '📊' },
    { id: 'biotech',       label: 'Biotech',           icon: '🧬' },
    { id: 'energy',        label: 'Energy',            icon: '⚡' },
    { id: 'manufacturing', label: 'Manufacturing',     icon: '🏭' },
    { id: 'environmental', label: 'Environmental',     icon: '🌍' },
    { id: 'defense',       label: 'Defense',           icon: '🛡️' },
    { id: 'education',     label: 'Education',         icon: '📚' },
    { id: 'custom',        label: 'Custom',            icon: '✨' },
  ];

  // ── State ──────────────────────────────────────────────────────────────
  let apiUrl = API_URL;
  let _showLoginPassword = false;
  let _showRegisterPassword = false;

  // ── Init ───────────────────────────────────────────────────────────────
  document.addEventListener('DOMContentLoaded', async () => {
    await loadSettings();
    initTabs();
    startPulseDisplay();
    CKOWidget.injectStyles();
    await initAuth();
    wireAuthGate();
    wireAppViewer();
    applyAuthGate();
    renderPlatformToggles();
    renderSkillToggles();
    wireChat();
    wireSettings();
    initTabControls();
    CKOClient.onUpdate(onCKOUpdate);
    chrome.runtime?.onMessage?.addListener(handleMessage);
    wireServiceStatus();
    refreshServiceStatus();
    wireContextBar();
  });

  // ── Tab Navigation ─────────────────────────────────────────────────────
  function initTabs() {
    const buttons = document.querySelectorAll('.tab-btn');
    buttons.forEach(btn => {
      btn.addEventListener('click', () => {
        // Stop the Observer poll whenever we leave it (re-armed on reopen).
        if (typeof ObserverTab !== 'undefined' && btn.dataset.tab !== 'observer') ObserverTab.stop();
        if (typeof DaemonTab !== 'undefined' && btn.dataset.tab !== 'daemon') DaemonTab.stop();
        buttons.forEach(b => b.classList.remove('active'));
        btn.classList.add('active');

        document.querySelectorAll('.tab-content').forEach(tc => tc.classList.remove('active'));
        const tabId = `tab-${btn.dataset.tab}`;
        document.getElementById(tabId)?.classList.add('active');

        // Zeq Text — host the live Message app in-panel (real messenger,
        // framed via the chrome-extension frame-ancestors allowance). The old
        // local client targeted a dead localhost WS; this runs the real app.
        if (btn.dataset.tab === 'text') {
          hostLiveApp('zeqtext-container', '/apps/message/');
        }

        // Zeq Vault — host the full live vault app in-panel (real machine vault).
        if (btn.dataset.tab === 'vault') {
          hostLiveApp('vault-container', '/vault/');
        }

        // Mail — host the live Mail app in-panel.
        if (btn.dataset.tab === 'mail') {
          hostLiveApp('mail-container', '/apps/mail/');
        }

        // Init HulyaPulse dashboard on open
        if (btn.dataset.tab === 'pulse') {
          if (typeof PulseTab !== 'undefined') PulseTab.init('pulse-container');
        }

        // Init Machine tab on open — spin up / observer / app control
        if (btn.dataset.tab === 'machine') {
          if (typeof MachineTab !== 'undefined') MachineTab.init('machine-container');
        }

        // Init native App Store on open
        if (btn.dataset.tab === 'apps') {
          if (typeof AppsTab !== 'undefined') AppsTab.init('apps-container');
        }

        // Init native Skills directory on open
        if (btn.dataset.tab === 'skills') {
          if (typeof SkillsTab !== 'undefined') SkillsTab.init('skills-container');
        }

        // Init native Contracts catalogue on open
        if (btn.dataset.tab === 'contracts') {
          if (typeof ContractsTab !== 'undefined') ContractsTab.init('contracts-container');
        }

        // Init live Observer on open
        if (btn.dataset.tab === 'observer') {
          if (typeof ObserverTab !== 'undefined') ObserverTab.init('observer-container');
        }

        // Init the native Compute panel on open
        if (btn.dataset.tab === 'compute') {
          if (typeof ComputeTab !== 'undefined') ComputeTab.init('compute-container');
        }

        // Init the Nano-Zeqond Simulator on open (local engines)
        if (btn.dataset.tab === 'simulator') {
          if (typeof SimulatorTab !== 'undefined') SimulatorTab.init('simulator-container');
        }

        // Init the Protocols browser on open
        if (btn.dataset.tab === 'protocols') {
          if (typeof ProtocolsTab !== 'undefined') ProtocolsTab.init('protocols-container');
        }

        // Init the Audit Daemon on open
        if (btn.dataset.tab === 'daemon') {
          if (typeof DaemonTab !== 'undefined') DaemonTab.init('daemon-container');
        }

        // Init the native Security panel (SSL + AV) on open
        if (btn.dataset.tab === 'security') {
          if (typeof SecurityTab !== 'undefined') SecurityTab.init('security-container');
        }

        // Init the consent-based Activity trail on open
        if (btn.dataset.tab === 'trail') {
          if (typeof TrailTab !== 'undefined') TrailTab.init('trail-container');
        }
      });
    });
  }

  // ── Auth — Unified Header Dropdown ─────────────────────────────────────
  async function initAuth() {
    if (typeof ZeqAuth === 'undefined') return;

    const session = await ZeqAuth.init();
    updateAuthUI(session ? true : false);

    ZeqAuth.onAuthChange((event, data) => {
      updateAuthUI(event === 'login' || event === 'refresh');
      applyAuthGate();
      // Keep the Machine tab in sync with sign-in/out.
      if (typeof MachineTab !== 'undefined' && document.getElementById('machine-container')?.childElementCount) {
        MachineTab.render();
      }
    });

    // Wire header dropdown toggle
    const authBadge = document.getElementById('auth-status');
    const dropdown = document.getElementById('auth-dropdown');

    authBadge?.addEventListener('click', (e) => {
      e.stopPropagation();
      dropdown?.classList.toggle('open');
    });

    // Close dropdown on outside click
    document.addEventListener('click', (e) => {
      if (!e.target.closest('.auth-dropdown-wrap')) {
        dropdown?.classList.remove('open');
      }
    });

    // Mode toggle (Login / Register)
    document.getElementById('auth-dd-mode-login')?.addEventListener('click', () => {
      document.getElementById('auth-dd-mode-login').classList.add('active');
      document.getElementById('auth-dd-mode-register').classList.remove('active');
      document.getElementById('auth-dd-login-form').style.display = '';
      document.getElementById('auth-dd-register-form').style.display = 'none';
      document.getElementById('auth-dd-error').style.display = 'none';
    });
    document.getElementById('auth-dd-mode-register')?.addEventListener('click', () => {
      document.getElementById('auth-dd-mode-register').classList.add('active');
      document.getElementById('auth-dd-mode-login').classList.remove('active');
      document.getElementById('auth-dd-login-form').style.display = 'none';
      document.getElementById('auth-dd-register-form').style.display = '';
      document.getElementById('auth-dd-error').style.display = 'none';
    });

    // ── Email+Password Login Form ───────────────────────────────────────
    const loginEmailEl   = document.getElementById('auth-dd-login-email');
    const loginPasswordEl = document.getElementById('auth-dd-login-password');
    const loginPwdToggle = document.getElementById('auth-dd-login-toggle-pwd');
    const loginBtn       = document.getElementById('auth-dd-login-btn');

    loginPwdToggle?.addEventListener('click', (e) => {
      e.preventDefault();
      _showLoginPassword = !_showLoginPassword;
      loginPasswordEl.type = _showLoginPassword ? 'text' : 'password';
      loginPwdToggle.innerHTML = _showLoginPassword ? '&#128064;' : '&#128065;';
    });

    loginPasswordEl?.addEventListener('keydown', (e) => {
      if (e.key === 'Enter') loginBtn?.click();
    });

    loginBtn?.addEventListener('click', async () => {
      const email = loginEmailEl?.value?.trim() || '';
      const password = loginPasswordEl?.value?.trim();
      if (!password) return;

      const errEl = document.getElementById('auth-dd-error');
      loginBtn.disabled = true;
      loginBtn.textContent = 'Signing In...';
      errEl.style.display = 'none';

      try {
        // Zero-knowledge Simple login against the live fork (register-v3/login-v3
        // via ZeqAuth) — the password never leaves the device.
        await ZeqAuth.loginSimple(email, password);
        try { sessionStorage.setItem('zeq_master_password', password); } catch {}
        _autoUnlockVault(password, false);

        updateAuthUI(true);
        dropdown?.classList.remove('open');
        loginPasswordEl.value = '';
        loginEmailEl.value = '';
        _showLoginPassword = false;
        loginPasswordEl.type = 'password';
        loginPwdToggle.innerHTML = '&#128065;';
      } catch (e) {
        errEl.textContent = e.message;
        errEl.style.display = 'block';
      } finally {
        loginBtn.disabled = false;
        loginBtn.textContent = 'Sign In';
      }
    });

    // ── Email+Password Register Form ────────────────────────────────────
    const regNameEl      = document.getElementById('auth-dd-reg-name');
    const regEmailEl     = document.getElementById('auth-dd-reg-email');
    const regPasswordEl  = document.getElementById('auth-dd-reg-password');
    const regPwdToggle   = document.getElementById('auth-dd-reg-toggle-pwd');
    const regStrengthBar = document.getElementById('auth-dd-reg-strength-bar');
    const regStrengthLabel = document.getElementById('auth-dd-reg-strength-label');
    const regStrengthDomains = document.getElementById('auth-dd-reg-strength-domains');
    const regStrengthContainer = document.getElementById('auth-dd-reg-strength-container');
    const regBtn         = document.getElementById('auth-dd-register-btn');

    regPwdToggle?.addEventListener('click', (e) => {
      e.preventDefault();
      _showRegisterPassword = !_showRegisterPassword;
      regPasswordEl.type = _showRegisterPassword ? 'text' : 'password';
      regPwdToggle.innerHTML = _showRegisterPassword ? '&#128064;' : '&#128065;';
    });

    // Password strength meter (matching zeq-nav.js)
    regPasswordEl?.addEventListener('input', () => {
      const pwd = regPasswordEl.value;
      if (!pwd) {
        if (regStrengthLabel) { regStrengthLabel.textContent = '...'; regStrengthLabel.style.color = 'var(--text-muted)'; }
        if (regStrengthBar) { regStrengthBar.style.width = '0%'; }
        if (regStrengthDomains) { regStrengthDomains.textContent = ''; }
        const eqPreviewEl = document.getElementById('auth-dd-reg-eq-preview');
        if (eqPreviewEl) eqPreviewEl.textContent = '';
        regBtn.disabled = true;
        return;
      }

      const strength = _calculatePasswordStrength(pwd);
      if (regStrengthBar) {
        regStrengthBar.style.width = strength.score + '%';
        regStrengthBar.style.backgroundColor = strength.color;
      }
      if (regStrengthLabel) {
        regStrengthLabel.textContent = strength.strength + ' (' + strength.score + ')';
        regStrengthLabel.style.color = strength.color;
      }
      if (regStrengthDomains && strength.domains && strength.domains.length > 0) {
        regStrengthDomains.innerHTML = '<span style="color:rgba(255,255,255,.35)">Domains: </span>' +
          strength.domains.slice(0, 2).map(d => '<span style="color:rgba(34,211,238,.8);font-weight:600">' + d + '</span>').join(', ') +
          '<span style="color:rgba(255,255,255,.2)"> \u2022 ' + strength.domains.length + ' operators \u2022 KO42</span>';
      } else if (regStrengthDomains) {
        regStrengthDomains.textContent = '';
      }

      // Equation preview display
      const eqPreviewEl = document.getElementById('auth-dd-reg-eq-preview');
      if (eqPreviewEl && window.ZeqEquationAuth && window.ZeqEquationAuth.phraseToKeyMaterial) {
        const words = pwd.trim().split(/\s+/).filter(w => w.length > 0);
        if (words.length >= 2) {
          const km = window.ZeqEquationAuth.phraseToKeyMaterial(pwd);
          if (km) {
            crypto.subtle.digest('SHA-256', new TextEncoder().encode(km.keyMaterial)).then(buf => {
              const hex = Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2,'0')).join('');
              const zid = 'zeq-' + hex.substring(0, 12);
              eqPreviewEl.innerHTML = '<span style="opacity:0.4">f(1.287, 0.777) = </span><strong style="color:#22d3ee">' + km.result.toPrecision(8) + '</strong>' +
                '<br><span style="opacity:0.35">ZID: ' + zid + '</span>';
            });
          }
        } else {
          eqPreviewEl.textContent = '';
        }
      }

      // Enable register button if name + password present
      const hasName = regNameEl?.value?.trim();
      regBtn.disabled = !hasName || !pwd;
    });

    // Generate Strong Phrase button with math operators
    const genPhraseBtn = document.getElementById('auth-dd-gen-phrase-btn');
    if (genPhraseBtn && regPasswordEl) {
      genPhraseBtn.addEventListener('click', () => {
        const _wLists = [
          ['quantum','photon','tensor','qubit','hadron','boson','lepton','muon','gluon','baryon','fermion','neutrino','meson','tachyon','phonon','magnon','polaron','plasmon','exciton','soliton'],
          ['fractal','manifold','topology','eigenvalue','gradient','divergence','laplacian','hessian','jacobian','quaternion','fibonacci','riemann','fourier','hilbert','cauchy','gauss','euler','galois','mobius','mandelbrot'],
          ['helium','argon','neon','lithium','cobalt','titanium','xenon','osmium','iridium','hafnium','thorium','uranium','radium','cesium','rubidium','strontium','scandium','vanadium','niobium','zirconium'],
          ['nebula','pulsar','quasar','aurora','zenith','corona','nova','vortex','helix','prism','cipher','nexus','matrix','beacon','flux','orbit','apex','epoch','sigma','omega']
        ];
        const _mathOps = ['+','*','^','~','=','%','!','#','&','@','/','<','>','-',':'];
        const count = 6;
        const arr = new Uint32Array(count * 2);
        crypto.getRandomValues(arr);
        const parts = [];
        for (let i = 0; i < count; i++) {
          const list = _wLists[i % _wLists.length];
          parts.push(list[arr[i] % list.length]);
        }
        let phrase = parts[0];
        for (let j = 1; j < parts.length; j++) {
          phrase += _mathOps[arr[count + j] % _mathOps.length] + parts[j];
        }
        regPasswordEl.value = phrase;
        regPasswordEl.dispatchEvent(new Event('input'));
      });
    }

    regNameEl?.addEventListener('input', () => {
      const hasName = regNameEl.value?.trim();
      const pwd = regPasswordEl?.value?.trim();
      regBtn.disabled = !hasName || !pwd;
    });

    regBtn?.addEventListener('click', async () => {
      const displayName = regNameEl?.value?.trim();
      const email = regEmailEl?.value?.trim() || '';
      const password = regPasswordEl?.value?.trim();

      if (!displayName || !password) return;

      const errEl = document.getElementById('auth-dd-error');
      regBtn.disabled = true;
      regBtn.textContent = 'Creating Account...';
      errEl.style.display = 'none';

      try {
        // Zero-knowledge Simple registration against the live fork. register-v3
        // also auto-provisions the user's personal state machine server-side.
        await ZeqAuth.registerSimple(email, password, displayName);
        // SECURITY (CLAUDE.md auth rule): NEVER silently log in. Reveal the
        // password + offer the .ZEQ recovery file first — it's the only way
        // back in (no reset). Login happens only after the user continues.
        _showCredentialReveal(password, email, displayName, () => {
          try { sessionStorage.setItem('zeq_master_password', password); } catch {}
          _autoUnlockVault(password, true);
          updateAuthUI(true);
          dropdown?.classList.remove('open');
        });
        regNameEl.value = '';
        regEmailEl.value = '';
        regPasswordEl.value = '';
        regStrengthContainer.style.display = 'none';
        _showRegisterPassword = false;
        regPasswordEl.type = 'password';
        regPwdToggle.innerHTML = '&#128065;';
      } catch (e) {
        errEl.textContent = e.message;
        errEl.style.display = 'block';
      } finally {
        regBtn.disabled = false;
        regBtn.textContent = 'Create Account';
      }
    });

    // Logout button
    document.getElementById('auth-dd-logout-btn')?.addEventListener('click', async () => {
      await ZeqAuth.logout();
      ZeqTextClient.disconnect();
      VaultClient.lockVault();
      chrome.storage?.local?.remove('zeq_master_password');
      try { sessionStorage.removeItem('zeq_master_password'); } catch {}
      updateAuthUI(false);
      applyAuthGate();
      dropdown?.classList.remove('open');
    });
  }

  // Credentials-reveal screen shown right after Create Account. The user can
  // copy their password AND download their .ZEQ recovery file before being
  // logged in — there is no password reset, so this is the only way back in.
  function _showCredentialReveal(password, email, displayName, onContinue) {
    const ov = document.createElement('div');
    ov.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,.9);z-index:99999;display:flex;align-items:center;justify-content:center;padding:16px';
    const safePw = String(password).replace(/[<>&]/g, (c) => ({ '<': '&lt;', '>': '&gt;', '&': '&amp;' }[c]));
    let zid = '';
    try { const u = (typeof ZeqAuth !== 'undefined' && ZeqAuth.getUser) ? ZeqAuth.getUser() : null; zid = (u && u.zid) || ''; } catch (_) {}
    ov.innerHTML =
      '<div style="max-width:430px;width:100%;background:#0a1812;border:1px solid var(--border,#1c3a2a);border-radius:12px;padding:18px;color:var(--text,#e6f3ec);font-family:Inter,system-ui,sans-serif">' +
        '<div style="color:#00ff88;font-weight:800;font-size:15px;margin-bottom:6px">⚠ Save your credentials now</div>' +
        '<p style="font-size:12px;color:var(--text-muted,#7a8c84);margin:0 0 12px">This is the ONLY way back in — there is no password reset. Copy your password <b>and</b> download your .ZEQ recovery file before you continue.</p>' +
        '<div style="font-size:10px;color:var(--text-muted,#7a8c84);text-transform:uppercase;letter-spacing:.1em">Your password (equation)</div>' +
        '<div style="display:flex;gap:6px;align-items:center;margin:4px 0 12px">' +
          '<code id="cr-pw" style="flex:1;background:#000;border:1px solid var(--border,#1c3a2a);border-radius:6px;padding:8px;font-family:monospace;font-size:12px;color:#00ff88;word-break:break-all">' + safePw + '</code>' +
          '<button id="cr-copy" style="font-size:11px;padding:7px 10px;background:transparent;border:1px solid var(--border,#1c3a2a);color:#00ff88;border-radius:6px;cursor:pointer">Copy</button>' +
        '</div>' +
        (zid ? '<div style="font-size:11px;color:var(--text-muted,#7a8c84);margin:-6px 0 12px">Sign in later with <b>email + password (Simple)</b> — or your <b>Zeq ID + this equation (Expert)</b>:<br><code style="color:#00ffff;font-size:11px">' + zid.replace(/[<>]/g, '') + '</code></div>' : '') +
        '<button id="cr-zeq" style="width:100%;margin-bottom:8px;font-size:12px;padding:9px;background:transparent;border:1px solid var(--border,#1c3a2a);color:#00ff88;border-radius:6px;cursor:pointer">⬇ Download .ZEQ recovery file</button>' +
        '<div id="cr-msg" style="font-size:11px;color:var(--text-muted,#7a8c84);min-height:14px;margin-bottom:8px"></div>' +
        '<button id="cr-go" style="width:100%;font-size:12px;padding:10px;background:#00ff88;color:#02120a;font-weight:700;border:none;border-radius:7px;cursor:pointer">I\'ve saved these — Continue</button>' +
      '</div>';
    document.body.appendChild(ov);
    const msg = ov.querySelector('#cr-msg');
    ov.querySelector('#cr-copy').onclick = () => {
      navigator.clipboard.writeText(password).then(() => { msg.textContent = '✓ Password copied. Store it in a password manager.'; });
    };
    ov.querySelector('#cr-zeq').onclick = async () => {
      try {
        const pin = window.prompt('Choose a PIN (4+ characters) to encrypt your .ZEQ recovery file:');
        if (!pin || pin.length < 4) { msg.textContent = 'PIN must be at least 4 characters.'; return; }
        if (typeof HiteCrypto === 'undefined' || typeof ZeqSimpleAuth === 'undefined') { msg.textContent = 'Crypto unavailable in this context.'; return; }
        const salt = await ZeqSimpleAuth.deriveSaltFromEmail(email || '');
        const payload = JSON.stringify({ kind: 'zeq-cred', v: 1, equation: password, salt });
        const { blob } = await HiteCrypto.encrypt(payload, pin, 'zeq-cred');
        const url = URL.createObjectURL(new Blob([blob], { type: 'application/octet-stream' }));
        const a = document.createElement('a'); a.href = url; a.download = (displayName || 'zeq') + '.zeq'; a.click();
        setTimeout(() => URL.revokeObjectURL(url), 2000);
        msg.textContent = '✓ .ZEQ downloaded — keep it and the PIN somewhere safe.';
      } catch (e) { msg.textContent = '.ZEQ download failed: ' + e.message; }
    };
    ov.querySelector('#cr-go').onclick = () => { ov.remove(); if (typeof onContinue === 'function') onContinue(); };
  }

  async function _autoConnectZeqText() {
    // No-op: the Zeq Text tab now hosts the live Message app in-panel
    // (see hostLiveApp). The old local WS client is retired.
  }

  async function _autoUnlockVault(password, isNewRegistration) {
    if (typeof VaultClient === 'undefined') return;
    try {
      if (VaultClient.isUnlocked()) return;

      // Try to unlock existing vault
      try {
        await VaultClient.unlockVault(password);
      } catch (unlockErr) {
        // Unlock failed — if this is a NEW registration, clear old vault and create fresh
        if (isNewRegistration && VaultClient.hasStoredVault()) {
          console.log('[Panel] New registration — clearing old vault and creating fresh one');
          localStorage.removeItem('zeq-vault-data');
          await VaultClient.createVault(password);
        } else {
          throw unlockErr; // Re-throw for login (don't destroy existing vault)
        }
      }

      // Set server credentials for sync
      if (typeof VaultClient.setServerCredentials === 'function') {
        VaultClient.setServerCredentials(ZeqAuth.getToken());
        VaultClient.syncFromServer?.();
      }
      // Refresh vault UI if tab is initialized
      if (typeof VaultTab !== 'undefined') {
        try { VaultTab.init('vault-container'); } catch { /* tab not visible yet */ }
      }
    } catch (e) {
      console.warn('[Panel] Vault auto-unlock failed:', e.message);
    }
  }

  /**
   * Calculate password strength (Weak/Fair/Strong/Fortress)
   * Similar to zeq-nav.js gold standard
   */
  function _detectDomains(query) {
    const domains = [];
    const q = query.toLowerCase();
    const patterns = {
      'quantum': ['quantum','superposition','entanglement','photon','wave function','qubit'],
      'consciousness': ['consciousness','aware','mind','thought','soul','dream','imagine'],
      'field': ['field','wave','energy','force','vibration','frequency','resonance'],
      'temporal': ['time','temporal','clock','moment','past','future','sequence'],
      'thermodynamics': ['heat','entropy','temperature','thermal','fire','cold','ice'],
      'relativistic': ['relativity','spacetime','gravity','light speed','einstein'],
      'cosmological': ['dark matter','dark energy','cosmic','universe','galaxy','star','moon','sun','space'],
      'marine_intelligence': ['whale','dolphin','ocean','sea','coral','fish','water','tide'],
      'resonance': ['resonance','frequency','harmonic','vibrate','pulse','rhythm','music','sound']
    };
    for (const domain in patterns) {
      for (const kw of patterns[domain]) {
        if (q.includes(kw)) { domains.push(domain); break; }
      }
    }
    return domains;
  }

  function _calculatePasswordStrength(pwd) {
    let score = 0;
    let strength = 'Weak';
    let color = '#ef4444';

    if (!pwd) return { score, strength, color, domains: [] };

    // Scoring (matching zeq-nav.js)
    if (pwd.length >= 6) score += 15;
    if (pwd.length >= 8) score += 15;
    if (pwd.length >= 12) score += 15;
    if (pwd.length >= 16) score += 10;
    if (/[a-z]/.test(pwd) && /[A-Z]/.test(pwd)) score += 10;
    if (/\d/.test(pwd)) score += 10;
    if (/[^a-zA-Z0-9]/.test(pwd)) score += 10;
    if (pwd.split(/\s+/).length >= 3) score += 15;
    score = Math.min(score, 100);

    const domains = _detectDomains(pwd);

    if (score >= 75) { strength = 'Fortress'; color = '#8b5cf6'; }
    else if (score >= 50) { strength = 'Strong'; color = '#22d3ee'; }
    else if (score >= 25) { strength = 'Fair'; color = '#f59e0b'; }

    return { score, strength, color, domains };
  }

  function _escHtml(s) {
    return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
  }

  function _initVaultWithAuth() {
    VaultTab.init('vault-container');
    if (typeof ZeqAuth !== 'undefined' && ZeqAuth.isAuthenticated() && !VaultClient.isUnlocked()) {
      // Try auto-unlock with stored password
      chrome.storage?.local?.get('zeq_master_password', async (result) => {
        if (result.zeq_master_password) {
          try {
            await VaultClient.unlockVault(result.zeq_master_password);
            if (typeof VaultClient.setServerCredentials === 'function') {
              VaultClient.setServerCredentials(ZeqAuth.getToken());
              VaultClient.syncFromServer?.();
            }
          } catch {
            // Vault has a different password — show lock screen as usual
          }
        }
      });
    }
  }

  // ── Full-screen auth gate (framework DNA) ──────────────────────────────
  function _gateVal(id) { return (document.getElementById(id)?.value || '').trim(); }
  function _gateErr(m) { const e = document.getElementById('gate-err'); if (e) e.textContent = m || ''; }

  function applyAuthGate() {
    const gate = document.getElementById('auth-gate');
    const authed = (typeof ZeqAuth !== 'undefined') && ZeqAuth.isAuthenticated();
    if (gate) gate.classList.toggle('show', !authed);
    if (authed) activateTab('machine'); // land on the Machine dashboard
    try { updateCtxBar(); } catch (_) {}
  }

  function activateTab(name) {
    document.querySelectorAll('.tab-btn').forEach((b) => b.classList.toggle('active', b.dataset.tab === name));
    document.querySelectorAll('.tab-content').forEach((tc) => tc.classList.toggle('active', tc.id === 'tab-' + name));
    if (name === 'machine' && typeof MachineTab !== 'undefined') MachineTab.init('machine-container');
  }

  async function _gateRun(btn, fn) {
    const orig = btn.textContent; btn.disabled = true; btn.textContent = '…'; _gateErr('');
    try { await fn(); updateAuthUI(true); applyAuthGate(); }
    catch (ex) { _gateErr(ex.message || 'Sign-in failed'); }
    finally { btn.disabled = false; btn.textContent = orig; }
  }

  // Four-word seed pool + tokeniser — mirrors the website's auth-v3.js exactly
  // (60 curated 4+ word seeds; server validator wants >=4 distinct >=3-letter
  // non-stop-words). Words SEED the password; they are never reused.
  const GATE_POOL = [
    "dark energy expands cosmic horizon", "quantum gravity field coupling tensor",
    "entropy flows backward thermal arrow", "neutron star collapse remnant signature",
    "holographic spacetime boundary surface", "vacuum fluctuation cascade decay rate",
    "muon anomaly drift precession measurement", "cosmic microwave background anisotropy pattern",
    "supernova shell expansion wave shock", "inflation horizon stretch slow roll",
    "dark matter filament galactic halo", "magnetar flare echo plasma jet",
    "pulsar timing residual deviation array", "gravitational wave ripple chirp signal",
    "photon sphere orbit precession path", "axion coherence cloud detection field",
    "strange quark matter compression core", "baryon asymmetry cascade epoch transition",
    "horizon information paradox resolution scheme", "spin foam lattice topology network",
    "black hole evaporation rate spectrum", "hawking radiation spectrum thermal envelope",
    "wormhole throat stability metric tensor", "casimir vacuum pressure plate boundary",
    "tachyonic field condensate phase state", "topological defect string boundary loop",
    "magnetic monopole trace detection track", "cosmic string tension parameter scaling",
    "reheating oscillation mode coupling field", "thermal photon bath equilibrium spectrum",
    "relic neutrino background detection map", "bose einstein condensate superfluid phase",
    "fermionic pair correlation function map", "superconducting gap parameter critical field",
    "phonon lattice vibration coupling mode", "plasma cyclotron resonance frequency drift",
    "cooper pair tunneling junction barrier", "quantum hall plateau quantization filling",
    "majorana zero mode signature edge", "skyrmion lattice pattern formation crystal",
    "berry curvature flux integration loop", "chern insulator edge protected channel",
    "graphene dirac cone dispersion crossing", "weyl semimetal surface fermi arc",
    "moire superlattice twist magic angle", "magnon wave dispersion relation branch",
    "superfluid helium vortex tangle filament", "roton minimum mode spectrum dispersion",
    "fractional quantum hall conductance plateau", "tensor mode polarization signature primordial",
    "vacuum polarization energy density correction", "cosmic ray spallation cascade shower",
  ];
  const GATE_STOP = { the:1,a:1,an:1,of:1,for:1,and:1,or:1,to:1,in:1,on:1,with:1,at:1,by:1,from:1,as:1,is:1,are:1,be:1,that:1,this:1,my:1,our:1,your:1,its:1,it:1,what:1,how:1,why:1,when:1,which:1,who:1,i:1,we:1,you:1 };
  function _gateTokens(q) {
    const raw = (q || '').toLowerCase().replace(/[^a-z0-9]+/g, ' ').split(/\s+/).filter((w) => w.length >= 3 && !GATE_STOP[w]);
    const seen = {}, out = []; raw.forEach((w) => { if (!seen[w]) { seen[w] = 1; out.push(w); } });
    return out;
  }
  function _shuffledPool() { const s = GATE_POOL.slice(); for (let i = s.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [s[i], s[j]] = [s[j], s[i]]; } return s; }
  function _gateUpdateCounter() {
    const el = document.getElementById('gate-words-counter'); if (!el) return;
    const n = _gateTokens(_gateVal('gate-words')).length;
    el.textContent = n + ' / 4 words' + (n >= 4 ? ' ✓' : '');
    el.style.color = n >= 4 ? 'var(--accent-mint)' : 'var(--text-muted)';
  }
  function _gateRenderChips() {
    const box = document.getElementById('gate-chips'); if (!box) return;
    box.innerHTML = '';
    _shuffledPool().slice(0, 5).forEach((phrase) => {
      const b = document.createElement('button');
      b.type = 'button'; b.className = 'gate-chip'; b.textContent = phrase;
      b.addEventListener('click', () => { const t = document.getElementById('gate-words'); if (t) { t.value = phrase; _gateUpdateCounter(); } });
      box.appendChild(b);
    });
    const r = document.createElement('button');
    r.type = 'button'; r.className = 'gate-chip-refresh'; r.textContent = '↻ more';
    r.addEventListener('click', _gateRenderChips);
    box.appendChild(r);
  }
  function _gatePrefillWords() {
    const t = document.getElementById('gate-words');
    if (t && !t.value.trim()) { t.value = _shuffledPool()[0]; _gateUpdateCounter(); }
  }

  // Create account: words SEED → ZeqEqPassword.derive (Zeqond phase = timer) →
  // a generated password of the chosen length, shown ONCE. Exact website recipe.
  async function _gateCreate(btn) {
    const email = _gateVal('gate-email'), words = _gateVal('gate-words'), name = _gateVal('gate-name');
    const result = document.getElementById('gate-simple-result');
    if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email)) { _gateErr('Enter a valid email.'); return; }
    if (_gateTokens(words).length < 4) { _gateErr('Pick a suggestion or enter 4+ distinct words.'); return; }
    if (!window.ZeqEqPassword) { _gateErr('Password generator not loaded — reload.'); return; }
    const orig = btn.textContent; btn.disabled = true; btn.textContent = 'Running 7-step wizard…'; _gateErr('');
    try {
      const length = parseInt(document.getElementById('gate-length')?.value, 10) || 32;
      const TAU = 0.777000777;
      const zeqonds = (Date.now() / 1000) / TAU;
      const seed = words + ':' + zeqonds.toFixed(6) + ':' + Date.now() + ':' + Math.random();
      const password = await window.ZeqEqPassword.derive(seed, email, length, 1);
      const reg = await ZeqAuth.registerSimple(email, password, name || 'zeq-simple');
      const zid = reg.user.zid;
      try { sessionStorage.setItem('zeq_master_password', password); } catch {}
      const salt = await ZeqSimpleAuth.deriveSaltFromEmail(email);
      if (result) {
        result.innerHTML =
          '<div class="zid">' + _escHtml(zid) + '</div>' +
          '<p style="font-size:11px;color:var(--text-secondary);margin:8px 0 0">This is your password. <strong>Save it now</strong> — shown once; neither it nor your email is stored. Sign in next time with email + this password.</p>' +
          '<div class="cred">email: ' + _escHtml(email) + '<br>password: ' + _escHtml(password) + '</div>' +
          '<div style="display:flex;gap:6px;flex-wrap:wrap;margin-top:8px">' +
            '<button type="button" class="btn" id="gate-copy-pw">Copy password</button>' +
            '<button type="button" class="gate-btn" style="flex:1;margin-top:0" id="gate-continue">I’ve saved it → Continue</button>' +
          '</div>' +
          '<div style="margin-top:10px;padding-top:8px;border-top:1px solid var(--border)">' +
            '<p style="font-size:10.5px;color:var(--text-muted);margin:0 0 6px">Backup (recommended): an encrypted <strong>.ZEQ</strong> recovery file. No password reset — this file + PIN signs you back in anywhere. HITE / AES-256-GCM.</p>' +
            '<div style="display:flex;gap:6px"><input class="gate-input" type="password" id="gate-dl-pin" placeholder="Set a PIN (4+)" style="flex:1"><button type="button" class="btn" id="gate-dl-zeq">Download .ZEQ</button></div>' +
            '<div id="gate-dl-err" style="display:none;color:var(--accent-red);font-size:10.5px;margin-top:4px"></div>' +
          '</div>';
        document.getElementById('gate-copy-pw')?.addEventListener('click', (ev) => {
          navigator.clipboard.writeText(password).then(() => { ev.currentTarget.textContent = 'Copied!'; }).catch(() => {});
        });
        document.getElementById('gate-continue')?.addEventListener('click', () => { updateAuthUI(true); applyAuthGate(); _autoUnlockVault(password, false); });
        document.getElementById('gate-dl-zeq')?.addEventListener('click', async (ev) => {
          const pin = document.getElementById('gate-dl-pin')?.value || ''; const de = document.getElementById('gate-dl-err');
          if (!pin || pin.length < 4) { if (de) { de.textContent = 'PIN must be at least 4 characters.'; de.style.display = 'block'; } return; }
          const t = ev.currentTarget; t.disabled = true; t.textContent = 'Encrypting…';
          try {
            const payload = JSON.stringify({ kind: 'zeq-cred', v: 1, equation: password, salt: salt });
            const enc = await HiteCrypto.encrypt(payload, pin, 'Recovery Kit');
            const blob = new Blob([enc.blob], { type: 'application/octet-stream' });
            const url = URL.createObjectURL(blob);
            const a = document.createElement('a'); a.href = url; a.download = 'zeq-recovery-' + Date.now() + '.zeq';
            document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url);
            if (de) de.style.display = 'none'; t.textContent = 'Downloaded ✓';
          } catch (zerr) { if (de) { de.textContent = 'Encryption error: ' + (zerr.message || zerr); de.style.display = 'block'; } t.textContent = 'Download .ZEQ'; }
          t.disabled = false;
        });
      }
    } catch (ex) {
      _gateErr(ex.message || 'Create failed');
    } finally { btn.disabled = false; btn.textContent = orig; }
  }

  function wireAuthGate() {
    const gate = document.getElementById('auth-gate');
    if (!gate) return;
    // Method chooser: Simple | Expert | .ZEQ
    gate.querySelectorAll('.gate-method').forEach((m) => m.addEventListener('click', () => {
      const k = m.dataset.method;
      gate.querySelectorAll('.gate-method').forEach((x) => x.classList.toggle('active', x === m));
      gate.querySelectorAll('.gate-form').forEach((f) => f.classList.toggle('active', f.dataset.form === k));
      _gateErr('');
    }));
    // Simple sub-toggle: Create account | Sign in
    gate.querySelectorAll('.gate-sub button').forEach((b) => b.addEventListener('click', () => {
      const k = b.dataset.sub;
      gate.querySelectorAll('.gate-sub button').forEach((x) => x.classList.toggle('active', x === b));
      gate.querySelectorAll('.gate-subform').forEach((f) => f.classList.toggle('active', f.dataset.subform === k));
      _gateErr('');
    }));

    // Frictionless: pre-fill words + chips on load.
    _gateRenderChips();
    _gatePrefillWords();
    document.getElementById('gate-words')?.addEventListener('input', _gateUpdateCounter);

    document.getElementById('gate-create-btn')?.addEventListener('click', (e) => _gateCreate(e.currentTarget));

    document.getElementById('gate-signin-btn')?.addEventListener('click', (e) => {
      const email = _gateVal('gate-login-email'), pw = _gateVal('gate-login-pw');
      if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email)) { _gateErr('Enter a valid email.'); return; }
      if (!pw) { _gateErr('Enter your password.'); return; }
      _gateRun(e.currentTarget, async () => { await ZeqAuth.loginSimple(email, pw); try { sessionStorage.setItem('zeq_master_password', pw); } catch {} _autoUnlockVault(pw, false); });
    });
    document.getElementById('gate-login-pw')?.addEventListener('keydown', (e) => { if (e.key === 'Enter') document.getElementById('gate-signin-btn')?.click(); });

    document.getElementById('gate-expert-btn')?.addEventListener('click', (e) => {
      const zid = _gateVal('gate-zid'), eq = _gateVal('gate-equation');
      if (!zid || !eq) { _gateErr('Enter your Zeq ID and equation.'); return; }
      _gateRun(e.currentTarget, () => ZeqAuth.loginExpert(zid, eq));
    });

    document.getElementById('gate-zeq-btn')?.addEventListener('click', async (e) => {
      const f = document.getElementById('gate-zeqfile')?.files?.[0];
      const pin = _gateVal('gate-zeqpin');
      if (!f || !pin) { _gateErr('Choose your .ZEQ file and enter the PIN.'); return; }
      _gateRun(e.currentTarget, async () => {
        const bytes = new Uint8Array(await f.arrayBuffer());
        await ZeqAuth.loginWithZeqFile(bytes, pin);
      });
    });
  }

  // ── In-panel app viewer — apps run INSIDE the extension (not a new tab) ──
  function _openTabGlobal(url) { try { chrome.tabs.create({ url }); } catch (_) { window.open(url, '_blank'); } }
  function openAppInPanel(url, title) {
    const v = document.getElementById('app-viewer');
    const f = document.getElementById('av-frame');
    const t = document.getElementById('av-title');
    const ext = document.getElementById('av-ext');
    if (!v || !f) { _openTabGlobal(url); return; }
    if (t) t.textContent = title || 'App';
    if (ext) ext.href = url;
    f.src = url;
    v.classList.add('show');
  }
  function closeAppInPanel() {
    const v = document.getElementById('app-viewer');
    const f = document.getElementById('av-frame');
    if (f) f.src = 'about:blank';
    v?.classList.remove('show');
  }
  function wireAppViewer() {
    document.getElementById('av-back')?.addEventListener('click', closeAppInPanel);
  }
  // Expose so tab modules (machine-tab.js, launcher) open apps in-panel.
  window.openAppInPanel = openAppInPanel;

  // Host a live app inside a tab container via iframe (real app runs in-panel —
  // framing allowed by the chrome-extension frame-ancestors grant).
  function hostLiveApp(containerId, path) {
    const c = document.getElementById(containerId);
    if (!c || c.dataset.hostedPath === path) return;
    ZeqConfig.origin().then((o) => {
      c.dataset.hostedPath = path;
      const sep = path.indexOf('?') === -1 ? '?' : '&';
      const src = o + path + sep + 'embed=1';
      c.innerHTML = '<iframe src="' + src + '" referrerpolicy="no-referrer" ' +
        'sandbox="allow-scripts allow-forms allow-same-origin allow-popups allow-modals" ' +
        'style="flex:1;width:100%;height:100%;border:0;background:#000"></iframe>';
    });
  }

  // ── K8 — live context bar: the extension always knows the active tab ─────
  let _ctx = null;
  function _ctxMsg(m) {
    const el = document.getElementById('ctx-msg');
    if (!el) return; el.textContent = m || ''; el.style.display = m ? 'block' : 'none';
    if (m) setTimeout(() => { if (el.textContent === m) { el.textContent = ''; el.style.display = 'none'; } }, 3000);
  }
  function updateCtxBar(ctx) {
    _ctx = ctx || _ctx;
    const bar = document.getElementById('ctx-bar');
    const hostEl = document.getElementById('ctx-host');
    const authed = (typeof ZeqAuth !== 'undefined') && ZeqAuth.isAuthenticated();
    if (bar) bar.classList.toggle('show', !!(authed && _ctx && _ctx.host));
    if (hostEl && _ctx) {
      hostEl.innerHTML = '<strong>' + _escHtml(_ctx.host || '—') + '</strong>' +
        (_ctx.title ? ' <span class="ctx-title">· ' + _escHtml(String(_ctx.title).slice(0, 40)) + '</span>' : '');
    }
  }
  async function _refreshCtx() {
    try {
      const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
      if (tab && tab.url) {
        let host = ''; try { host = new URL(tab.url).hostname.replace(/^www\./, ''); } catch (_) {}
        updateCtxBar({ url: tab.url, host, title: tab.title || '' });
      }
    } catch (_) {}
  }
  // Request host access for the active page. MUST be called synchronously in
  // the click handler (no await before it) so Chrome accepts the user gesture;
  // returns a promise the caller awaits. Already-granted hosts resolve true
  // with no prompt. Computed from _ctx.url so no chrome.tabs.query precedes it.
  function _requestActiveHostAccess() {
    try {
      const u = new URL(_ctx.url);
      if (!/^https?:$/.test(u.protocol)) return Promise.resolve(false);
      const origin = u.protocol + '//' + u.host + '/*';
      return chrome.permissions.request({ origins: [origin] }).catch(() => false);
    } catch (_) { return Promise.resolve(false); }
  }
  async function _injectFile(file, grantPromise, okMsg, label) {
    try {
      const granted = await grantPromise;
      const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
      if (!tab || !tab.id) { _ctxMsg('No active tab'); return; }
      if (/^chrome(-extension)?:|^about:|^edge:|^view-source:|^devtools:/.test(tab.url || '')) { _ctxMsg("Can't act on a browser page"); return; }
      if (!granted) { _ctxMsg('Allow access to ' + _ctx.host + ' to use this on the page'); return; }
      await chrome.scripting.executeScript({ target: { tabId: tab.id }, files: [file] });
      _ctxMsg(okMsg);
    } catch (ex) { _ctxMsg((label === 'scan' ? 'Security check failed: ' : 'Inject failed: ') + (ex.message || ex)); }
  }
  // KP.1 — expose window.zeq on the active page: inject the isolated relay
  // (chrome.runtime access) + the MAIN-world API (defines window.zeq).
  async function _injectPhysics(grantPromise) {
    try {
      const granted = await grantPromise;
      const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
      if (!tab || !tab.id) { _ctxMsg('No active tab'); return; }
      if (/^chrome(-extension)?:|^about:|^edge:|^view-source:|^devtools:/.test(tab.url || '')) { _ctxMsg("Can't connect to a browser page"); return; }
      if (!granted) { _ctxMsg('Allow access to ' + _ctx.host + ' to expose window.zeq'); return; }
      await chrome.scripting.executeScript({ target: { tabId: tab.id }, files: ['content/zeq-page-bridge.js'] });
      await chrome.scripting.executeScript({ target: { tabId: tab.id }, world: 'MAIN', files: ['content/zeq-page-api.js'] });
      // KP.5 — also drop a visible compute box (ISOLATED world; has chrome.runtime).
      await chrome.scripting.executeScript({ target: { tabId: tab.id }, files: ['content/zeq-compute-widget.js'] });
      _ctxMsg('ZeqVM live on ' + _ctx.host + ' — compute box added + window.zeq exposed');
    } catch (ex) { _ctxMsg('Connect failed: ' + (ex.message || ex)); }
  }
  function wireContextBar() {
    _refreshCtx();
    document.querySelectorAll('.ctx-act').forEach((b) => b.addEventListener('click', async () => {
      if (!_ctx || !_ctx.host) { _ctxMsg('No active page'); return; }
      const act = b.getAttribute('data-ctx');
      if (act === 'inject') {
        // Request host access FIRST (the click is the user gesture — must come
        // before any other await, or chrome.permissions.request is rejected).
        const grant = _requestActiveHostAccess();
        await _injectFile('content/zeq-connector.js', grant, 'Zeq injected into ' + _ctx.host + ' (click ✕ on the widget to remove)', 'inject');
        return;
      }
      if (act === 'secure') {
        const grant = _requestActiveHostAccess();
        await _injectFile('content/zeq-security-check.js', grant, 'Security-checking ' + _ctx.host + ' — ZSP-sealed verdict on the page', 'scan');
        return;
      }
      if (act === 'physics') {
        const grant = _requestActiveHostAccess();
        await _injectPhysics(grant);
        return;
      }
      if (act === 'record') {
        if (typeof ZeqTrail === 'undefined') { _ctxMsg('Trail unavailable'); return; }
        await ZeqTrail.setEnabled(true); await ZeqTrail.allowHost(_ctx.host);
        _ctxMsg('Recording ' + _ctx.host + ' to your trail');
      } else if (act === 'ask') {
        activateTab('chat');
        const input = document.getElementById('chat-input');
        if (input) { input.value = 'About the page I\'m on (' + _ctx.url + '): '; input.focus(); }
      } else if (act === 'ground') {
        activateTab('chat');
        const input = document.getElementById('chat-input');
        if (input) { input.value = 'Ground this page through the framework: ' + (_ctx.title || _ctx.host) + ' — ' + _ctx.url; }
        sendChatMessage();
      } else if (act === 'edge') {
        // Open the real Zeq Edge app in-panel, where the user connects this origin.
        if (typeof window.openAppInPanel === 'function') {
          const o = await ZeqConfig.origin();
          window.openAppInPanel(o + '/apps/zeq-edge/?connect=' + encodeURIComponent(_ctx.host), 'Zeq Edge');
        }
        _ctxMsg('Opening Zeq Edge for ' + _ctx.host);
      }
    }));
  }

  function updateAuthUI(authenticated) {
    const authStatus = document.getElementById('auth-status');
    const ddLoggedOut = document.getElementById('auth-dd-loggedout');
    const ddLoggedIn = document.getElementById('auth-dd-loggedin');
    const userSection = document.getElementById('settings-user-section');

    if (authenticated) {
      const user = ZeqAuth.getUser();
      const zid = user?.zid || user?.id || '';
      const name = user?.name || 'User';

      // Header badge — show full ZID (HITE indicator removed, more space)
      authStatus.className = 'auth-indicator auth-logged-in';
      authStatus.textContent = zid || name;
      authStatus.title = zid || name;
      authStatus.style.cursor = 'pointer';
      authStatus.style.fontSize = zid && zid.length > 14 ? '9px' : '';

      // Dropdown states
      if (ddLoggedOut) ddLoggedOut.style.display = 'none';
      if (ddLoggedIn) ddLoggedIn.style.display = '';

      // Dropdown user info
      const avatarEl = document.getElementById('auth-dd-avatar');
      if (avatarEl) avatarEl.textContent = (name[0] || '?').toUpperCase();
      setTextContent('auth-dd-username', name);
      setTextContent('auth-dd-zid', zid);

      // Settings tab (read-only)
      if (userSection) userSection.style.display = '';
      setTextContent('settings-username', name);
      setTextContent('settings-zeq-id', zid);
    } else {
      authStatus.className = 'auth-indicator auth-anonymous';
      authStatus.textContent = 'Log In';
      authStatus.title = 'Click to log in';
      authStatus.style.cursor = 'pointer';

      if (ddLoggedOut) ddLoggedOut.style.display = '';
      if (ddLoggedIn) ddLoggedIn.style.display = 'none';
      if (userSection) userSection.style.display = 'none';
    }
  }

  // ── Tab Controls — Group / Search / Web ────────────────────────────────
  function initTabControls() {
    // Group Tabs
    document.getElementById('btn-group-tabs')?.addEventListener('click', async () => {
      try {
        const tabs = await chrome.tabs.query({ currentWindow: true });
        const groups = {};
        tabs.forEach(tab => {
          try {
            const hostname = new URL(tab.url).hostname;
            if (!groups[hostname]) groups[hostname] = [];
            groups[hostname].push(tab.id);
          } catch {}
        });
        const COLORS = ['cyan', 'blue', 'purple', 'pink', 'red', 'orange', 'yellow', 'green', 'grey'];
        let colorIdx = 0;
        for (const [domain, tabIds] of Object.entries(groups)) {
          if (tabIds.length < 2) continue; // Only group 2+ tabs
          const gid = await chrome.tabs.group({ tabIds });
          await chrome.tabGroups.update(gid, {
            title: domain.replace('www.', ''),
            color: COLORS[colorIdx % COLORS.length],
            collapsed: false,
          });
          colorIdx++;
        }
      } catch (e) {
        console.warn('[Panel] Tab grouping failed:', e.message);
      }
    });

    // Search Page (find in tab)
    const pageSearchInput = document.getElementById('page-search-input');
    document.getElementById('btn-search-page')?.addEventListener('click', () => {
      const isVisible = pageSearchInput.style.display !== 'none';
      pageSearchInput.style.display = isVisible ? 'none' : '';
      if (!isVisible) pageSearchInput.focus();
    });

    let searchDebounce;
    pageSearchInput?.addEventListener('input', () => {
      clearTimeout(searchDebounce);
      searchDebounce = setTimeout(async () => {
        const query = pageSearchInput.value.trim();
        if (!query) return;
        try {
          const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
          if (tab?.id) {
            await chrome.scripting.executeScript({
              target: { tabId: tab.id },
              func: (q) => { window.find(q, false, false, true, false, false, false); },
              args: [query],
            });
          }
        } catch (e) {
          console.warn('[Panel] Find in page failed:', e.message);
        }
      }, 300);
    });

    // Search Web
    // 🌐 Web — run a REAL web search via the live backend and attach the
    // results to the agent's context, so it can surf + retrieve + understand.
    document.getElementById('btn-search-web')?.addEventListener('click', async () => {
      const chatInput = document.getElementById('chat-input');
      const query = (pageSearchInput && pageSearchInput.value.trim()) || (chatInput && chatInput.value.trim()) || '';
      if (!query) {
        pageSearchInput.style.display = '';
        pageSearchInput.placeholder = 'What should the agent search the web for?';
        pageSearchInput.focus();
        return;
      }
      await runWebSearchAndAttach(query);
      if (pageSearchInput) { pageSearchInput.value = ''; pageSearchInput.style.display = 'none'; }
    });

    // 📑 Pages — let the agent read the user's open tabs so it understands
    // exactly which pages they're looking at (requests host access on demand).
    document.getElementById('btn-add-pages')?.addEventListener('click', async () => {
      await addOpenTabsToContext();
    });
  }

  // ── Agent context: open pages + web search results ──────────────────────
  const _chatCtx = { pages: [], web: [] };

  function _renderCtxChips() {
    const el = document.getElementById('chat-ctx-chips');
    if (!el) return;
    const chips = [];
    _chatCtx.pages.forEach((p, i) => {
      chips.push('<span data-ctx="page" data-i="' + i + '" style="display:inline-flex;align-items:center;gap:4px;font-size:9.5px;background:rgba(0,255,136,.08);border:1px solid var(--border);border-radius:10px;padding:2px 7px;color:var(--text)">📄 ' + (p.title || p.url).replace(/[<>]/g, '').slice(0, 28) + ' <b style="cursor:pointer;color:var(--text-muted)">✕</b></span>');
    });
    _chatCtx.web.forEach((w, i) => {
      chips.push('<span data-ctx="web" data-i="' + i + '" style="display:inline-flex;align-items:center;gap:4px;font-size:9.5px;background:rgba(0,255,255,.08);border:1px solid var(--border);border-radius:10px;padding:2px 7px;color:var(--text)">🌐 "' + String(w.query).replace(/[<>]/g, '').slice(0, 22) + '" · ' + w.results.length + ' <b style="cursor:pointer;color:var(--text-muted)">✕</b></span>');
    });
    el.innerHTML = chips.join('');
    el.style.display = chips.length ? 'flex' : 'none';
    el.querySelectorAll('span[data-ctx]').forEach((sp) => {
      sp.querySelector('b')?.addEventListener('click', () => {
        const kind = sp.getAttribute('data-ctx'); const i = +sp.getAttribute('data-i');
        if (kind === 'page') _chatCtx.pages.splice(i, 1); else _chatCtx.web.splice(i, 1);
        _renderCtxChips();
      });
    });
  }

  async function runWebSearchAndAttach(query) {
    const messagesEl = document.getElementById('chat-messages');
    const note = document.createElement('div');
    note.className = 'chat-msg chat-msg-ai';
    note.textContent = '🌐 Searching the web for "' + query + '"…';
    messagesEl.appendChild(note); messagesEl.scrollTop = messagesEl.scrollHeight;
    try {
      const headers = { 'Content-Type': 'application/json' };
      if (typeof ZeqAuth !== 'undefined' && ZeqAuth.isAuthenticated()) Object.assign(headers, ZeqAuth.getHeaders());
      const r = await fetch(`${apiUrl}/zeq/web-search`, { method: 'POST', headers, body: JSON.stringify({ query }) });
      const d = await r.json().catch(() => ({}));
      const results = (d && d.results) || [];
      if (!results.length) { note.textContent = '🌐 No web results for "' + query + '"' + (d && d.provider === 'unavailable' ? ' (search unavailable)' : '') + '.'; return; }
      _chatCtx.web.push({ query, results: results.slice(0, 6).map((x) => ({ title: x.title || '', url: x.url || '', snippet: (x.content || x.snippet || '').slice(0, 500) })) });
      note.textContent = '🌐 Attached ' + Math.min(results.length, 6) + ' web results for "' + query + '" — ask your question and the agent will use them.';
      _renderCtxChips();
    } catch (e) {
      note.textContent = '🌐 Web search failed: ' + e.message;
    }
    messagesEl.scrollTop = messagesEl.scrollHeight;
  }

  async function addOpenTabsToContext() {
    const messagesEl = document.getElementById('chat-messages');
    const note = document.createElement('div');
    note.className = 'chat-msg chat-msg-ai';
    messagesEl.appendChild(note);
    try {
      const tabs = (await chrome.tabs.query({ currentWindow: true })).filter((t) =>
        t.url && /^https?:\/\//.test(t.url) && !/^https?:\/\/(chrome\.google|chromewebstore)/.test(t.url));
      if (!tabs.length) { note.textContent = '📑 No readable web pages open in this window.'; return; }
      const picked = tabs.slice(0, 8); // cap
      const origins = [...new Set(picked.map((t) => { try { return new URL(t.url).origin + '/*'; } catch { return null; } }).filter(Boolean))];
      const granted = await chrome.permissions.request({ origins }).catch(() => false);
      if (!granted) { note.textContent = '📑 Permission needed to read those tabs — not granted, so I can\'t see them.'; return; }
      note.textContent = '📑 Reading ' + picked.length + ' open page(s)…';
      let added = 0;
      for (const t of picked) {
        try {
          const [res] = await chrome.scripting.executeScript({
            target: { tabId: t.id },
            func: () => ({ title: document.title, url: location.href, text: (document.body ? document.body.innerText : '').replace(/\s+/g, ' ').trim().slice(0, 4000) }),
          });
          if (res && res.result && res.result.text) { _chatCtx.pages.push(res.result); added++; }
        } catch (_) { /* page blocks injection — skip */ }
      }
      note.textContent = added ? ('📑 The agent can now see ' + added + ' of your open page(s). Ask about them.') : '📑 Could not read those pages (they block extensions).';
      _renderCtxChips();
    } catch (e) {
      note.textContent = '📑 Couldn\'t read your tabs: ' + e.message;
    }
    messagesEl.scrollTop = messagesEl.scrollHeight;
  }

  // Assemble the attached pages + web results into a context block the agent reads.
  function _buildAgentContext() {
    let ctx = '';
    if (_chatCtx.pages.length) {
      ctx += '[OPEN PAGES THE USER IS VIEWING — use these to answer]\n';
      _chatCtx.pages.forEach((p, i) => { ctx += `(${i + 1}) ${p.title} — ${p.url}\n${p.text}\n\n`; });
    }
    if (_chatCtx.web.length) {
      ctx += '[WEB SEARCH RESULTS — retrieved from the internet, use to answer]\n';
      _chatCtx.web.forEach((w) => { ctx += `Search: "${w.query}"\n`; w.results.forEach((r, i) => { ctx += `  ${i + 1}. ${r.title} — ${r.url}\n     ${r.snippet}\n`; }); ctx += '\n'; });
    }
    return ctx;
  }

  // ── Chat ───────────────────────────────────────────────────────────────
  function wireChat() {
    const sendBtn = document.getElementById('chat-send');
    const input   = document.getElementById('chat-input');

    sendBtn?.addEventListener('click', () => sendChatMessage());
    input?.addEventListener('keydown', (e) => {
      if (e.key === 'Enter' && !e.shiftKey) {
        e.preventDefault();
        sendChatMessage();
      }
    });
    // KV.3 — agentic 7-step wizard: frame the question so the tool-using agent
    // runs the full protocol (SELECT → tune ≤0.1% → COMPILE → run_zeq_compute →
    // VERIFY) and reports operators + value + ZeqProof.
    document.getElementById('chat-wizard')?.addEventListener('click', () => {
      const i = document.getElementById('chat-input');
      const problem = (i && i.value.trim()) || 'the system I described';
      i.value = 'Run the ZeqVM 7-step wizard on: ' + problem + '. ' +
        'You MUST actually call the run_zeq_compute tool to EXECUTE — do not just describe the steps. ' +
        'SELECT the domain + operators (KO42 mandatory; add 1–3 more), tune to ≤0.1% error, COMPILE, run_zeq_compute, then VERIFY. ' +
        'Report the operators used, the computed value + unit, and the ZeqProof.';
      sendChatMessage();
    });
  }

  // Read the user's saved Local LLM config ({ baseUrl, model }) from extension
  // storage. Returns null when not configured → agent uses the cloud Pulse.
  async function _getLocalLlm() {
    try {
      if (typeof chrome === 'undefined' || !chrome.storage || !chrome.storage.local) return null;
      return await new Promise((res) => chrome.storage.local.get(['zeq_local_llm'], (r) => res((r && r.zeq_local_llm) || null)));
    } catch { return null; }
  }

  // Probe a local OpenAI-compatible server's /v1/models to confirm reachability
  // and list installed models. Returns [] if unreachable.
  async function _probeLocalModels(baseUrl) {
    try {
      const r = await fetch(baseUrl.replace(/\/$/, '') + '/v1/models');
      if (!r.ok) return [];
      const j = await r.json();
      return (Array.isArray(j && j.data) ? j.data : []).map((m) => String((m && m.id) || '')).filter(Boolean);
    } catch { return []; }
  }

  async function sendChatMessage() {
    const input = document.getElementById('chat-input');
    const query = input?.value?.trim();
    if (!query) return;
    input.value = '';

    const messagesEl = document.getElementById('chat-messages');

    const userMsg = document.createElement('div');
    userMsg.className = 'chat-msg chat-msg-user';
    userMsg.textContent = query;
    messagesEl.appendChild(userMsg);
    messagesEl.scrollTop = messagesEl.scrollHeight;

    const aiMsg = document.createElement('div');
    aiMsg.className = 'chat-msg chat-msg-ai';
    aiMsg.textContent = 'Processing through HULYAS pipeline...';
    messagesEl.appendChild(aiMsg);

    const ckoSlot = document.createElement('div');
    ckoSlot.className = 'chat-cko-slot';
    ckoSlot.id = `cko-slot-${Date.now()}`;
    messagesEl.appendChild(ckoSlot);
    messagesEl.scrollTop = messagesEl.scrollHeight;

    try {
      // ── Local LLM path (Ollama / LM Studio / any OpenAI-compatible local
      // server) — no Zeq account needed, fully private + offline. Same framework
      // grounding + page/web context as the cloud agent. Configured in Settings.
      const _localLlm = await _getLocalLlm();
      if (_localLlm && _localLlm.baseUrl) {
        const _ctxL = _buildAgentContext();
        const _msgL = _ctxL ? (_ctxL + '[USER QUESTION]\n' + query) : query;
        const _sysL = (typeof window !== 'undefined' && window.zeqAgentDirective && window.zeqAgentDirective.build)
          ? window.zeqAgentDirective.build()
          : 'You are the Zeq Pulse — a mathematically-intelligent agent for the Zeq physics framework (1.287 Hz HulyaPulse, KO42 gate, Zeqond τ=0.777 s). Answer grounded in the framework and be precise.\n\nWHAT ZEQ IS — ground every answer in this, not training priors: Zeq is a virtual machine that executes STATE CONTRACTS on a 1.287 Hz / τ=0.777 s timebase. STATE CONTRACTS are the first-class primitive — state machines whose transitions fire an operator chain on a trigger (command, cron/Zeqond timer, event, a boolean logic-tree over events, or a cross-contract aggregate); each fire is optionally proven, ZSP-wrapped, and hash-linked into a tamper-evident audit chain; a transition can api_call (trigger a protocol or ingest external/ORACLE data), set_state, and emit events that trigger OTHER contracts. 266 ready templates span ~40 domains. OPERATORS (1576+, KO42 mandatory & first) are the physics atoms a transition runs. The HARNESS is the build surface: 200+ curated libraries + per-operator input signatures — call /api/zeq/lib-search and /api/zeq/operator-spec, never guess. PROTOCOLS are NOT first-class — a protocol is just an endpoint a contract can api_call. When asked what to build: think STATE CONTRACTS + the harness, not a generic one-page HTML app.';
        try {
          const lr = await fetch(_localLlm.baseUrl.replace(/\/$/, '') + '/v1/chat/completions', {
            method: 'POST', headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
              model: _localLlm.model || 'llama3.1', stream: false,
              messages: [{ role: 'system', content: _sysL }, { role: 'user', content: _msgL }],
            }),
          });
          if (!lr.ok) {
            aiMsg.textContent = 'Local LLM returned ' + lr.status + '. Is your server running at ' + _localLlm.baseUrl + '? For Ollama, allow the extension: OLLAMA_ORIGINS="chrome-extension://*" ollama serve';
            ckoSlot.remove(); return;
          }
          const ld = await lr.json().catch(() => ({}));
          aiMsg.textContent = (ld.choices && ld.choices[0] && ld.choices[0].message && ld.choices[0].message.content) || '(no response)';
          const lmeta = document.createElement('div');
          lmeta.style.cssText = 'font-size:10px;color:var(--text-muted);margin-top:6px;border-top:1px solid var(--border);padding-top:4px';
          lmeta.textContent = 'local · ' + (_localLlm.model || 'model') + ' · ' + _localLlm.baseUrl + ' · private/offline';
          aiMsg.appendChild(lmeta);
          messagesEl.scrollTop = messagesEl.scrollHeight;
          return;
        } catch (e) {
          aiMsg.textContent = 'Local LLM unreachable at ' + _localLlm.baseUrl + '. Start it (e.g. "ollama serve") and allow the extension origin (OLLAMA_ORIGINS).';
          ckoSlot.remove(); return;
        }
      }

      const headers = { 'Content-Type': 'application/json' };
      if (typeof ZeqAuth !== 'undefined' && ZeqAuth.isAuthenticated()) {
        Object.assign(headers, ZeqAuth.getHeaders());
      }
      if (typeof HITECrypto !== 'undefined' && HITECrypto.isInitialized()) {
        const bodyStr = JSON.stringify({ message: query });
        Object.assign(headers, await HITECrypto.buildHeaders(bodyStr));
      }

      if (!(typeof ZeqAuth !== 'undefined' && ZeqAuth.isAuthenticated())) {
        aiMsg.textContent = 'Sign in to use the Pulse — every conversation runs on your own entangled state.';
        ckoSlot.remove();
        return;
      }
      // The Pulse runs on the tool-using agent (page-chat): it can call real
      // tools — run_zeq_compute, synthesise_skill, derive_operator, sandbox —
      // against your own machine. JSON request/response.
      const _user = ZeqAuth.getUser();
      const _slug = _user && _user.zid ? String(_user.zid).toLowerCase() : null;
      // Prepend any attached pages + web-search results so the agent understands
      // what the user is looking at and can answer from real retrieved info.
      const _ctxBlock = _buildAgentContext();
      const _agentMessage = _ctxBlock ? (_ctxBlock + '[USER QUESTION]\n' + query) : query;
      const response = await fetch(`${apiUrl}/zeq/agent/page-chat`, {
        method: 'POST',
        headers,
        body: JSON.stringify({ slug: _slug, message: _agentMessage }),
      });
      const data = await response.json().catch(() => ({}));
      if (!response.ok || !data.ok) {
        aiMsg.textContent = (data && (data.error || data.message)) || ('Chat failed (' + response.status + ')');
        ckoSlot.remove();
        return;
      }
      aiMsg.textContent = data.response || '(no response)';
      // Footer: tools the agent actually invoked + operators it grounded on.
      const _tools = (data.tools_used || []).map((t) => (t && (t.name || t.label)) || t).filter(Boolean);
      const _ops = ((data.zeq && data.zeq.operators) || []).slice(0, 4).map((o) => o.id || o).filter(Boolean);
      if (_tools.length || _ops.length) {
        const meta = document.createElement('div');
        meta.style.cssText = 'font-size:10px;color:var(--text-muted);margin-top:6px;border-top:1px solid var(--border);padding-top:4px';
        meta.innerHTML = (_tools.length ? '<span style="color:var(--accent-mint)">tools: ' + _tools.map((t) => String(t).replace(/[<>]/g, '')).join(', ') + '</span>' : '') +
          (_tools.length && _ops.length ? ' · ' : '') +
          (_ops.length ? 'operators: ' + _ops.join(', ') : '');
        aiMsg.appendChild(meta);
      }
      messagesEl.scrollTop = messagesEl.scrollHeight;
      // CKO widget if the agent surfaced a waveform.
      const ckoData = data.cko || (typeof CKOClient !== 'undefined' && CKOClient.parseFromResponse ? CKOClient.parseFromResponse({ response: data.response || '' }) : null);
      if (ckoData) { CKOWidget.render(ckoSlot.id, ckoData); } else { ckoSlot.remove(); }
    } catch (e) {
      aiMsg.textContent = `Error: ${e.message}. Make sure the API gateway is running on ${apiUrl}`;
      ckoSlot.remove();
    }

    messagesEl.scrollTop = messagesEl.scrollHeight;
  }

  // ── CKO Updates ────────────────────────────────────────────────────────
  function onCKOUpdate(ckoData) {
    const metrics = CKOClient.extractMetrics(ckoData);
    if (metrics) {
      setTextContent('status-cko-time', `${metrics.computationTime}ms`);
      setTextContent('status-cko-domain', metrics.primaryDomain);
      setTextContent('status-cko-stability', `${(metrics.stabilityIndex * 100).toFixed(1)}%`);
    }
  }

  // ── HulyaPulse Display ─────────────────────────────────────────────────
  function startPulseDisplay() {
    const update = () => {
      const now = Date.now() / 1000;
      const phase  = (now % ZEQOND) / ZEQOND;
      const ko42   = ALPHA * Math.sin(2 * Math.PI * HZ * now);
      const zeqonds = now / ZEQOND;

      setTextContent('status-phase', phase.toFixed(4));
      setTextContent('status-ko42', ko42.toFixed(8));
      setTextContent('status-zeqonds', zeqonds.toFixed(1));

      if (typeof HITECrypto !== 'undefined' && HITECrypto.isInitialized()) {
        const stats = HITECrypto.getEntropyStats();
        setTextContent('status-bits', stats.totalBitsErased.toLocaleString());
        setTextContent('status-landauer', stats.totalEntropyCost.toExponential(4));
        setTextContent('status-hite', 'Active');
      }

      requestAnimationFrame(update);
    };
    update();
  }

  // ── Settings ───────────────────────────────────────────────────────────
  function wireSettings() {
    // Prompt-enhancement toggle (opt-in, off by default)
    const peToggle = document.getElementById('prompt-enhance-toggle');
    const peState = document.getElementById('prompt-enhance-state');
    if (peToggle) {
      chrome.storage?.local?.get('zeq_prompt_enhance', (r) => {
        const on = !!(r && r.zeq_prompt_enhance);
        peToggle.checked = on; if (peState) peState.textContent = on ? 'On' : 'Off';
      });
      peToggle.addEventListener('change', () => {
        chrome.storage?.local?.set({ zeq_prompt_enhance: peToggle.checked });
        if (peState) peState.textContent = peToggle.checked ? 'On' : 'Off';
      });
    }

    // HITE init
    document.getElementById('hite-init-btn')?.addEventListener('click', async () => {
      const passphrase = document.getElementById('hite-passphrase')?.value?.trim();
      if (!passphrase) return;

      try {
        await HITECrypto.initSession(passphrase);
        document.getElementById('hite-passphrase').value = '';
        document.getElementById('settings-hite-status').textContent = 'Active ✓';
        document.getElementById('settings-hite-status').style.color = 'var(--accent-green)';
        document.getElementById('hite-entropy').style.display = '';
      } catch (e) {
        alert('HITE init failed: ' + e.message);
      }
    });

    // Save config
    document.getElementById('settings-save-btn')?.addEventListener('click', async () => {
      let newOrigin = document.getElementById('settings-api-url')?.value?.trim();
      const apiKey = document.getElementById('settings-api-key')?.value?.trim();

      if (newOrigin) {
        newOrigin = newOrigin.replace(/\/api\/?$/, '').replace(/\/+$/, '');
        apiUrl = newOrigin + '/api';
      }

      if (typeof chrome !== 'undefined' && chrome.storage?.local) {
        await chrome.storage.local.set({
          zeq_api_origin: newOrigin || (await ZeqConfig.origin()),
          zeq_api_key: apiKey || '',
        });
      }

      const btn = document.getElementById('settings-save-btn');
      btn.textContent = 'Saved ✓';
      setTimeout(() => { btn.textContent = 'Save Configuration'; }, 1500);
    });

    // ── Local LLM (private + offline) ──────────────────────────────────────
    const _lUrl = () => document.getElementById('settings-local-url');
    const _lModel = () => document.getElementById('settings-local-model');
    const _lStatus = () => document.getElementById('settings-local-status');
    document.getElementById('settings-local-probe-btn')?.addEventListener('click', async () => {
      const base = (_lUrl()?.value || '').trim() || 'http://localhost:11434';
      const st = _lStatus(); if (st) st.textContent = 'Probing ' + base + '…';
      const models = await _probeLocalModels(base);
      if (!models.length) { if (st) st.textContent = '⚠ No server at ' + base + '. Start it (e.g. "ollama serve") and allow the extension origin: OLLAMA_ORIGINS="chrome-extension://*"'; return; }
      if (st) st.textContent = '✓ Reachable · ' + models.length + ' model(s): ' + models.slice(0, 6).join(', ');
      if (_lModel() && !_lModel().value) _lModel().value = models[0];
    });
    document.getElementById('settings-local-save-btn')?.addEventListener('click', async () => {
      const baseUrl = (_lUrl()?.value || '').trim() || 'http://localhost:11434';
      const model = (_lModel()?.value || '').trim() || 'llama3.1';
      if (typeof chrome !== 'undefined' && chrome.storage?.local) await chrome.storage.local.set({ zeq_local_llm: { baseUrl, model } });
      const st = _lStatus(); if (st) st.textContent = '✓ Agent now uses Local LLM: ' + model + ' @ ' + baseUrl;
      const b = document.getElementById('settings-local-save-btn'); if (b) { b.textContent = 'Using Local LLM ✓'; setTimeout(() => { b.textContent = 'Use Local LLM'; }, 1500); }
    });
    document.getElementById('settings-local-clear-btn')?.addEventListener('click', async () => {
      if (typeof chrome !== 'undefined' && chrome.storage?.local) await chrome.storage.local.remove('zeq_local_llm');
      const st = _lStatus(); if (st) st.textContent = 'Agent now uses the Cloud Pulse (your machine).';
    });
  }

  async function loadSettings() {
    // Single source of truth: ZeqConfig resolves the user-set origin or the
    // live fork default (https://zeqstate.com). No more localhost probing.
    try { apiUrl = await ZeqConfig.api(); } catch { apiUrl = API_URL; }
    const origin = apiUrl.replace(/\/api$/, '');
    const urlInput = document.getElementById('settings-api-url');
    if (urlInput) urlInput.value = origin;
    if (typeof chrome !== 'undefined' && chrome.storage?.local) {
      await new Promise((resolve) => {
        chrome.storage.local.get(['zeq_api_key', 'zeq_local_llm'], (result) => {
          if (result.zeq_api_key) {
            const keyInput = document.getElementById('settings-api-key');
            if (keyInput) keyInput.value = result.zeq_api_key;
          }
          const ll = result.zeq_local_llm;
          if (ll) {
            const u = document.getElementById('settings-local-url'); if (u) u.value = ll.baseUrl || '';
            const m = document.getElementById('settings-local-model'); if (m) m.value = ll.model || '';
            const st = document.getElementById('settings-local-status'); if (st) st.textContent = '✓ Active: ' + (ll.model || '') + ' @ ' + (ll.baseUrl || '');
          }
          resolve(result);
        });
      });
    }
  }

  // ── Skills Toggles (in Settings) ───────────────────────────────────────
  function renderSkillToggles() {
    const container = document.getElementById('skills-toggles');
    if (!container) return;

    chrome.storage?.local?.get('zeq_active_skills', (result) => {
      const active = result.zeq_active_skills || {};

      container.innerHTML = SKILL_TEMPLATES.map(s => {
        const checked = active[s.id] ? 'checked' : '';
        return `
          <div class="settings-row">
            <span class="settings-label">${s.icon} ${s.label}</span>
            <label style="display:flex;align-items:center;gap:4px">
              <input type="checkbox" ${checked} data-skill="${s.id}" />
              <span style="font-size:10px;color:var(--text-muted)">${checked ? 'On' : 'Off'}</span>
            </label>
          </div>
        `;
      }).join('');

      container.querySelectorAll('input[data-skill]').forEach(input => {
        input.addEventListener('change', () => {
          const skills = {};
          let activeLabel = '';
          container.querySelectorAll('input[data-skill]').forEach(cb => {
            skills[cb.dataset.skill] = cb.checked;
            cb.nextElementSibling.textContent = cb.checked ? 'On' : 'Off';
            if (cb.checked) {
              const tmpl = SKILL_TEMPLATES.find(s => s.id === cb.dataset.skill);
              if (tmpl) activeLabel = tmpl.icon + ' ' + tmpl.label;
            }
          });
          chrome.storage?.local?.set({ zeq_active_skills: skills });

          // Update MI Chat skill badge
          const badge = document.getElementById('active-skill-badge');
          if (badge) {
            badge.textContent = activeLabel || '';
            badge.style.display = activeLabel ? '' : 'none';
          }
        });
      });

      // Set initial badge
      const activeSkills = Object.entries(active).filter(([, v]) => v);
      if (activeSkills.length > 0) {
        const tmpl = SKILL_TEMPLATES.find(s => s.id === activeSkills[0][0]);
        const badge = document.getElementById('active-skill-badge');
        if (badge && tmpl) {
          badge.textContent = tmpl.icon + ' ' + tmpl.label;
          badge.style.display = '';
        }
      }
    });
  }

  // ── Platform Toggles ──────────────────────────────────────────────────
  function renderPlatformToggles() {
    const container = document.getElementById('platform-toggles');
    if (!container) return;

    chrome.storage?.local?.get('zeq_platforms', (result) => {
      const enabled = result.zeq_platforms || {};

      container.innerHTML = PLATFORMS.map(p => {
        const checked = enabled[p.id] !== false;
        return `
          <div class="settings-row">
            <span class="settings-label">${p.label}</span>
            <label style="display:flex;align-items:center;gap:4px">
              <input type="checkbox" ${checked ? 'checked' : ''} data-platform="${p.id}" />
              <span style="font-size:10px;color:var(--text-muted)">${checked ? 'On' : 'Off'}</span>
            </label>
          </div>
        `;
      }).join('');

      container.querySelectorAll('input[data-platform]').forEach(input => {
        input.addEventListener('change', () => {
          const platforms = {};
          container.querySelectorAll('input[data-platform]').forEach(cb => {
            platforms[cb.dataset.platform] = cb.checked;
            cb.nextElementSibling.textContent = cb.checked ? 'On' : 'Off';
          });
          chrome.storage?.local?.set({ zeq_platforms: platforms });
        });
      });
    });
  }

  // ── Handle messages from service worker ────────────────────────────────
  function handleMessage(message) {
    if (message.type === 'zeq_active_tab') {
      updateCtxBar(message.ctx);
    }
    if (message.type === 'zeq_cko_update') {
      CKOClient.parseFromResponse(message.payload);
    }
    if (message.type === 'zeq_sync_tick') {
      const meshEl = document.getElementById('mesh-status');
      if (meshEl) {
        meshEl.textContent = message.meshNodes > 0
          ? `mesh: ${message.meshNodes}`
          : '';
      }
      updateServiceIndicator('svc-sync', 'online', message.meshNodes + ' nodes');
      setTextContent('svc-mesh-nodes', message.meshNodes.toString());
    }
  }

  // ── Service Status ─────────────────────────────────────────────────────
  function wireServiceStatus() {
    document.getElementById('refresh-status-btn')?.addEventListener('click', refreshServiceStatus);
  }

  function refreshServiceStatus() {
    chrome.runtime?.sendMessage?.({ type: 'getServiceStatus' }, (response) => {
      if (chrome.runtime.lastError || !response?.success) return;
      const s = response.status;

      // Live ZeqVM services (keys from the retargeted service worker).
      updateServiceIndicator('svc-sync',      s.syncEngine);
      updateServiceIndicator('svc-api',       s.apiGateway);
      updateServiceIndicator('svc-auth',      s.compute);   // compute = identity-bearing API
      updateServiceIndicator('svc-zeqtext',   s.operators);
      updateServiceIndicator('svc-librechat', s.compute);
      updateServiceIndicator('svc-forensic',  s.ssl);
      updateServiceIndicator('svc-wizard',    s.compute);
      updateServiceIndicator('svc-daemon',    s.chain);
      setTextContent('svc-mesh-nodes', (s.meshNodes || 0).toString());
    });
  }

  function updateServiceIndicator(elementId, status, extra) {
    const el = document.getElementById(elementId);
    if (!el) return;

    const colors = { online: '#22c55e', offline: '#64748b', error: '#ef4444' };
    const labels = { online: 'online', offline: 'offline', error: 'error' };
    const color  = colors[status] || colors.offline;
    const label  = extra || labels[status] || status;

    el.innerHTML = `<span style="color:${color}">●</span> <span style="font-size:10px;color:${color}">${label}</span>`;
  }

  // ── Utility ────────────────────────────────────────────────────────────
  function setTextContent(id, text) {
    const el = document.getElementById(id);
    if (el) el.textContent = text;
  }
})();
