// ANKLE Terminal v2.1.9 — shared module for aleqth.com + external embeds · ported from extension/content.js
/* Drop-in via:  <script src="https://aleqth.com/atcmd/widget-v2.js"></script>
   Optional:     <script src="https://aleqth.com/atcmd/widget-v2.js" data-base="https://aleqth.com"></script>

   Preserves:
   - extension UX (shadow-DOM terminal, FAB, quick actions, slash menu, @cmd chips, Hermes bubbles)
   - `window.atcmd.*` API surface from atcmd-widget.js so sites that use me(), call(), open(), send(), etc. keep working.

   Auth chain:
   1. URL fragment #ankle_token=...  → POST /api/identity/claim → persist as `@cmd_identity`
   2. localStorage `@cmd_identity`   → parsed as { token, user, timestamp }
   3. localStorage `ankleBearer`     → raw token (extension legacy)
   4. Same-origin cookie fallback via credentials:"include"
*/
(function () {
  "use strict";

  // ─── Double-load guard ───────────────────────────────────
  if (window.__ANKLE_TERMINAL_V2_LOADED) return;
  window.__ANKLE_TERMINAL_V2_LOADED = true;
  window.__ANKLE_TERMINAL_VERSION = "2.1.9";

  // ─── Script tag + BASE resolution ───────────────────────
  var _currentScript = document.currentScript || (function () {
    var s = document.getElementsByTagName("script");
    return s[s.length - 1];
  })();

  var BASE = "https://aleqth.com";
  try {
    if (_currentScript && _currentScript.dataset && _currentScript.dataset.base) {
      BASE = _currentScript.dataset.base.replace(/\/+$/, "");
    }
  } catch (e) {}

  var ORIGIN = (function () {
    try {
      if (_currentScript && _currentScript.dataset && _currentScript.dataset.origin) return _currentScript.dataset.origin;
    } catch (e) {}
    try { return location.origin; } catch (e) { return ""; }
  })();

  var FIRST_PARTY = (function () {
    var host = (location.hostname || "").toLowerCase();
    return host === "aleqth.com" || host === "www.aleqth.com" ||
           host === "ankle.website" || host === "www.ankle.website" ||
           host === "localhost" || host === "127.0.0.1";
  })();
  var LEGAL_POLICY = {
    bundle_version: "2026-07-21.1",
    terms_version: "1.2",
    privacy_version: "1.2",
    acceptable_use_version: "1.0"
  };

  // Token storage keys (match both widget + extension conventions)
  var STORAGE_KEY_IDENTITY = "@cmd_identity";   // { token, user, timestamp }
  var STORAGE_KEY_BEARER   = "ankleBearer";     // raw token (extension legacy)
  var STORAGE_KEY_STATE    = "ankleTerminalState"; // { open, expanded, outputHTML, input, history, scroll, savedAt }
  var STORAGE_KEY_THEME    = "ankleTheme";
  var STORAGE_KEY_THEME_CUSTOM = "ankleThemeCustom";

  // ─── localStorage shim that mirrors chrome.storage.local ─
  // Accepts JSON for objects; falls back to string for raw bearer.
  var storage = {
    get: function (keys, cb) {
      var out = {};
      try {
        var list = Array.isArray(keys) ? keys : (typeof keys === "string" ? [keys] : Object.keys(keys || {}));
        list.forEach(function (k) {
          var raw = null;
          try { raw = localStorage.getItem(k); } catch (e) {}
          if (raw == null) return;
          // Identity + state + theme-custom live as JSON; bearer + theme preset live as raw strings.
          if (k === STORAGE_KEY_BEARER || k === STORAGE_KEY_THEME) {
            out[k] = raw;
            return;
          }
          try { out[k] = JSON.parse(raw); }
          catch (e) { out[k] = raw; }
        });
      } catch (e) {}
      try { if (typeof cb === "function") cb(out); } catch (e) {}
    },
    set: function (obj, cb) {
      try {
        Object.keys(obj || {}).forEach(function (k) {
          var v = obj[k];
          try {
            if (typeof v === "string") localStorage.setItem(k, v);
            else localStorage.setItem(k, JSON.stringify(v));
          } catch (e) {}
        });
      } catch (e) {}
      try { if (typeof cb === "function") cb(); } catch (e) {}
    },
  };

  // Cross-tab storage events — translate to the same shape chrome.storage.onChanged used.
  function onStorageChanged(keys, handler) {
    var watch = Array.isArray(keys) ? keys : [keys];
    window.addEventListener("storage", function (e) {
      if (!e || !e.key) return;
      if (watch.indexOf(e.key) === -1) return;
      try { handler(); } catch (err) {}
    });
  }

  // ─── Token helpers ───────────────────────────────────────
  function _readIdentityToken() {
    if (!FIRST_PARTY) return null;
    try {
      var raw = localStorage.getItem(STORAGE_KEY_IDENTITY);
      if (!raw) return null;
      // Support both wrapped-object and raw-string forms (widget stored raw).
      if (raw.charAt(0) === "{") {
        try {
          var parsed = JSON.parse(raw);
          if (parsed && typeof parsed === "object" && parsed.token) return parsed.token;
        } catch (e) {}
      }
      return raw;
    } catch (e) { return null; }
  }

  function _readBearer() {
    if (!FIRST_PARTY) return null;
    try { return localStorage.getItem(STORAGE_KEY_BEARER) || null; } catch (e) { return null; }
  }

  function getToken() {
    return _readIdentityToken() || _readBearer() || null;
  }

  function setToken(t, user) {
    if (!FIRST_PARTY || !t) return false;
    try {
      var payload = { token: t, user: user || null, timestamp: Date.now() };
      localStorage.setItem(STORAGE_KEY_IDENTITY, JSON.stringify(payload));
    } catch (e) {}
    return true;
  }

  function clearTokens() {
    try { localStorage.removeItem(STORAGE_KEY_IDENTITY); } catch (e) {}
    try { localStorage.removeItem(STORAGE_KEY_BEARER); } catch (e) {}
    _currentUser = null;
  }

  function _validIdentityToken(token) {
    return typeof token === "string" && /^[a-f0-9]{32}$/i.test(token);
  }

  // ─── URL fragment claim — cross-domain handoff ──────────
  function _consumeFragmentToken() {
    if (!FIRST_PARTY) return null;
    try {
      var hash = location.hash || "";
      var m = hash.match(/[#&]ankle_token=([^&]+)/);
      if (!m) return null;
      var handoff = decodeURIComponent(m[1]);
      try {
        // Strip the fragment to keep tokens out of history.
        history.replaceState(null, "", location.pathname + location.search);
      } catch (e) {}
      return handoff;
    } catch (e) { return null; }
  }

  function _claimHandoff(handoff) {
    if (!FIRST_PARTY) return Promise.resolve(false);
    return fetch(BASE + "/api/identity/claim", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        handoff_token: handoff,
        origin: ORIGIN,
        device_name: (navigator.userAgent.indexOf("ANKLE") !== -1) ? "ANKLE.app" : "browser",
      }),
    })
    .then(function (r) { return r.json(); })
    .then(function (d) {
      if (d && d.ok && d.app_token) {
        setToken(d.app_token, d.user || null);
        _currentUser = d.user || null;
        return true;
      }
      return false;
    })
    .catch(function () { return false; });
  }

  // ─── Auth fetch (apiCall) — same shape the extension exposed ─
  // Returns { ok, status, data, error }
  function apiCall(path, method, body) {
    method = method || "GET";
    var token = getToken();
    var headers = { "Content-Type": "application/json" };
    if (token) headers["Authorization"] = "Bearer " + token;
    return fetch(BASE + path, {
      method: method,
      headers: headers,
      credentials: token ? "omit" : (FIRST_PARTY ? "include" : "omit"),
      body: body ? JSON.stringify(body) : undefined,
    })
    .then(function (r) {
      var status = r.status;
      var ok = r.ok;
      return r.json().then(
        function (d) { return { ok: ok, status: status, data: d }; },
        function () { return { ok: ok, status: status, data: null }; }
      );
    })
    .catch(function (err) {
      return { ok: false, status: 0, error: (err && err.message) || "network error" };
    });
  }

  var _currentUser = null;

  // ─── Shadow DOM root ────────────────────────────────────
  if (document.getElementById("ankle-term-v2-root")) return;

  var root = document.createElement("div");
  root.id = "ankle-term-v2-root";
  root.style.cssText = "all:initial;position:fixed;z-index:2147483647;pointer-events:none;top:0;left:0;width:0;height:0;";
  (document.body || document.documentElement).appendChild(root);

  var shadow = root.attachShadow({ mode: "closed" });

  // ─── Theme presets ──────────────────────────────────────
  var THEME_PRESETS = {
    midnight: { bg:"#0a0a0a", card:"#111111", border:"#1a1a1a", borderHover:"#333333", text:"#e0e0e0", textDim:"#666666", textBright:"#ffffff", accent:"#1E4DFF" },
    void:     { bg:"#000000", card:"#0a0a0a", border:"#151515", borderHover:"#2a2a2a", text:"#cccccc", textDim:"#555555", textBright:"#ffffff", accent:"#FF4444" },
    carbon:   { bg:"#121212", card:"#1a1a1a", border:"#252525", borderHover:"#404040", text:"#d0d0d0", textDim:"#666666", textBright:"#ffffff", accent:"#00C98D" },
    bone:     { bg:"#f0ece4", card:"#e8e3d9", border:"#d5cfc3", borderHover:"#b8b0a0", text:"#2a2a2a", textDim:"#888888", textBright:"#111111", accent:"#1E4DFF" },
    gold:     { bg:"#0d0d0d", card:"#141410", border:"#222218", borderHover:"#3a3a2a", text:"#d4c99e", textDim:"#706a50", textBright:"#fff8dc", accent:"#FFD700" },
    violet:   { bg:"#0e0a14", card:"#161020", border:"#221a30", borderHover:"#3a2a50", text:"#c8b8e0", textDim:"#6a5a80", textBright:"#f0e8ff", accent:"#6B35D4" },
    // ankle96 — Win95 / Netscape revival theme used across aleqth.com.
    // Color-map substitutes only the base palette; ANKLE96_OVERRIDES below
    // adds the bevels, hard edges, and serif type that define the look.
    ankle96:  { bg:"#c0c0c0", card:"#ffffff", border:"#000000", borderHover:"#0000ee", text:"#000000", textDim:"#555555", textBright:"#000000", accent:"#0000ee" },
  };

  // ankle96-only: bevels, serif, hard edges, period button chrome.
  // Appended after the color-substituted base CSS when ankle96 is active.
  var ANKLE96_OVERRIDES = [
    /* turn off ALL rounding */
    ":host *,:host *::before,:host *::after{border-radius:0 !important;}",
    /* terminal panel — Win95 panel with hard 1px black border + drop shadow */
    ".ankle-terminal{font-family:'Times New Roman',Times,serif !important;border:1px solid #000 !important;box-shadow:3px 3px 0 #000 !important;}",
    ".terminal-header{background:#c0c0c0 !important;border-bottom:1px solid #000 !important;color:#000 !important;}",
    ".terminal-header .label{color:#000 !important;font-family:'Courier New',Courier,monospace !important;}",
    ".terminal-header .label span{color:#0000ee !important;}",
    /* output well — white paper */
    ".terminal-output{background:#ffffff !important;color:#000 !important;font-family:'Courier New',Courier,monospace !important;border-top:1px inset #fff;}",
    ".terminal-output .line.cmd{color:#0000ee !important;}",
    ".terminal-output .line.cmd::before{color:#000 !important;}",
    ".terminal-output .line.result{color:#000 !important;}",
    ".terminal-output .line.system{color:#555 !important;}",
    ".terminal-output .line.hermes{color:#006400 !important;}",
    ".terminal-output .line.hermes::before{color:#006400 !important;}",
    ".terminal-output .line.error{color:#cc0000 !important;}",
    ".terminal-output::-webkit-scrollbar{width:14px;}",
    ".terminal-output::-webkit-scrollbar-track{background:#c0c0c0;border:1px solid #000;}",
    ".terminal-output::-webkit-scrollbar-thumb{background:#c0c0c0;border:1px outset #fff;}",
    /* quick action bar — beveled button strip */
    ".quick-actions{background:#c0c0c0 !important;border-top:1px solid #000 !important;}",
    ".qa-btn{background:#c0c0c0 !important;border:2px outset #fff !important;color:#000 !important;font-family:'Courier New',Courier,monospace !important;text-transform:uppercase;letter-spacing:.04em;}",
    ".qa-btn:active{border:2px inset #fff !important;}",
    ".qa-btn:hover{background:#c0c0c0 !important;border-color:#fff !important;color:#0000ee !important;}",
    ".qa-btn .g{color:#0000ee !important;}",
    /* input row + caret */
    ".terminal-input-row{background:#c0c0c0 !important;border-top:1px solid #000 !important;}",
    ".terminal-input{background:#ffffff !important;color:#000 !important;border:2px inset #fff !important;padding:3px 6px !important;font-family:'Courier New',Courier,monospace !important;caret-color:#000 !important;}",
    ".terminal-input::placeholder{color:#888 !important;}",
    ".terminal-prompt{color:#000 !important;font-family:'Courier New',Courier,monospace !important;}",
    ".terminal-hermes-tag{color:#006400 !important;}",
    /* close/reset/copy/expand row buttons */
    ".terminal-close,.terminal-reset,.terminal-copy,.terminal-expand,.terminal-popout{color:#000 !important;font-family:'Courier New',Courier,monospace !important;}",
    ".terminal-close:hover,.terminal-reset:hover,.terminal-copy:hover,.terminal-expand:hover,.terminal-popout:hover{color:#0000ee !important;}",
    /* hermes bubbles — sticky-note yellow with blue rule */
    ".hermes-bubble{background:#ffffe0 !important;border:1px solid #000 !important;border-left:4px solid #0000ee !important;}",
    ".hermes-bubble .b-text{color:#000 !important;font-family:'Times New Roman',Times,serif !important;}",
    ".hermes-bubble .b-text strong{color:#000 !important;}",
    ".hermes-bubble .b-text em{color:#0000ee !important;font-style:italic;}",
    /* command chips — period beveled buttons */
    ".cmd-chip{background:#c0c0c0 !important;border:2px outset #fff !important;color:#000 !important;font-family:'Courier New',Courier,monospace !important;padding:2px 8px;}",
    ".cmd-chip:active{border:2px inset #fff !important;}",
    ".cmd-chip:hover{background:#c0c0c0 !important;color:#0000ee !important;}",
    ".cmd-chip.primary{background:#ffffe0 !important;color:#000 !important;}",
    ".cmd-chip.primary:hover{background:#ffffe0 !important;color:#0000ee !important;}",
    ".cmd-chip.muted{background:#c0c0c0 !important;color:#555 !important;}",
    /* slash-menu — drop-down list of commands */
    ".slash-menu{background:#ffffff !important;border:1px solid #000 !important;box-shadow:3px 3px 0 #000 !important;font-family:'Courier New',Courier,monospace !important;}",
    ".slash-item{color:#000 !important;border-bottom:1px solid #c0c0c0 !important;}",
    ".slash-item .k{color:#0000ee !important;}",
    ".slash-item .d{color:#555 !important;}",
    ".slash-item:hover,.slash-item.sel{background:#0000ee !important;color:#fff !important;}",
    ".slash-item:hover .k,.slash-item.sel .k{color:#ffffe0 !important;}",
    ".slash-item:hover .d,.slash-item.sel .d{color:#cce !important;}",
    /* FAB — beveled square button (no longer round) */
    ".ankle-fab{border-radius:0 !important;background:#c0c0c0 !important;border:2px outset #fff !important;color:#000 !important;font-family:'Courier New',Courier,monospace !important;box-shadow:2px 2px 0 #000 !important;}",
    ".ankle-fab:hover{transform:none !important;border-color:#fff !important;color:#0000ee !important;box-shadow:2px 2px 0 #000 !important;}",
    ".ankle-fab.active{background:#c0c0c0 !important;color:#0000ee !important;border:2px inset #fff !important;}",
    /* notification — modal dialog look */
    ".ankle-notification{background:#c0c0c0 !important;border:1px solid #000 !important;box-shadow:3px 3px 0 #000 !important;color:#000 !important;font-family:'Courier New',Courier,monospace !important;}",
    ".ankle-notification .notif-sigil{color:#0000ee !important;}",
  ].join("\n");

  var COLOR_MAP = {
    "#0a0a0a": "bg",
    "#0f0f0f": "card",
    "#111":    "card",
    "#111111": "card",
    "#1a1a1a": "border",
    "#333":    "borderHover",
    "#333333": "borderHover",
    "#1E4DFF": "accent",
    "#1e4dff": "accent",
    "#e0e0e0": "text",
    "#ccc":    "text",
    "#cccccc": "text",
    "#666":    "textDim",
    "#666666": "textDim",
    "#fff":    "textBright",
    "#ffffff": "textBright",
  };

  function themeCSS(baseCSS, theme) {
    var out = baseCSS;
    for (var hex in COLOR_MAP) {
      if (!COLOR_MAP.hasOwnProperty(hex)) continue;
      var role = COLOR_MAP[hex];
      var to = theme[role];
      if (!to) continue;
      out = out.split(hex).join(to);
    }
    return out;
  }

  var BASE_CSS = [
    "@import url('https://fonts.googleapis.com/css2?family=Fragment+Mono&display=swap');",
    "*{margin:0;padding:0;box-sizing:border-box}",
    ".ankle-fab{position:fixed;bottom:20px;right:20px;width:36px;height:36px;border-radius:50%;background:#0a0a0a;border:1px solid #333;color:#1E4DFF;font-size:16px;font-family:'Fragment Mono',monospace;cursor:pointer;display:flex;align-items:center;justify-content:center;pointer-events:auto;transition:transform .15s,border-color .15s,box-shadow .15s;box-shadow:0 2px 8px rgba(0,0,0,.4);user-select:none;z-index:2147483647;}",
    ".ankle-fab:hover{transform:scale(1.1);border-color:#1E4DFF;box-shadow:0 2px 16px rgba(30,77,255,.3);}",
    ".ankle-fab.active{background:#1E4DFF;color:#fff;border-color:#1E4DFF;}",
    ".ankle-terminal{position:fixed;bottom:68px;right:20px;width:440px;max-width:calc(100vw - 40px);height:440px;max-height:calc(100vh - 100px);background:#0a0a0a;border:1px solid #1a1a1a;border-radius:8px;display:flex;flex-direction:column;pointer-events:auto;box-shadow:0 8px 32px rgba(0,0,0,.6);opacity:0;transform:translateY(10px) scale(.97);transition:opacity .2s,transform .2s;overflow:hidden;z-index:2147483647;}",
    ".ankle-terminal.open{opacity:1;transform:translateY(0) scale(1);}",
    ".ankle-terminal.closed{pointer-events:none;}",
    ".terminal-header{display:flex;align-items:center;padding:8px 12px;border-bottom:1px solid #1a1a1a;background:#0f0f0f;flex-shrink:0;}",
    ".terminal-header .label{font-family:'Fragment Mono',monospace;font-size:10px;color:#666;letter-spacing:1px;margin-left:auto;padding-right:8px;text-align:right;}",
    ".terminal-header .label span{color:#1E4DFF;}",
    ".terminal-close,.terminal-reset,.terminal-copy,.terminal-expand,.terminal-popout{background:none;border:none;color:#555;font-size:13px;cursor:pointer;padding:0 4px;font-family:'Fragment Mono',monospace;pointer-events:auto;}",
    ".terminal-close:hover{color:#fff}",
    ".terminal-copy:hover{color:#FFD700}",
    ".terminal-expand:hover,.terminal-popout:hover{color:#1E4DFF}",
    ".terminal-reset:hover{color:#FF8C42}",
    ".terminal-copy.flash{color:#FFD700}",
    ".ankle-terminal.expanded{width:calc(100vw - 40px) !important;height:calc(100vh - 100px) !important;right:20px;bottom:68px;}",
    ".terminal-output{flex:1;overflow-y:auto;padding:10px 12px;font-family:'Fragment Mono',monospace;font-size:11px;line-height:1.6;color:#ccc;}",
    ".terminal-output::-webkit-scrollbar{width:4px}",
    ".terminal-output::-webkit-scrollbar-track{background:transparent}",
    ".terminal-output::-webkit-scrollbar-thumb{background:#333;border-radius:2px}",
    ".terminal-output .line{margin-bottom:4px}",
    ".terminal-output .line.cmd{color:#1E4DFF}",
    ".terminal-output .line.cmd::before{content:'> ';color:#555}",
    ".terminal-output .line.result{color:#ccc}",
    ".terminal-output .line.error{color:#FF4444}",
    ".terminal-output .line.system{color:#555;font-style:italic}",
    ".terminal-output .line.hermes{color:#FF8C42}",
    ".terminal-output .line.hermes::before{content:'\u26A1 ';color:#FF8C42}",
    ".terminal-input-row{display:flex;align-items:center;padding:8px 12px;border-top:1px solid #1a1a1a;background:#0f0f0f;flex-shrink:0;}",
    ".terminal-prompt{color:#555;font-family:'Fragment Mono',monospace;font-size:11px;margin-right:6px;user-select:none;}",
    ".terminal-hermes-tag{color:#FF8C42;font-family:'Fragment Mono',monospace;font-size:9px;margin-right:6px;user-select:none;letter-spacing:0.5px;}",
    ".terminal-input{flex:1;background:transparent;border:none;outline:none;color:#fff;font-family:'Fragment Mono',monospace;font-size:11px;caret-color:#1E4DFF;}",
    ".terminal-input::placeholder{color:#333}",
    ".hermes-bubble{margin:6px 0 10px 0;padding:8px 10px 10px;background:rgba(255,140,66,.04);border-left:2px solid #FF8C42;border-radius:2px;}",
    ".hermes-bubble .b-text{color:#ddd;white-space:pre-wrap;word-break:break-word;font-size:11px;line-height:1.55;}",
    ".hermes-bubble .b-text strong{color:#fff;font-weight:600}",
    ".hermes-bubble .b-text em{color:#FF8C42;font-style:normal}",
    ".hermes-bubble .b-chips{display:flex;flex-wrap:wrap;gap:4px;margin-top:8px;}",
    ".cmd-chip{background:#1a1207;border:1px solid #663d1e;color:#FF8C42;font-family:'Fragment Mono',monospace;font-size:10px;padding:3px 8px;border-radius:10px;cursor:pointer;line-height:1.4;letter-spacing:.2px;transition:all .12s;pointer-events:auto;}",
    ".cmd-chip:hover{background:#FF8C42;color:#000;border-color:#FF8C42;}",
    ".cmd-chip.primary{background:#0a1d3d;border-color:#1E4DFF;color:#6A8CFF;}",
    ".cmd-chip.primary:hover{background:#1E4DFF;color:#fff}",
    ".cmd-chip.muted{background:#0f0f0f;border-color:#2a2a2a;color:#777;}",
    ".cmd-chip.muted:hover{background:#333;color:#fff}",
    ".hermes-meta{color:#444;font-size:9px;margin-top:6px}",
    ".quick-actions{display:flex;flex-wrap:wrap;gap:4px;padding:6px 8px;background:#0d0d0d;border-top:1px solid #1a1a1a;flex-shrink:0;}",
    ".qa-btn{background:#141414;border:1px solid #262626;color:#aaa;font-family:'Fragment Mono',monospace;font-size:10px;padding:3px 7px;border-radius:3px;cursor:pointer;transition:all .12s;pointer-events:auto;}",
    ".qa-btn:hover{background:#1E4DFF;border-color:#1E4DFF;color:#fff}",
    ".qa-btn .g{color:#1E4DFF;margin-right:4px}",
    ".slash-menu{position:absolute;bottom:74px;left:10px;right:10px;max-height:180px;overflow-y:auto;background:#0f0f0f;border:1px solid #1E4DFF;border-radius:4px;box-shadow:0 4px 16px rgba(30,77,255,.2);z-index:2;}",
    ".slash-menu.hidden{display:none}",
    ".slash-item{padding:6px 10px;font-family:'Fragment Mono',monospace;font-size:10px;color:#aaa;cursor:pointer;border-bottom:1px solid #1a1a1a;pointer-events:auto;}",
    ".slash-item:last-child{border-bottom:none}",
    ".slash-item .k{color:#1E4DFF;margin-right:8px}",
    ".slash-item .d{color:#555}",
    ".slash-item:hover,.slash-item.sel{background:#1E4DFF;color:#fff}",
    ".slash-item:hover .k,.slash-item.sel .k{color:#fff}",
    ".slash-item:hover .d,.slash-item.sel .d{color:#cce}",
    ".ankle-notification{position:fixed;top:20px;right:20px;background:#111;border:1px solid #333;border-radius:6px;padding:10px 16px;font-family:'Fragment Mono',monospace;font-size:11px;color:#ccc;pointer-events:auto;box-shadow:0 4px 16px rgba(0,0,0,.4);opacity:0;transform:translateY(-10px);transition:opacity .2s,transform .2s;z-index:2147483647;max-width:320px;}",
    ".ankle-notification.visible{opacity:1;transform:translateY(0);}",
    ".ankle-notification .notif-sigil{color:#1E4DFF;margin-right:6px;}",
  ].join("\n");

  // ankle96 is now the default — Win95 / Netscape revival across the rest of
  // aleqth.com, and the extension was the last surface still on midnight.
  var DEFAULT_THEME_KEY = "ankle96";

  function buildCSS(themeKey, theme) {
    var css = themeCSS(BASE_CSS, theme);
    if (themeKey === "ankle96") css += "\n" + ANKLE96_OVERRIDES;
    return css;
  }

  var style = document.createElement("style");
  style.textContent = buildCSS(DEFAULT_THEME_KEY, THEME_PRESETS[DEFAULT_THEME_KEY]);
  shadow.appendChild(style);

  function resolveTheme(data) {
    if (data.ankleTheme === "custom" && data.ankleThemeCustom) {
      return { key: "custom", palette: data.ankleThemeCustom };
    }
    var key = data.ankleTheme || DEFAULT_THEME_KEY;
    return { key: key, palette: THEME_PRESETS[key] || THEME_PRESETS[DEFAULT_THEME_KEY] };
  }

  storage.get([STORAGE_KEY_THEME, STORAGE_KEY_THEME_CUSTOM], function (data) {
    try { var t = resolveTheme(data || {}); style.textContent = buildCSS(t.key, t.palette); } catch (e) {}
  });
  onStorageChanged([STORAGE_KEY_THEME, STORAGE_KEY_THEME_CUSTOM], function () {
    storage.get([STORAGE_KEY_THEME, STORAGE_KEY_THEME_CUSTOM], function (data) {
      try { var t = resolveTheme(data || {}); style.textContent = buildCSS(t.key, t.palette); } catch (e) {}
    });
  });

  // ─── FAB ────────────────────────────────────────────────
  var fab = document.createElement("button");
  fab.className = "ankle-fab";
  fab.textContent = "\u2318";
  fab.title = "ANKLE @\u2318 (Ctrl+K)";
  shadow.appendChild(fab);

  // ─── Terminal panel ─────────────────────────────────────
  var terminal = document.createElement("div");
  terminal.className = "ankle-terminal closed";
  terminal.innerHTML = [
    '<div class="terminal-header">',
    '  <button class="terminal-copy" title="copy output">\u29C9</button>',
    '  <button class="terminal-expand" title="expand">\u21F1</button>',
    '  <button class="terminal-popout" title="pop out to aleqth.com">\u2934</button>',
    '  <button class="terminal-reset" title="clear">\u21BB</button>',
    '  <div class="label"><span>@\u2318</span> terminal by Ankle</div>',
    '  <button class="terminal-close" title="close">\u00D7</button>',
    '</div>',
    '<div class="terminal-output">',
    '  <div class="line system">ANKLE protocol v2 \u2014 @\u2318 terminal by Ankle \u00B7 tap any chip to run \u00B7 type / for commands</div>',
    '  <div class="line system">Connected to ' + BASE + '</div>',
    '</div>',
    '<div class="quick-actions">',
    '  <button class="qa-btn" data-cmd="/ingest"><span class="g">\u2193</span>ingest page</button>',
    '  <button class="qa-btn" data-cmd="/collect-images"><span class="g">\u25C8</span>collect imgs</button>',
    '  <button class="qa-btn" data-cmd="/surf-images"><span class="g">\u2248</span>to /surf</button>',
    '  <button class="qa-btn" data-cmd="/refine"><span class="g">~</span>refine</button>',
    '  <button class="qa-btn" data-cmd="/surf"><span class="g">\u2248</span>surf</button>',
    '  <button class="qa-btn" data-cmd="/discover"><span class="g">\u25CE</span>discover</button>',
    '  <button class="qa-btn" data-cmd="/taste"><span class="g">\u27E1</span>taste</button>',
    '  <button class="qa-btn" data-cmd="/credits"><span class="g">\u25C7</span>credits</button>',
    '  <button class="qa-btn" data-cmd="/install"><span class="g">\u2193</span>install</button>',
    '  <button class="qa-btn" data-cmd="/help"><span class="g">?</span>help</button>',
    '</div>',
    '<div class="slash-menu hidden"></div>',
    '<div class="terminal-input-row">',
    '  <span class="terminal-hermes-tag">hermes</span>',
    '  <span class="terminal-prompt">&gt;</span>',
    '  <input class="terminal-input" type="text" placeholder="ask hermes or / for commands\u2026" autocomplete="off" spellcheck="false">',
    '</div>',
  ].join("\n");
  shadow.appendChild(terminal);

  var output = terminal.querySelector(".terminal-output");
  var input = terminal.querySelector(".terminal-input");
  var closeBtn = terminal.querySelector(".terminal-close");
  var resetBtn = terminal.querySelector(".terminal-reset");
  var copyBtn = terminal.querySelector(".terminal-copy");
  var expandBtn = terminal.querySelector(".terminal-expand");
  var popoutBtn = terminal.querySelector(".terminal-popout");
  var qaBar = terminal.querySelector(".quick-actions");
  var slashMenu = terminal.querySelector(".slash-menu");

  // ─── Notification ───────────────────────────────────────
  var notification = document.createElement("div");
  notification.className = "ankle-notification";
  shadow.appendChild(notification);

  function showNotification(message) {
    notification.innerHTML = '<span class="notif-sigil">\u25C8</span>' + escapeHtml(message);
    notification.classList.add("visible");
    setTimeout(function () { notification.classList.remove("visible"); }, 3000);
  }

  function escapeHtml(s) {
    var d = document.createElement("div");
    d.textContent = s == null ? "" : String(s);
    return d.innerHTML;
  }

  // ─── Terminal state (runtime) ───────────────────────────
  var terminalOpen = false;
  var commandHistory = [];
  var historyIndex = -1;

  // ─── Command Catalog ────────────────────────────────────
  var COMMANDS = [
    { key: "/ingest",          desc: "ingest current page into /surf", hint: "" },
    { key: "/collect-images",  desc: "queue every <img> on this page for inventory", hint: "" },
    { key: "/surf-images",     desc: "add every <img> on this page to /surf archive", hint: "" },
    { key: "/surf-recursive",  desc: "recursively crawl + /surf-ingest from this page", hint: "[depth 1-3]" },
    { key: "/collect",         desc: "collect a URL to your inventory", hint: "<url>" },
    { key: "/refine",          desc: "rewrite the site's ?q= search", hint: "<new query>" },
    { key: "/surf",            desc: "search SURF archive", hint: "<query>" },
    { key: "/discover",        desc: "find adjacent URLs from your taste", hint: "" },
    { key: "/taste",           desc: "show your taste profile", hint: "" },
    { key: "/hermes",          desc: "talk to Hermes directly", hint: "<message>" },
    { key: "/remix",           desc: "remix current page", hint: "" },
    { key: "/verify",          desc: "verify current page or object", hint: "" },
    { key: "/verify-url",      desc: "legacy URL badge queue", hint: "<url>" },
    { key: "/dada",            desc: "open /dada engine", hint: "" },
    { key: "/credits",         desc: "top up ANKLE credits", hint: "" },
    { key: "/install",         desc: "open field-kit install guide", hint: "" },
    // Widget-shared commands — forwarded to /atcmd/chat when no local handler matches.
    { key: "/balance",         desc: "show credit balance", hint: "" },
    { key: "/search",          desc: "search the network", hint: "<query>" },
    { key: "/explore",         desc: "explore the protocol", hint: "" },
    { key: "/discourse",       desc: "open discourse", hint: "" },
    { key: "/enrich",          desc: "enrich current context", hint: "" },
    { key: "/gif",             desc: "make a kinetic gif", hint: "<prompt>" },
    { key: "/radio",           desc: "tune the radio", hint: "" },
    { key: "/haha",            desc: "spin a beat", hint: "<prompt>" },
    { key: "/feed",            desc: "check the feed", hint: "" },
    { key: "/status",          desc: "server status", hint: "" },
    { key: "/clear",           desc: "clear terminal", hint: "" },
    { key: "/help",            desc: "show this help", hint: "" },
  ];

  var CMD_ALIASES = {
    ingest: "/ingest", ingest_page: "/ingest",
    collect: "/collect", collect_images: "/collect-images",
    surf_images: "/surf-images", add_to_surf: "/surf-images",
    refine: "/refine", search_refine: "/refine",
    surf: "/surf", image_search: "/surf", search: "/search",
    discover: "/discover", adjacent: "/discover", archive_dive: "/discover",
    taste: "/taste",
    remix: "/remix", verify: "/verify", verify_object: "/verify", dada: "/dada",
    credits: "/credits", topup: "/credits", top_up: "/credits",
    install: "/install", download: "/install", field_kit: "/install",
    balance: "/balance", explore: "/explore", discourse: "/discourse",
    enrich: "/enrich", gif: "/gif", radio: "/radio", haha: "/haha", feed: "/feed",
    help: "/help", clear: "/clear", status: "/status",
    hermes: "/hermes",
  };

  // Commands that should be forwarded to /atcmd/chat if not locally handled.
  var SERVER_DISPATCH = [
    "/balance", "/verify", "/remix", "/search", "/explore",
    "/discourse", "/enrich", "/dada", "/gif", "/radio", "/haha", "/feed",
  ];

  // ─── Slash-menu autocomplete ────────────────────────────
  var slashSelIdx = 0;

  function slashOpen() { return !slashMenu.classList.contains("hidden"); }

  function renderSlashMenu(query) {
    var q = (query || "").toLowerCase();
    var matches = COMMANDS.filter(function (c) { return c.key.indexOf(q) === 0 || q.length <= 1; });
    if (!matches.length) { hideSlashMenu(); return; }
    slashSelIdx = 0;
    slashMenu.innerHTML = matches.slice(0, 10).map(function (c, i) {
      return '<div class="slash-item ' + (i === 0 ? 'sel' : '') + '" data-cmd="' + c.key + '" data-hint="' + c.hint + '">' +
        '<span class="k">' + c.key + (c.hint ? ' ' + c.hint : '') + '</span>' +
        '<span class="d">' + c.desc + '</span>' +
      '</div>';
    }).join("");
    slashMenu.classList.remove("hidden");
  }

  function hideSlashMenu() {
    slashMenu.classList.add("hidden");
    slashMenu.innerHTML = "";
  }

  slashMenu.addEventListener("click", function (e) {
    var item = e.target.closest(".slash-item");
    if (!item) return;
    var cmd = item.dataset.cmd;
    var hint = item.dataset.hint || "";
    input.value = cmd + (hint ? " " : "");
    hideSlashMenu();
    if (!hint) {
      commandHistory.unshift(cmd);
      historyIndex = -1;
      input.value = "";
      handleCommand(cmd);
    } else {
      input.focus();
    }
  });

  // ─── Cross-page persistence ─────────────────────────────
  var saveTimer = null;
  function persistState() {
    clearTimeout(saveTimer);
    saveTimer = setTimeout(function () {
      try {
        var state = {
          open: terminalOpen,
          expanded: terminal.classList.contains("expanded"),
          outputHTML: output.innerHTML.slice(0, 400000),
          input: input.value,
          history: commandHistory.slice(0, 100),
          scroll: output.scrollTop,
          savedAt: Date.now(),
        };
        var obj = {};
        obj[STORAGE_KEY_STATE] = state;
        storage.set(obj);
      } catch (e) {}
    }, 250);
  }

  function restoreState() {
    storage.get([STORAGE_KEY_STATE], function (data) {
      try {
        var s = data && data[STORAGE_KEY_STATE];
        if (!s) return;
        if (typeof s.outputHTML === "string" && s.outputHTML.length) {
          output.innerHTML = s.outputHTML;
        }
        if (Array.isArray(s.history)) commandHistory = s.history.slice(0, 100);
        if (s.expanded) {
          terminal.classList.add("expanded");
          if (expandBtn) { expandBtn.textContent = "\u21F2"; expandBtn.title = "collapse"; }
        }
        if (typeof s.input === "string") input.value = s.input;
        if (s.open) {
          terminalOpen = true;
          terminal.classList.remove("closed");
          terminal.classList.add("open");
          fab.classList.add("active");
          setTimeout(function () { input.focus(); }, 80);
        }
        requestAnimationFrame(function () {
          output.scrollTop = typeof s.scroll === "number" ? s.scroll : output.scrollHeight;
        });
      } catch (e) {}
    });
  }

  new MutationObserver(persistState).observe(output, { childList: true, subtree: true });
  input.addEventListener("input", persistState);

  // Delegated chip clicks
  output.addEventListener("click", function (e) {
    var chip = e.target.closest(".cmd-chip[data-cmd]");
    if (!chip) return;
    var cmd = chip.dataset.cmd;
    if (!cmd) return;
    commandHistory.unshift(cmd);
    historyIndex = -1;
    handleCommand(cmd);
  });

  // ─── Header button wiring ───────────────────────────────
  resetBtn.addEventListener("click", function () {
    output.innerHTML =
      '<div class="line system">ANKLE protocol v2 \u2014 @\u2318 terminal by Ankle \u00B7 tap any chip to run \u00B7 type / for commands</div>' +
      '<div class="line system">Connected to ' + BASE + '</div>';
    commandHistory = [];
    historyIndex = -1;
    input.value = "";
    hideSlashMenu();
    input.focus();
    persistState();
  });

  copyBtn.addEventListener("click", function () {
    var nodes = output.querySelectorAll(".line, .hermes-bubble .b-text");
    var text = Array.prototype.map.call(nodes, function (el) { return el.textContent.trim(); })
      .filter(Boolean).join("\n");
    var done = function () {
      copyBtn.classList.add("flash");
      copyBtn.textContent = "\u2713";
      setTimeout(function () { copyBtn.classList.remove("flash"); copyBtn.textContent = "\u29C9"; }, 1200);
    };
    try {
      if (navigator.clipboard && navigator.clipboard.writeText) {
        navigator.clipboard.writeText(text).then(done, function () { showNotification("clipboard blocked by this page"); });
      } else {
        showNotification("clipboard unavailable on this page");
      }
    } catch (e) { showNotification("clipboard blocked by this page"); }
  });

  expandBtn.addEventListener("click", function () {
    var isExp = terminal.classList.toggle("expanded");
    expandBtn.textContent = isExp ? "\u21F2" : "\u21F1";
    expandBtn.title = isExp ? "collapse" : "expand";
    persistState();
  });

  popoutBtn.addEventListener("click", function () {
    window.open(BASE + "/dashboard", "_blank");
  });

  closeBtn.addEventListener("click", toggleTerminal);
  fab.addEventListener("click", toggleTerminal);

  // Quick actions
  qaBar.addEventListener("click", function (e) {
    var btn = e.target.closest(".qa-btn");
    if (!btn) return;
    var cmd = btn.dataset.cmd;
    if (!cmd) return;
    if (["/refine", "/surf", "/collect", "/search", "/gif", "/haha"].indexOf(cmd) !== -1) {
      input.value = cmd + " ";
      input.focus();
      return;
    }
    input.value = "";
    commandHistory.unshift(cmd);
    historyIndex = -1;
    handleCommand(cmd);
  });

  // ─── Toggle + shortcut ──────────────────────────────────
  function toggleTerminal() {
    terminalOpen = !terminalOpen;
    if (terminalOpen) {
      terminal.classList.remove("closed");
      terminal.classList.add("open");
      fab.classList.add("active");
      setTimeout(function () { input.focus(); }, 100);
    } else {
      terminal.classList.remove("open");
      terminal.classList.add("closed");
      fab.classList.remove("active");
    }
    persistState();
  }

  function openTerminal()  { if (!terminalOpen) toggleTerminal(); }
  function closeTerminal() { if (terminalOpen)  toggleTerminal(); }

  // Global keyboard shortcuts
  document.addEventListener("keydown", function (e) {
    if ((e.metaKey || e.ctrlKey) && (e.key === "k" || e.key === "K")) {
      e.preventDefault();
      toggleTerminal();
      return;
    }
    // Legacy extension shortcut: Ctrl+Shift+A
    if (e.ctrlKey && e.shiftKey && (e.key === "A" || e.key === "a")) {
      e.preventDefault();
      toggleTerminal();
      return;
    }
    if (e.key === "Escape" && terminalOpen) {
      // Only swallow Escape when terminal is the focused context
      if (shadow.activeElement || document.activeElement === root) {
        closeTerminal();
      }
    }
  });

  // ─── Input handling ─────────────────────────────────────
  input.addEventListener("input", function () {
    var v = input.value;
    if (v.indexOf("/") === 0) renderSlashMenu(v.trim());
    else hideSlashMenu();
  });

  input.addEventListener("keydown", function (e) {
    if (slashOpen()) {
      var items = slashMenu.querySelectorAll(".slash-item");
      if (e.key === "ArrowDown") {
        e.preventDefault();
        slashSelIdx = (slashSelIdx + 1) % items.length;
        Array.prototype.forEach.call(items, function (el, i) { el.classList.toggle("sel", i === slashSelIdx); });
        return;
      }
      if (e.key === "ArrowUp") {
        e.preventDefault();
        slashSelIdx = (slashSelIdx - 1 + items.length) % items.length;
        Array.prototype.forEach.call(items, function (el, i) { el.classList.toggle("sel", i === slashSelIdx); });
        return;
      }
      if (e.key === "Tab") {
        e.preventDefault();
        var pick = items[slashSelIdx];
        if (pick) {
          var cmdT = pick.dataset.cmd;
          var hintT = pick.dataset.hint || "";
          input.value = cmdT + (hintT ? " " : "");
          if (!hintT) hideSlashMenu();
        }
        return;
      }
      if (e.key === "Escape") { hideSlashMenu(); return; }
    }

    if (e.key === "Enter") {
      if (slashOpen()) {
        var items2 = slashMenu.querySelectorAll(".slash-item");
        var pick2 = items2[slashSelIdx];
        if (pick2) {
          var cmdE = pick2.dataset.cmd;
          var hintE = pick2.dataset.hint || "";
          if (hintE) {
            input.value = cmdE + " ";
            hideSlashMenu();
            return;
          }
          input.value = "";
          hideSlashMenu();
          commandHistory.unshift(cmdE);
          historyIndex = -1;
          handleCommand(cmdE);
          return;
        }
      }
      var cmd = input.value.trim();
      if (!cmd) return;
      input.value = "";
      hideSlashMenu();
      commandHistory.unshift(cmd);
      historyIndex = -1;
      handleCommand(cmd);
      return;
    }

    if (e.key === "ArrowUp" && !slashOpen()) {
      e.preventDefault();
      if (historyIndex < commandHistory.length - 1) {
        historyIndex++;
        input.value = commandHistory[historyIndex];
      }
      return;
    }

    if (e.key === "ArrowDown" && !slashOpen()) {
      e.preventDefault();
      if (historyIndex > 0) {
        historyIndex--;
        input.value = commandHistory[historyIndex];
      } else {
        historyIndex = -1;
        input.value = "";
      }
      return;
    }

    if (e.key === "Escape" && !slashOpen()) {
      toggleTerminal();
    }
  });

  // ─── Auth gate ──────────────────────────────────────────
  var _authCache = { ok: false, at: 0 };
  function ensureAuth() {
    if (_authCache.ok && Date.now() - _authCache.at < 30000) return Promise.resolve(true);
    return apiCall("/api/auth/me").then(function (r) {
      var user = r && r.data && (r.data.user || (r.data.user_id ? r.data : null));
      var signedIn = !!(r.ok && user && (user.id || user.user_id || user.username));
      _authCache = { ok: signedIn, at: Date.now() };
      if (signedIn) {
        _currentUser = user;
        return true;
      }
      renderHermesBubble({
        text: "**Sign in required.** Open the one-click sign-in page — it mints a persistent token so collect, ingest, and discover work.",
        chips: [
          { label: "\u25C8 sign in now", cmd: "__signin", primary: true },
          { label: "\u2193 install field kit", cmd: "__open https://ankle.website/install" },
        ],
      });
      return false;
    });
  }

  // ─── Hermes bubble renderer ─────────────────────────────
  function renderHermesBubble(opts) {
    opts = opts || {};
    var bubble = document.createElement("div");
    bubble.className = "line hermes-bubble";
    var body = document.createElement("div");
    body.className = "b-text";

    var parsed = parseHermesTextToHTML(opts.text || "");
    body.innerHTML = parsed.html;
    bubble.appendChild(body);

    var allChips = [];
    if (Array.isArray(opts.chips)) allChips = allChips.concat(opts.chips);
    if (opts.contextualChips) {
      parsed.cmds.forEach(function (c) { allChips.push(c); });
    }

    if (allChips.length) {
      var row = document.createElement("div");
      row.className = "b-chips";
      allChips.forEach(function (c) {
        var btn = document.createElement("button");
        btn.className = "cmd-chip" + (c.primary ? " primary" : "") + (c.muted ? " muted" : "");
        btn.textContent = c.label;
        if (c.cmd) btn.dataset.cmd = c.cmd;
        row.appendChild(btn);
      });
      bubble.appendChild(row);
    }

    if (opts.provider) {
      var meta = document.createElement("div");
      meta.className = "hermes-meta";
      meta.textContent = "\u2014 " + opts.provider + "/" + (opts.model || "");
      bubble.appendChild(meta);
    }

    output.appendChild(bubble);
    output.scrollTop = output.scrollHeight;
  }

  function parseHermesTextToHTML(raw) {
    var cmds = [];
    var seen = {};
    var html = (raw || "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");

    html = html
      .replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>")
      .replace(/(^|\s)_([^_\n]+)_(?=\s|$|[.,;:!?])/g, "$1<em>$2</em>");

    html = html.replace(/@cmd\s+([a-z_\-]+)(?:\s+([^\n.`]{1,80}))?/gi, function (_m, name, rest) {
      var alias = CMD_ALIASES[(name || "").toLowerCase()] || ("/" + name.toLowerCase());
      var argStr = (rest || "").trim();
      var fullCmd = argStr ? (alias + " " + argStr) : alias;
      var key = fullCmd.toLowerCase();
      if (!seen[key]) {
        seen[key] = true;
        cmds.push({ label: fullCmd, cmd: fullCmd });
      }
      return "<em>" + fullCmd.replace(/</g, "&lt;") + "</em>";
    });

    html = html.replace(/(^|\s)(\/[a-z\-]+)(?:\s+([^\n.`]{1,60}))?/gi, function (m, lead, slash, rest) {
      var base = slash.toLowerCase();
      var known = COMMANDS.some(function (c) { return c.key === base; });
      if (!known) return m;
      var full = rest ? (base + " " + rest.trim()) : base;
      var key = full.toLowerCase();
      if (!seen[key]) {
        seen[key] = true;
        cmds.push({ label: full, cmd: full, primary: base === "/surf" || base === "/ingest" });
      }
      return lead + "<em>" + full + "</em>";
    });

    return { html: html, cmds: cmds };
  }

  function appendLine(text, type) {
    var div = document.createElement("div");
    div.className = "line " + (type || "result");
    div.textContent = text;
    output.appendChild(div);
    output.scrollTop = output.scrollHeight;
  }

  function clearThinking() {
    var lines = output.querySelectorAll(".line.system");
    var last = lines[lines.length - 1];
    if (last && last.textContent === "hermes is thinking\u2026") last.remove();
  }

  // ─── Server dispatch — POST to /atcmd/chat ──────────────
  function serverDispatch(raw) {
    appendLine("thinking\u2026", "system");
    var body = {
      input: raw,
      context_stack: [],
      page: (function () { try { return location.pathname || "/"; } catch (e) { return "/"; } })(),
      origin: ORIGIN,
    };
    try { body.page_url = location.href; body.page_title = document.title || ""; } catch (e) {}

    return apiCall("/atcmd/chat", "POST", body).then(function (r) {
      // drop the thinking line
      var lines = output.querySelectorAll(".line.system");
      var last = lines[lines.length - 1];
      if (last && last.textContent === "thinking\u2026") last.remove();

      if (r.status === 401) {
        renderHermesBubble({
          text: "**Sign in required.** This command runs against aleqth.com and needs an @\u2318 identity.",
          chips: [{ label: "\u25C8 sign in", cmd: "__signin", primary: true }],
        });
        return;
      }
      if (!r.ok) {
        appendLine("server dispatch failed: " + (r.status || r.error || "unknown"), "error");
        return;
      }
      var data = r.data || {};
      if (data.ok === false) {
        appendLine(data.message || data.error || "command failed", "error");
        return;
      }
      // Mirror atcmd-widget.js renderChatOutput shape
      if (data.results && data.results.length) {
        data.results.forEach(function (res) {
          var msg = res.message || res.output || "Done.";
          if (typeof msg === "object") msg = JSON.stringify(msg);
          renderHermesBubble({ text: String(msg), contextualChips: true });
          if (res.output && res.output.type === "redirect" && res.output.url) {
            var u = res.output.url;
            var full = u.indexOf("http") === 0 ? u : (BASE + u);
            appendLine("opening " + full, "system");
            window.open(full, "_blank");
          }
        });
      } else if (data.message) {
        renderHermesBubble({ text: String(data.message), contextualChips: true });
      } else if (data.response || data.output) {
        renderHermesBubble({ text: String(data.response || data.output), contextualChips: true });
      } else {
        appendLine("(no response)", "system");
      }

      if (Array.isArray(data.suggestions) && data.suggestions.length) {
        var row = document.createElement("div");
        row.className = "b-chips";
        data.suggestions.forEach(function (s) {
          var btn = document.createElement("button");
          btn.className = "cmd-chip";
          btn.textContent = s;
          btn.dataset.cmd = s;
          row.appendChild(btn);
        });
        output.appendChild(row);
      }
    });
  }

  // ─── Local intent router ────────────────────────────────
  // Matches common navigation and page-action intents so they dispatch
  // immediately without burning an LLM call. Returns true if handled.
  var KNOWN_ROUTES = {
    home: "/", dashboard: "/dashboard", surf: "/surf", atlas: "/atlas",
    world: "/world", space: "/space", reel: "/reel", dada: "/dada",
    market: "/market", gallery: "/gallery", feed: "/feed", ankle: "/ankle",
    studio: "/studio", terminal: "/terminal", botta: "/botta",
    profile: "/profile", account: "/profile", settings: "/settings",
    "home page": "/", "homepage": "/", "the home page": "/",
  };
  function _navigate(path, label) {
    appendLine("→ navigating to " + (label || path), "system");
    try { window.top.location.href = path; }
    catch (e) { try { window.location.href = path; } catch (_e) {} }
  }
  function _clickSaveOnHost() {
    // Try several plausible save controls on the host page.
    var selectors = ["#saveBtn", "[data-action='save']", "button.save", "[aria-label='Save']"];
    for (var i = 0; i < selectors.length; i++) {
      try {
        var el = window.top.document.querySelector(selectors[i]);
        if (el) { el.click(); return true; }
      } catch (_e) {}
    }
    return false;
  }
  function tryLocalIntent(raw) {
    var s = (raw || "").trim().toLowerCase().replace(/[?.!]+$/, "");
    if (!s) return false;

    // Back / forward / reload
    if (/^(go\s+)?back$/.test(s))            { try { window.top.history.back(); } catch (_e) { history.back(); } return true; }
    if (/^(go\s+)?forward$/.test(s))         { try { window.top.history.forward(); } catch (_e) { history.forward(); } return true; }
    if (/^(reload|refresh)( the page)?$/.test(s)) { try { window.top.location.reload(); } catch (_e) { location.reload(); } return true; }

    // Save / publish on host page
    if (/^(save|save it|save this|save the page|publish|publish it)$/.test(s)) {
      if (_clickSaveOnHost()) { appendLine("✓ save triggered on host", "system"); return true; }
      // If no save control is present, fall through to Hermes.
      return false;
    }

    // Direct single-word match against known routes
    if (KNOWN_ROUTES.hasOwnProperty(s)) { _navigate(KNOWN_ROUTES[s], s); return true; }

    // Navigation phrases: "take me to <x>", "go to <x>", "open <x>", "show me <x>", "navigate to <x>", "lets go to <x>"
    var m = s.match(/^(?:take me (?:to|home)|take me to the|bring me to|go(?: to)?|lets? go(?: to)?|let's go(?: to)?|open|show me|navigate to|head (?:to|back to)|jump (?:to|back to)|can you take me (?:to|home)(?: page)?|can you take me to the)\s+(.+?)$/);
    if (m) {
      var target = m[1].trim().replace(/^(the|a|an)\s+/, "").replace(/\s+page$/, "");
      if (KNOWN_ROUTES.hasOwnProperty(target)) { _navigate(KNOWN_ROUTES[target], target); return true; }
      // "/foo/bar" style explicit path
      if (/^\/[a-z0-9\-\/]+$/i.test(target))   { _navigate(target, target); return true; }
      // bare route word with extra junk — try first word
      var first = target.split(/\s+/)[0];
      if (KNOWN_ROUTES.hasOwnProperty(first))  { _navigate(KNOWN_ROUTES[first], first); return true; }
    }

    // "take me home", "go home"
    if (/^(take me home|go home|head home|homepage)$/.test(s)) { _navigate("/", "home"); return true; }

    // Absolute path shortcuts ("/surf", "/world/…")
    if (/^\/[a-z0-9\-\/]+$/i.test(s))          { _navigate(s, s); return true; }

    return false;
  }

  // ─── Hermes direct query ────────────────────────────────
  function hermesQuery(message) {
    if (!FIRST_PARTY) {
      serverDispatch(message);
      return;
    }
    appendLine("hermes is thinking\u2026", "system");

    var pageUrl = (function () { try { return location.href; } catch (e) { return ""; } })();
    var pageTitle = (function () { try { return document.title; } catch (e) { return ""; } })();
    var appProxy = BASE.indexOf("127.0.0.1:18484/proxy") !== -1 || BASE.indexOf("localhost:18484/proxy") !== -1;
    var payload = {
      message: message,
      contexts: [
        pageTitle ? "page:" + pageTitle : "",
        pageUrl ? "url:" + pageUrl : "",
        "origin:" + ORIGIN,
        "surface:" + (appProxy ? "ankle-app" : "terminal-v2"),
      ].filter(Boolean),
    };

    var replied = false;
    // Hermes can take a while when the fast providers fall back to local ollama.
    // Keep the user informed instead of lying and bailing at 25s.
    var progressTimers = [
      setTimeout(function () { if (!replied) appendLine("still thinking\u2026 (falling back through providers)", "system"); }, 25000),
      setTimeout(function () { if (!replied) appendLine("still thinking\u2026 server load is high", "system"); }, 60000),
    ];
    var timeout = setTimeout(function () {
      if (replied) return;
      replied = true;
      clearThinking();
      appendLine("hermes timed out after 2 min \u2014 try again or run a lighter command.", "error");
    }, 120000);

    apiCall("/api/hermes/chat", "POST", payload).then(function (r) {
      if (replied) return;
      replied = true;
      clearTimeout(timeout);
      progressTimers.forEach(clearTimeout);
      clearThinking();

      if (r.status === 401) {
        renderHermesBubble({
          text: "**Sign in required.** Hermes needs an @\u2318 identity to answer.",
          chips: [{ label: "\u25C8 sign in", cmd: "__signin", primary: true }],
        });
        return;
      }
      if (!r.ok) {
        appendLine("hermes unavailable (" + (r.status || r.error || "") + ")", "error");
        return;
      }
      var data = r.data || {};
      if (data.ok === false) {
        renderHermesBubble({
          text: String(data.message || data.error || "Hermes could not complete that request."),
          chips: data.buy_url ? [{ label: "buy compute credits", cmd: "__open " + BASE + data.buy_url, primary: true }] : [],
        });
        return;
      }
      var text = data.response || data.output || data.message || "\u2026";
      renderHermesBubble({
        text: String(text),
        provider: data.provider,
        model: data.model,
        contextualChips: true,
      });
    });
  }

  // ─── Command handler ────────────────────────────────────
  function handleCommand(cmd) {
    appendLine(cmd, "cmd");
    var parts = cmd.split(/\s+/);
    var head = parts[0];
    var args = parts.slice(1).join(" ").trim();

    // /help
    if (cmd === "/help") {
      COMMANDS.forEach(function (c) {
        var pad = c.key;
        while (pad.length < 18) pad += " ";
        appendLine(pad + " \u2014 " + c.desc, "system");
      });
      return;
    }

    if (cmd === "/clear") {
      output.innerHTML = "";
      return;
    }

    if (cmd === "/install" || cmd === "/download") {
      window.open("https://ankle.website/install", "_blank");
      return;
    }

    if (cmd === "/credits" || cmd === "/topup" || cmd === "/top-up") {
      window.open(BASE + "/credits/buy", "_blank");
      return;
    }

    if (cmd === "/status") {
      apiCall("/api/status").then(function (r) {
        if (r.ok && r.data) appendLine("status: " + (r.data.status || "ok"), "result");
        else appendLine("Could not reach server", "error");
      });
      return;
    }

    // /surf [query] — with query: search archive. Without: deep-surf this page.
    if (head === "/surf") {
      if (args) {
        window.open(BASE + "/surf?q=" + encodeURIComponent(args), "_blank");
        appendLine('Opened SURF for "' + args + '"', "system");
        return;
      }
      var pageUrl = location.href;
      var pageHost = location.hostname.replace(/^www\./, "");
      var imgs = Array.prototype.slice.call(document.querySelectorAll("img"))
        .map(function (i) { return i.currentSrc || i.src; })
        .filter(function (u) { return u && /^https?:/.test(u); });
      var imgCount = new Set(imgs).size;
      var curBare = pageUrl.split("#")[0];
      var linkMap = new Map();
      var anchors = document.querySelectorAll("a[href]");
      for (var ai = 0; ai < anchors.length; ai++) {
        var a = anchors[ai];
        var href = a.href;
        if (!/^https?:/.test(href)) continue;
        if (href.split("#")[0] === curBare) continue;
        if (linkMap.has(href)) continue;
        var text = (a.innerText || a.textContent || "").trim().replace(/\s+/g, " ");
        linkMap.set(href, text.slice(0, 48));
      }
      var links = Array.from(linkMap.entries()).slice(0, 10);
      var waybackUrl = "https://web.archive.org/web/*/" + pageUrl;
      var waybackHost = "https://web.archive.org/web/*/" + pageHost + "/*";
      var descEl = document.querySelector('meta[name="description"]');
      var desc = descEl ? (descEl.content || "") : "";

      var chips = [];
      if (imgCount) chips.push({ label: "≈ send " + imgCount + " imgs to /surf", cmd: "/surf-images", primary: true });
      chips.push({ label: "↓ ingest as capsule", cmd: "/ingest" });
      if (imgCount) chips.push({ label: "◈ collect " + imgCount + " to inventory", cmd: "/collect-images" });
      chips.push({ label: "? page context", cmd: "/context" });
      chips.push({ label: "↩ wayback this page", cmd: "__open " + waybackUrl });
      chips.push({ label: "↩ wayback " + pageHost, cmd: "__open " + waybackHost });
      for (var li = 0; li < links.length; li++) {
        var lhref = links[li][0];
        var ltext = links[li][1];
        var lhost;
        try { lhost = new URL(lhref).hostname.replace(/^www\./, ""); } catch (e) { lhost = lhref.slice(0, 30); }
        var llabel = ltext || lhost;
        chips.push({ label: "→ /surf " + llabel.slice(0, 30), cmd: "__open " + lhref });
      }

      renderHermesBubble({
        text: "**deep surf · " + pageHost + "**\n" +
              "_" + (document.title || "(untitled)") + "_\n" +
              (desc ? desc.slice(0, 200) + "\n" : "") +
              "\n**" + imgCount + "** images · **" + links.length + "** outbound links",
        chips: chips,
      });
      return;
    }

    // /context — Hermes summarizes what this page is/was
    if (cmd === "/context") {
      appendLine("analyzing page context…", "system");
      var bodyText = ((document.body && document.body.innerText) || "").replace(/\s+/g, " ").slice(0, 1200);
      var descEl2 = document.querySelector('meta[name="description"]');
      var desc2 = descEl2 ? (descEl2.content || "") : "";
      var prompt =
        "Summarize this web page in 3-4 sentences: what is it, when was it likely made (era/decade), what aesthetic, what was its purpose, and anything notable.\n\n" +
        "URL: " + location.href + "\n" +
        "Title: " + document.title + "\n" +
        (desc2 ? "Description: " + desc2 + "\n" : "") +
        "\nFirst 1200 chars of body:\n" + bodyText;
      apiCall("/api/hermes/chat", "POST", {
        message: prompt,
        contexts: ["intent:page-context", "origin:" + ORIGIN, "surface:terminal-v2"],
      }).then(function (rctx) {
        if (!rctx.ok) { appendLine("context unavailable", "error"); return; }
        if (rctx.data && rctx.data.ok === false) {
          appendLine(rctx.data.message || rctx.data.error || "context unavailable", "error");
          return;
        }
        var reply = (rctx.data && (rctx.data.reply || rctx.data.response || rctx.data.text || rctx.data.message)) || "(no response)";
        renderHermesBubble({
          text: reply,
          chips: [
            { label: "↓ ingest as capsule", cmd: "/ingest", primary: true },
            { label: "≈ send images to /surf", cmd: "/surf-images" },
          ],
        });
      });
      return;
    }

    if (cmd === "/remix") {
      window.open(BASE + "/remix?url=" + encodeURIComponent(location.href), "_blank");
      appendLine("Opened remix", "system");
      return;
    }
    if (cmd === "/verify") {
      window.open(BASE + "/verify?url=" + encodeURIComponent(location.href), "_blank");
      appendLine("Opened verify", "system");
      return;
    }
    if (cmd === "/dada") {
      window.open(BASE + "/dada", "_blank");
      appendLine("Opened /dada", "system");
      return;
    }

    // /ingest
    if (cmd === "/ingest") {
      ensureAuth().then(function (ok) {
        if (!ok) return;
        appendLine("ingesting current page\u2026", "system");
        apiCall("/api/surf/ingest-page", "POST", {
          url: location.href,
          title: document.title || "",
          source: location.hostname,
          tags: ["embed-v2", "manual-ingest"],
        }).then(function (r) {
          if (r.status === 401) { appendLine("sign in required — use the sign-in action", "error"); return; }
          var data = r.data || {};
          if (r.ok && data.ok) {
            var flagged = data.status === "flagged-for-review" || (data.item && data.item.pending_review);
            renderHermesBubble({
              text: flagged
                ? "**Flagged for review** \u2014 page matched NSFW heuristic. Won't enter the public network until approved.\n_" + ((data.item && data.item.name) || location.href) + "_"
                : "**Ingested** \u2192 " + ((data.item && data.item.name) || location.href) + "\n_layer: " + ((data.item && data.item.layer) || "chaos") + "_",
              chips: flagged ? [
                { label: "\u25C8 view pending queue", cmd: "__open " + BASE + "/surf?pending=1", primary: false },
              ] : [
                { label: "\u2193 ingest another", cmd: "/ingest" },
                { label: "\u2248 view on /surf", cmd: "/surf " + location.hostname },
                { label: "\u25CE discover adjacent", cmd: "/discover", primary: true },
              ],
            });
          } else {
            appendLine("ingest failed: " + (data.detail || data.error || r.status || r.error), "error");
          }
        });
      });
      return;
    }

    // /collect <url>
    if (head === "/collect") {
      ensureAuth().then(function (ok) {
        if (!ok) return;
        var url = args || location.href;
        appendLine("collecting " + url.slice(0, 80) + "\u2026", "system");
        var ext = "";
        var m = url.match(/\.([a-z0-9]{2,5})(?:$|\?)/i);
        if (m) ext = m[1];
        var host = "";
        try { host = new URL(url, location.href).hostname.replace(/^www\./, ""); } catch (e) {}
        apiCall("/api/inventory/collect", "POST", {
          source_url: url,
          name: (url.split("/").pop() || "item").slice(0, 80),
          ext: ext,
          source_site: host,
          category: "image",
          meta: { page_url: location.href, page_title: document.title, origin: "terminal-v2" },
          tags: ["embed-v2"],
        }).then(function (r) {
          if (r.status === 401) { appendLine("sign in required — use the sign-in action", "error"); return; }
          var d = r.data || {};
          if (d.status === "already_collected") appendLine("already in inventory", "system");
          else if (d.status === "flagged-for-review") appendLine("flagged for review (NSFW heuristic) \u2014 not added", "error");
          else if (r.ok) appendLine("collected \u2192 inventory", "result");
          else appendLine("collect failed: " + (r.status || r.error), "error");
        });
      });
      return;
    }

    // Hard ceiling for batch operations \u2014 protects users from hammering the
    // server when a page has thousands of <img> tags. Pages can be larger
    // than this, but anything past it gets truncated with a notice. Was 40.
    var BATCH_LIMIT = 500;

    // Build a compact "from {page}" tag so each result bubble identifies its
    // source page \u2014 makes sequential runs distinguishable in the transcript.
    function _pageTag() {
      var host = location.hostname.replace(/^www\./, "");
      var path = (location.pathname || "/").slice(0, 28);
      var ttl = (document.title || "").trim().slice(0, 40);
      return ttl ? (ttl + " \u00B7 " + host + path) : (host + path);
    }

    // /collect-images
    if (cmd === "/collect-images") {
      ensureAuth().then(function (ok) {
        if (!ok) return;
        var imgs = Array.prototype.map.call(document.querySelectorAll("img"), function (i) { return i.currentSrc || i.src; })
          .filter(function (u) { return u && /^https?:/.test(u); });
        var allUnique = Array.from(new Set(imgs));
        var truncated = allUnique.length > BATCH_LIMIT;
        var unique = truncated ? allUnique.slice(0, BATCH_LIMIT) : allUnique;
        if (!unique.length) { appendLine("no images found on page", "error"); return; }
        appendLine("collecting " + unique.length + " image(s)" + (truncated ? " (capped from " + allUnique.length + ")" : "") + " from " + _pageTag() + "\u2026", "system");
        var pageTag = _pageTag();
        var okc = 0, dup = 0, fail = 0, flagged = 0, done = 0;
        var chain = Promise.resolve();
        unique.forEach(function (u, idx) {
          chain = chain.then(function () {
            var ext = "";
            var mm = u.match(/\.([a-z0-9]{2,5})(?:$|\?)/i);
            if (mm) ext = mm[1].toLowerCase();
            return apiCall("/api/inventory/collect", "POST", {
              source_url: u,
              name: (u.split("/").pop() || "image").slice(0, 80),
              ext: ext,
              source_site: location.hostname.replace(/^www\./, ""),
              category: "image",
              meta: { page_url: location.href, page_title: document.title, origin: "terminal-v2-batch" },
              tags: ["embed-v2", "batch"],
            }).then(function (r) {
              done++;
              if (r.status === 401) { fail++; return; }
              var d = r.data || {};
              if (d.status === "already_collected") dup++;
              else if (d.status === "flagged-for-review") flagged++;
              else if (r.ok) okc++;
              else fail++;
              // Heartbeat every 50 items so big batches don't look frozen.
              if (done % 50 === 0 && done < unique.length) {
                appendLine("  \u2026 " + done + "/" + unique.length + " (ok " + okc + " dup " + dup + ")", "system");
              }
            });
          });
        });
        chain.then(function () {
          renderHermesBubble({
            text: "**Inventory \u00B7 " + pageTag + "**\n" +
                  "Collected " + okc + " \u00B7 dup " + dup +
                  (flagged ? " \u00B7 flagged " + flagged : "") +
                  (fail ? " \u00B7 fail " + fail : "") +
                  (truncated ? "\n_(capped at " + BATCH_LIMIT + " of " + allUnique.length + " \u2014 re-run after collecting more, or use /surf-recursive)_" : ""),
            chips: [
              { label: "\u25C8 open inventory", cmd: "__open " + BASE + "/dashboard#inventory", primary: true },
              { label: "\u2248 send these to /surf", cmd: "/surf-images" },
              { label: "\u2193 also ingest page", cmd: "/ingest" },
            ].concat(flagged ? [{ label: "\u25C9 review flagged", cmd: "__open " + BASE + "/dashboard#inventory", muted: true }] : []),
          });
        });
      });
      return;
    }

    // /surf-images
    if (cmd === "/surf-images") {
      ensureAuth().then(function (ok) {
        if (!ok) return;
        var imgs = Array.prototype.map.call(document.querySelectorAll("img"), function (i) { return i.currentSrc || i.src; })
          .filter(function (u) { return u && /^https?:/.test(u); });
        var allUnique = Array.from(new Set(imgs));
        var truncated = allUnique.length > BATCH_LIMIT;
        var unique = truncated ? allUnique.slice(0, BATCH_LIMIT) : allUnique;
        if (!unique.length) { appendLine("no images found on page", "error"); return; }
        appendLine("sending " + unique.length + " image(s)" + (truncated ? " (capped from " + allUnique.length + ")" : "") + " to /surf from " + _pageTag() + "\u2026", "system");
        var pageTag = _pageTag();
        var pageHost = location.hostname.replace(/^www\./, "");
        var okc = 0, dup = 0, fail = 0, done = 0;
        var chain = Promise.resolve();
        unique.forEach(function (u) {
          chain = chain.then(function () {
            return apiCall("/api/surf/ingest", "POST", {
              url: u,
              source: location.href,
              title: document.title || "",
              description: "",
              tags: ["embed-v2", pageHost],
              extra_meta: { page_url: location.href, page_title: document.title, origin: "terminal-v2-surf-batch" },
            }).then(function (r) {
              done++;
              if (r.status === 401) { fail++; return; }
              var d = r.data || {};
              if (d.duplicate) dup++;
              else if (r.ok) okc++;
              else fail++;
              if (done % 50 === 0 && done < unique.length) {
                appendLine("  \u2026 " + done + "/" + unique.length + " (added " + okc + " dup " + dup + ")", "system");
              }
            });
          });
        });
        chain.then(function () {
          renderHermesBubble({
            text: "**/surf \u00B7 " + pageTag + "**\n" +
                  "added " + okc + " \u00B7 dup " + dup +
                  (fail ? " \u00B7 fail " + fail : "") +
                  (truncated ? "\n_(capped at " + BATCH_LIMIT + " of " + allUnique.length + ")_" : ""),
            chips: [
              { label: "\u2248 open /surf", cmd: "__open " + BASE + "/surf", primary: true },
              { label: "\u25C8 also collect to inventory", cmd: "/collect-images" },
              { label: "\u2192 recursive /surf this site", cmd: "/surf-recursive 2" },
            ],
          });
        });
      });
      return;
    }

    // /surf-recursive [depth] \u2014 server-side recursive crawl from this page.
    // Extension fires the request, the server walks links, scrapes images,
    // and ingests them. Returns a job_id so we can poll progress.
    if (head === "/surf-recursive") {
      ensureAuth().then(function (ok) {
        if (!ok) return;
        var depth = Math.max(1, Math.min(3, parseInt(args || "2", 10) || 2));
        var startTag = _pageTag();
        appendLine("queueing recursive /surf \u2014 depth " + depth + " from " + startTag + "\u2026", "system");
        apiCall("/api/surf/ingest-recursive", "POST", {
          url: location.href,
          depth: depth,
          same_host_only: depth >= 2,  // depth 1 = just this page; 2-3 = follow links on same host
          page_title: document.title,
        }).then(function (r) {
          if (r.status === 401) { appendLine("auth required — use the sign-in action", "error"); return; }
          var d = r.data || {};
          if (!r.ok) {
            renderHermesBubble({
              text: "**recursive /surf failed**\n" + (d.detail || d.error || "server error"),
              chips: [{ label: "retry", cmd: "/surf-recursive " + depth }],
            });
            return;
          }
          renderHermesBubble({
            text: "**queued \u00B7 " + startTag + "**\n" +
                  "depth " + depth + " \u00B7 job: `" + (d.job_id || "unknown") + "`\n" +
                  (d.note || "results stream into /surf as the crawl progresses"),
            chips: [
              { label: "\u2248 open /surf", cmd: "__open " + BASE + "/surf", primary: true },
              { label: "check status", cmd: "/surf-recursive-status " + (d.job_id || "") },
            ],
          });
        });
      });
      return;
    }
    if (head === "/surf-recursive-status" && args) {
      apiCall("/api/surf/ingest-recursive/" + encodeURIComponent(args), "GET").then(function (r) {
        var d = r.data || {};
        renderHermesBubble({
          text: "**job " + args + "**\n" +
                "pages: " + (d.pages_done || 0) + "/" + (d.pages_total || "?") + "\n" +
                "images: ok " + (d.imgs_ok || 0) + " \u00B7 dup " + (d.imgs_dup || 0) + " \u00B7 fail " + (d.imgs_fail || 0),
          chips: [{ label: "refresh", cmd: "/surf-recursive-status " + args }],
        });
      });
      return;
    }

    // /refine <q>
    if (head === "/refine") {
      var refq = args;
      if (!refq) { appendLine("usage: /refine <new query>", "error"); return; }
      try {
        var u = new URL(location.href);
        var qparam = ["q", "query", "search", "s", "k"].find(function (p) { return u.searchParams.has(p); });
        if (qparam) {
          u.searchParams.set(qparam, refq);
          appendLine("rewriting ?" + qparam + "=" + refq + "\u2026", "system");
          location.href = u.toString();
          return;
        }
      } catch (e) {}
      var candidates = Array.prototype.slice.call(document.querySelectorAll(
        "input[type=search], input[name=q], input[name=query], input[name=search], input[type=text][placeholder*=search i]"
      ));
      if (candidates.length) {
        var inp = candidates[0];
        inp.focus();
        inp.value = refq;
        inp.dispatchEvent(new Event("input", { bubbles: true }));
        var form = inp.closest("form");
        if (form) { appendLine("filled search input \u00B7 submitting\u2026", "system"); form.submit(); return; }
        appendLine("filled search input \u2014 press Enter on the page to submit", "system");
        return;
      }
      appendLine("no search param or input found \u2014 opening /surf instead", "system");
      window.open(BASE + "/surf?q=" + encodeURIComponent(refq), "_blank");
      return;
    }

    // /discover
    if (cmd === "/discover") {
      ensureAuth().then(function (ok) {
        if (!ok) return;
        appendLine("asking the system for adjacent URLs\u2026", "system");
        apiCall("/api/whoami").then(function (who) {
          var uid = who.data && who.data.user_id;
          if (!uid) { appendLine("sign in to use discover", "error"); return; }
          apiCall("/api/surf/discover-adjacent", "POST", {
            user_id: uid, max_per_tag: 2, max_total: 8,
          }).then(function (r) {
            var d = r.data || {};
            if (!r.ok || !d.ok) { appendLine("discover failed", "error"); return; }
            var chips = (d.candidates || []).slice(0, 6).map(function (c) {
              return {
                label: "+ " + ((c.title || c.url) || "").slice(0, 40) + " \u00B7 " + (c.reason || ""),
                cmd: "__open " + c.url,
              };
            });
            chips.push({ label: "\u25CE auto-ingest all", cmd: "/discover-ingest", primary: true });
            var topTags = ((d.taste_summary && d.taste_summary.top_tags) || []).map(function (t) { return t.value; }).join(" \u00B7 ");
            renderHermesBubble({
              text: "**" + (d.candidates ? d.candidates.length : 0) + " candidates** from taste: " + topTags,
              chips: chips,
            });
          });
        });
      });
      return;
    }

    if (cmd === "/discover-ingest") {
      ensureAuth().then(function (ok) {
        if (!ok) return;
        appendLine("auto-ingesting adjacents\u2026", "system");
        apiCall("/api/whoami").then(function (who) {
          var uid = who.data && who.data.user_id;
          if (!uid) { appendLine("sign in first", "error"); return; }
          apiCall("/api/surf/discover-adjacent", "POST", {
            user_id: uid, max_total: 10, auto_ingest: true,
          }).then(function (r) {
            var n = ((r.data || {}).ingested || []).length;
            appendLine("ingested " + n + " adjacents", "result");
          });
        });
      });
      return;
    }

    // /taste
    if (cmd === "/taste") {
      ensureAuth().then(function (ok) {
        if (!ok) return;
        apiCall("/api/surf/taste").then(function (r) {
          if (r.status === 401) { appendLine("sign in to see your taste", "error"); return; }
          var d = r.data || {};
          var tags = (d.top_tags || []).slice(0, 8).map(function (t) { return t.value; });
          var doms = (d.top_domains || []).slice(0, 5).map(function (x) { return x.value; });
          renderHermesBubble({
            text: "**Your taste** (from " + (d.sample_size || 0) + " ingests)\n" +
                  "_tags:_ " + (tags.join(" \u00B7 ") || "(none yet)") + "\n" +
                  "_domains:_ " + (doms.join(" \u00B7 ") || "(none yet)"),
            chips: tags.slice(0, 5).map(function (t) {
              return { label: "\u2248 /surf " + t, cmd: "/surf " + t };
            }).concat([{ label: "\u25CE discover adjacent", cmd: "/discover", primary: true }]),
          });
        });
      });
      return;
    }

    // __open <url>
    if (head === "__open") {
      if (args) window.open(args, "_blank");
      return;
    }

    if (head === "__signin") {
      _openIdentityPopup();
      return;
    }

    // /hermes <msg> or plain text → try local intent router first, then Hermes
    var isHermes = head === "/hermes" || cmd.charAt(0) !== "/";
    var hermesMessage = head === "/hermes" ? args : cmd;

    if (isHermes) {
      // Try local intent routing before burning a Hermes call on navigation
      // or simple page actions. Saves latency + cost + survives LLM outages.
      if (head !== "/hermes" && tryLocalIntent(hermesMessage)) return;
      hermesQuery(hermesMessage);
      return;
    }

    // Server-dispatched commands (/balance /search /explore /discourse /enrich /gif /radio /haha /feed, etc.)
    if (SERVER_DISPATCH.indexOf(head) !== -1) {
      serverDispatch(cmd);
      return;
    }

    // Unknown slash command — fall through to server so the backend can handle new verbs.
    if (head.charAt(0) === "/") {
      serverDispatch(cmd);
      return;
    }

    appendLine("unknown command: " + head + ". type /help", "error");
  }

  // ─── bounded cross-origin sign-in handshake ──────────────
  var _identityPopup = null;
  var _identityPendingUntil = 0;

  function _launchIdentityOAuth(origin, overlay) {
    var params = new URLSearchParams(Object.assign({
      next: "popup:" + origin,
      age_confirmed: "true",
      terms_accepted: "true",
      privacy_acknowledged: "true",
      acceptable_use_accepted: "true"
    }, LEGAL_POLICY));
    _identityPendingUntil = Date.now() + (10 * 60 * 1000);
    _identityPopup = window.open(
      BASE + "/oauth/google?" + params.toString(),
      "ankle-signin",
      "width=500,height=650,menubar=no,toolbar=no"
    );
    if (!_identityPopup) {
      _identityPendingUntil = 0;
      showNotification("allow the sign-in popup and try again");
    } else if (overlay) {
      overlay.remove();
    }
  }

  function _openIdentityPopup() {
    if (!FIRST_PARTY) {
      window.open(BASE + "/login", "_blank", "noopener");
      showNotification("sign in on aleqth.com; external embeds stay public-only");
      return;
    }
    if (document.getElementById("ankle-terminal-legal-overlay")) return;

    if (!document.getElementById("ankle-terminal-legal-style")) {
      var style = document.createElement("style");
      style.id = "ankle-terminal-legal-style";
      style.textContent = [
        "#ankle-terminal-legal-overlay{position:fixed;inset:0;z-index:2147483647;background:rgba(0,0,0,.72);display:flex;align-items:center;justify-content:center;padding:14px;box-sizing:border-box;font-family:'Fragment Mono',monospace}",
        "#ankle-terminal-legal-card{width:360px;max-width:100%;max-height:calc(100vh - 28px);overflow:auto;background:#0a0a0a;color:#eee;border:1px solid #34c759;padding:22px;box-shadow:8px 8px 0 rgba(0,0,0,.45);box-sizing:border-box}",
        "#ankle-terminal-legal-card h2{font:600 17px/1.2 inherit;margin:0 0 7px;color:#fff}",
        "#ankle-terminal-legal-card p{font:11px/1.5 inherit;color:#999;margin:0 0 12px}",
        ".ankle-terminal-legal-check{display:flex;align-items:flex-start;gap:8px;font:11px/1.45 inherit;margin:9px 0;cursor:pointer}",
        ".ankle-terminal-legal-check input{margin:2px 0 0;accent-color:#34c759;flex:0 0 auto}",
        ".ankle-terminal-legal-check a{color:#34c759;text-decoration:underline;text-underline-offset:2px}",
        ".ankle-terminal-legal-actions{display:flex;gap:8px;margin-top:16px}",
        ".ankle-terminal-legal-actions button{flex:1;border:1px solid #34c759;background:#111;color:#fff;padding:10px;font:11px/1 inherit;cursor:pointer}",
        ".ankle-terminal-legal-actions button[data-primary]{background:#34c759;color:#000;font-weight:700}",
        "#ankle-terminal-legal-error{min-height:15px;color:#ff5c5c;font:10px/1.4 inherit;margin-top:8px}"
      ].join("\n");
      document.head.appendChild(style);
    }

    var overlay = document.createElement("div");
    overlay.id = "ankle-terminal-legal-overlay";
    overlay.innerHTML = '<div id="ankle-terminal-legal-card" role="dialog" aria-modal="true" aria-labelledby="ankle-terminal-legal-title">' +
      '<h2 id="ankle-terminal-legal-title">Sign in to ANKLE</h2>' +
      '<p>Existing members sign in normally. If this creates an account, these acknowledgments and the current policy versions are recorded.</p>' +
      '<label class="ankle-terminal-legal-check"><input type="checkbox" data-legal="age"><span>I confirm I am 18 or older.</span></label>' +
      '<label class="ankle-terminal-legal-check"><input type="checkbox" data-legal="terms"><span>I agree to the <a href="https://aleqth.com/terms" target="_blank" rel="noopener">Terms</a>.</span></label>' +
      '<label class="ankle-terminal-legal-check"><input type="checkbox" data-legal="privacy"><span>I acknowledge the <a href="https://aleqth.com/privacy" target="_blank" rel="noopener">Privacy Notice</a>.</span></label>' +
      '<label class="ankle-terminal-legal-check"><input type="checkbox" data-legal="aup"><span>I agree to the <a href="https://aleqth.com/acceptable-use" target="_blank" rel="noopener">Community Rules</a>.</span></label>' +
      '<div id="ankle-terminal-legal-error" aria-live="polite"></div>' +
      '<div class="ankle-terminal-legal-actions"><button type="button" data-close>cancel</button><button type="button" data-primary>continue with Google</button></div>' +
      '</div>';
    document.body.appendChild(overlay);
    overlay.querySelector("[data-close]").onclick = function () { overlay.remove(); };
    overlay.onclick = function (event) { if (event.target === overlay) overlay.remove(); };
    overlay.querySelector("[data-primary]").onclick = function () {
      var required = ["age", "terms", "privacy", "aup"];
      var missing = required.some(function (name) {
        var input = overlay.querySelector('[data-legal="' + name + '"]');
        return !input || !input.checked;
      });
      if (missing) {
        overlay.querySelector("#ankle-terminal-legal-error").textContent = "Acknowledge all four items to continue.";
        return;
      }
      var origin = "";
      try { origin = location.origin; } catch (e) {}
      _launchIdentityOAuth(origin, overlay);
    };
  }

  window.addEventListener("message", function (e) {
    try {
      if (!FIRST_PARTY) return;
      if (!e.data || typeof e.data !== "object") return;
      var d = e.data;
      if (!_identityPopup || e.source !== _identityPopup || Date.now() > _identityPendingUntil) return;
      var trusted = false;
      try {
        var sourceURL = new URL(e.origin || "");
        trusted = (
          sourceURL.protocol === "https:" &&
          (sourceURL.hostname === "aleqth.com" || sourceURL.hostname === "www.aleqth.com" ||
           sourceURL.hostname === "ankle.website" || sourceURL.hostname === "www.ankle.website")
        ) || (
          sourceURL.protocol === "http:" &&
          (sourceURL.hostname === "localhost" || sourceURL.hostname === "127.0.0.1")
        );
      } catch (parseError) {}

      if (d.type === "ankle-identity" && _validIdentityToken(d.token) && trusted) {
        setToken(d.token, d.user || null);
        _currentUser = d.user || null;
        _identityPendingUntil = 0;
        _identityPopup = null;
        showNotification("@\u2318 signed in");
        return;
      }
    } catch (err) {}
  });

  // ─── Public API ─────────────────────────────────────────
  window.atcmd = window.atcmd || {};

  window.atcmd.me = function () {
    if (_currentUser) return JSON.parse(JSON.stringify(_currentUser));
    // Lazy fetch
    return apiCall("/api/auth/me").then(function (r) {
      var u = r && r.data && (r.data.user || (r.data.user_id ? r.data : null));
      if (u) _currentUser = u;
      return u || null;
    });
  };

  window.atcmd.call = function (path, opts) {
    opts = opts || {};
    return apiCall(path, opts.method || "GET", opts.body || null).then(function (r) {
      // Match the old widget's return shape (the parsed JSON body).
      return r.data;
    });
  };

  window.atcmd.open = function ()  { openTerminal(); };
  window.atcmd.close = function () { closeTerminal(); };
  window.atcmd.toggle = function () { toggleTerminal(); };

  window.atcmd.send = function (msg) {
    if (!msg) return;
    if (!terminalOpen) openTerminal();
    input.value = "";
    commandHistory.unshift(msg);
    historyIndex = -1;
    handleCommand(String(msg));
  };

  window.atcmd.getToken = function () { return FIRST_PARTY ? getToken() : null; };
  window.atcmd.setToken = function (t) {
    if (!FIRST_PARTY) return false;
    if (typeof t === "string") setToken(t, null);
    else if (t && typeof t === "object" && t.token) setToken(t.token, t.user || null);
    return true;
  };

  window.atcmd.signIn = function () {
    _openIdentityPopup();
  };
  window.atcmd.signOut = function () {
    clearTokens();
    showNotification("@\u2318 signed out");
  };

  // ─── Boot sequence ──────────────────────────────────────
  function boot() {
    var frag = _consumeFragmentToken();
    var bootPromise = frag ? _claimHandoff(frag) : Promise.resolve(true);

    bootPromise.then(function () {
      restoreState();
      if (!FIRST_PARTY) return;
      // Warm the auth cache so the first chip click doesn't take a round trip.
      apiCall("/api/auth/me").then(function (r) {
        var u = r && r.data && (r.data.user || (r.data.user_id ? r.data : null));
        if (r.ok && u) {
          _currentUser = u;
          _authCache = { ok: true, at: Date.now() };
        }
      });
    });
  }

  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", boot);
  } else {
    boot();
  }
})();
