Smart Stock Vault

Stock vault for Torn — spread, rebalance, withdraw, P&L, blocks, swing trading. Smart series UI.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         Smart Stock Vault
// @namespace    smart.torn.tools
// @version      10.2.12
// @description  Stock vault for Torn — spread, rebalance, withdraw, P&L, blocks, swing trading. Smart series UI.
// @author       Noobler
// @homepageURL  https://greasyfork.org/en/scripts/564798-smart-stock-vault
// @supportURL   https://www.torn.com/forums.php#/p=threads&f=67&t=16535978&b=0&a=0
// @match        https://www.torn.com/page.php?sid=stocks*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=torn.com
// @require      https://code.jquery.com/jquery-3.7.1.min.js
// @grant        GM_xmlhttpRequest
// @grant        GM.xmlHttpRequest
// @grant        unsafeWindow
// @connect      tornsy.com
// @run-at       document-idle
// @license      MIT
// ==/UserScript==


(() => {
  // src/shared/theme.js
  var tokens = {
    accent: "#caa14a",
    accentMuted: "rgba(202, 161, 74, 0.35)",
    accentSubtle: "rgba(202, 161, 74, 0.12)",
    accentText: "#f0e3c0",
    panelBg: "linear-gradient(180deg, #23252b, #1b1d22)",
    panelHeaderBg: "linear-gradient(180deg, #2c2f37, #23252b)",
    panelBorder: "#34373f",
    surface: "#15161a",
    text: "#d7d9de",
    textMuted: "#8a8d96",
    textDim: "#71747d",
    success: "#6fcf86",
    successBg: "#16341f",
    successBorder: "#2c6b3c",
    warning: "#ffd966",
    warningBg: "#332b13",
    warningBorder: "#6b5a1f",
    danger: "#ff6b6b",
    dangerBg: "#341818",
    dangerBorder: "#6b2c2c",
    info: "#8dbdf0",
    infoBg: "#15263d",
    infoBorder: "#2c4f7b",
    hover: "#d8b25c",
    font: "'Trebuchet MS', Verdana, sans-serif",
    radius: "8px",
    shadow: "0 2px 10px rgba(0, 0, 0, 0.35)",
    statDex: "#8a7ff0",
    statDef: "#e07a4f",
    statStr: "#3fae84",
    statSpd: "#4a97e6"
  };
  var STYLE_ID = "hf-torn-theme";
  function injectStyles(scopeClass = "hf-torn") {
    if (document.getElementById(STYLE_ID)) {
      return;
    }
    const css = `
    .${scopeClass} {
      --hf-accent: ${tokens.accent};
      --hf-accent-muted: ${tokens.accentMuted};
      --hf-accent-subtle: ${tokens.accentSubtle};
      --hf-accent-text: ${tokens.accentText};
      --hf-panel-bg: ${tokens.panelBg};
      --hf-panel-header-bg: ${tokens.panelHeaderBg};
      --hf-panel-border: ${tokens.panelBorder};
      --hf-surface: ${tokens.surface};
      --hf-text: ${tokens.text};
      --hf-text-muted: ${tokens.textMuted};
      --hf-text-dim: ${tokens.textDim};
      --hf-success: ${tokens.success};
      --hf-success-bg: ${tokens.successBg};
      --hf-success-border: ${tokens.successBorder};
      --hf-warning: ${tokens.warning};
      --hf-warning-bg: ${tokens.warningBg};
      --hf-warning-border: ${tokens.warningBorder};
      --hf-danger: ${tokens.danger};
      --hf-danger-bg: ${tokens.dangerBg};
      --hf-danger-border: ${tokens.dangerBorder};
      --hf-info: ${tokens.info};
      --hf-info-bg: ${tokens.infoBg};
      --hf-info-border: ${tokens.infoBorder};
      --hf-hover: ${tokens.hover};
      --hf-radius: ${tokens.radius};
      --hf-shadow: ${tokens.shadow};
      --hf-font: ${tokens.font};
      --hf-stat-dex: ${tokens.statDex};
      --hf-stat-def: ${tokens.statDef};
      --hf-stat-str: ${tokens.statStr};
      --hf-stat-spd: ${tokens.statSpd};
      color: var(--hf-text);
      font-family: var(--hf-font);
      font-size: 12px;
      line-height: 1.4;
      box-sizing: border-box;
    }

    .${scopeClass} *, .${scopeClass} *::before, .${scopeClass} *::after {
      box-sizing: border-box;
    }

    .${scopeClass} .hf-btn {
      appearance: none;
      border: 1px solid var(--hf-panel-border);
      border-radius: 5px;
      background: var(--hf-surface);
      color: var(--hf-accent-text);
      cursor: pointer;
      padding: 6px 10px;
      font: inherit;
      transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
    }

    .${scopeClass} .hf-btn:hover {
      background: var(--hf-accent-subtle);
      border-color: var(--hf-accent);
      color: #fff;
    }

    .${scopeClass} .hf-btn:disabled {
      opacity: 0.65;
      cursor: wait;
      pointer-events: none;
    }

    .${scopeClass} .hf-btn.is-active,
    .${scopeClass} .hf-btn--primary {
      background: var(--hf-accent);
      border-color: var(--hf-accent);
      color: #1b1d22;
      font-weight: 700;
    }

    .${scopeClass} .hf-btn.is-active:hover,
    .${scopeClass} .hf-btn--primary:hover {
      background: var(--hf-hover);
      border-color: var(--hf-hover);
    }

    .${scopeClass} .hf-muted {
      color: var(--hf-text-muted);
    }

    .${scopeClass} .hf-dim {
      color: var(--hf-text-dim);
    }

    .${scopeClass} .hf-badge {
      display: inline-block;
      padding: 2px 8px;
      border-radius: 4px;
      background: var(--hf-accent);
      color: #1b1d22;
      font-size: 11px;
      font-weight: 700;
      letter-spacing: 0.08em;
      text-transform: uppercase;
    }

    .${scopeClass} .hf-input,
    .${scopeClass} .hf-select {
      width: 100%;
      box-sizing: border-box;
      padding: 6px 8px;
      background: var(--hf-surface);
      border: 1px solid var(--hf-panel-border);
      color: var(--hf-accent-text);
      border-radius: 5px;
      font: inherit;
      font-size: 12px;
    }

    .${scopeClass} .hf-label {
      display: block;
      font-size: 11px;
      color: var(--hf-text-muted);
      margin: 12px 0 4px;
    }

    .${scopeClass} .hf-table {
      width: 100%;
      border-collapse: collapse;
    }

    .${scopeClass} .hf-table th,
    .${scopeClass} .hf-table td {
      padding: 4px 6px;
      text-align: left;
      border-bottom: 1px solid var(--hf-panel-border);
      color: var(--hf-text) !important;
      background: transparent !important;
    }

    .${scopeClass} .hf-table th {
      font-weight: bold;
      color: var(--hf-accent-text) !important;
    }

    .${scopeClass} .hf-table tbody tr:hover td {
      background: var(--hf-accent-subtle) !important;
    }

    .${scopeClass} .hf-table .hf-pos--1 {
      color: #e8c547 !important;
      font-weight: 700;
    }

    .${scopeClass} .hf-table .hf-pos--2 {
      color: #c4c9d4 !important;
      font-weight: 700;
    }

    .${scopeClass} .hf-table .hf-pos--3 {
      color: #c98a5a !important;
      font-weight: 700;
    }

    .${scopeClass} .hf-racing-log-toolbar {
      display: flex;
      flex-wrap: wrap;
      align-items: center;
      gap: 8px;
      margin-bottom: 10px;
    }

    .${scopeClass} .hf-racing-log-hint {
      font-size: 11px;
      color: var(--hf-text-muted);
    }

    .${scopeClass} strong {
      color: var(--hf-accent-text);
    }

    .${scopeClass} hr {
      border: none;
      border-top: 1px solid var(--hf-panel-border);
      margin: 10px 0;
    }

    .${scopeClass} .hf-section-break {
      box-sizing: border-box;
      border-top: 1px solid var(--hf-panel-border);
      margin-top: 10px;
      padding-top: 12px;
    }

    .${scopeClass} .hf-guide-row {
      margin: 0 0 4px;
    }

    .${scopeClass} .hf-guide-row:first-child {
      margin-top: 0;
    }

    .${scopeClass} .hf-racing-car-row {
      margin-bottom: 10px;
    }

    .${scopeClass} .hf-racing-live {
      margin-bottom: 10px;
      padding: 6px 8px;
      border-radius: 5px;
      background: var(--hf-accent-subtle);
      border: 1px solid var(--hf-accent-muted);
      color: var(--hf-accent-text);
      font-size: 11px;
    }

    .${scopeClass} .hf-placement-row {
      display: flex;
      flex-wrap: wrap;
      align-items: center;
      gap: 6px;
      margin: 4px 0 2px;
    }

    .${scopeClass} .hf-placement-chip {
      display: inline-flex;
      align-items: center;
      gap: 4px;
      width: auto;
      max-width: none;
      flex: 0 0 auto;
      padding: 3px 7px 3px 5px;
      border-radius: 5px;
      border: 1px solid var(--hf-panel-border);
      background: var(--hf-surface);
      font-size: 11px;
      font-weight: 700;
      font-variant-numeric: tabular-nums;
      line-height: 1;
    }

    .${scopeClass} .hf-placement-chip.is-zero {
      opacity: 0.35;
    }

    .${scopeClass} .hf-placement-chip svg.hf-placement-icon {
      width: 14px !important;
      height: 14px !important;
      min-width: 14px !important;
      max-width: 14px !important;
      min-height: 14px !important;
      max-height: 14px !important;
      flex: 0 0 14px !important;
      display: block !important;
      overflow: visible !important;
      vertical-align: middle;
    }

    .${scopeClass} .hf-placement-chip--gold {
      color: #e8c547;
      border-color: rgba(232, 197, 71, 0.35);
      background: rgba(232, 197, 71, 0.1);
    }

    .${scopeClass} .hf-placement-chip--silver {
      color: #c4c9d4;
      border-color: rgba(196, 201, 212, 0.35);
      background: rgba(196, 201, 212, 0.08);
    }

    .${scopeClass} .hf-placement-chip--bronze {
      color: #c98a5a;
      border-color: rgba(201, 138, 90, 0.35);
      background: rgba(201, 138, 90, 0.1);
    }

    .${scopeClass} .hf-placement-chip--out {
      color: var(--hf-text-muted);
      border-color: var(--hf-panel-border);
      background: rgba(0, 0, 0, 0.15);
    }

    .${scopeClass}.hf-panel {
      margin: 0 0 12px;
      border-radius: var(--hf-radius);
      background: var(--hf-panel-bg);
      border: 1px solid var(--hf-panel-border);
      box-shadow: var(--hf-shadow);
      overflow: hidden;
    }

    .${scopeClass}.hf-panel--page {
      width: 100%;
    }

    .${scopeClass}.hf-panel--fixed {
      position: fixed;
      z-index: 9999;
    }

    .${scopeClass} .hf-panel-header {
      background: var(--hf-panel-header-bg);
      border-bottom: 1px solid var(--hf-panel-border);
      border-radius: var(--hf-radius) var(--hf-radius) 0 0;
    }

    .${scopeClass} .hf-panel-tabs {
      border-bottom: 1px solid var(--hf-panel-border);
    }

    .${scopeClass} .hf-panel-body {
      padding: 12px 13px;
    }

    .${scopeClass}.hf-panel--page .hf-panel-body {
      max-height: min(70vh, 640px);
      overflow-y: auto;
    }

    .${scopeClass}.hf-panel--page.hf-panel--grow .hf-panel-body {
      max-height: none;
      overflow: visible;
    }

    .${scopeClass}.hf-overlay,
    .${scopeClass} .hf-overlay {
      position: fixed;
      inset: 0;
      background: rgba(0, 0, 0, 0.6);
      z-index: 99999;
      display: flex;
      align-items: center;
      justify-content: center;
    }

    .${scopeClass}.hf-modal,
    .${scopeClass} .hf-modal {
      background: #1f2127;
      border: 1px solid var(--hf-panel-border);
      border-radius: var(--hf-radius);
      width: min(440px, 92vw);
      max-height: 84vh;
      overflow-y: auto;
      padding: 18px;
      color: var(--hf-text);
    }

    .${scopeClass}.hf-modal h2,
    .${scopeClass} .hf-modal h2 {
      margin: 0 0 14px;
      font-size: 15px;
      color: var(--hf-accent-text);
      display: flex;
      justify-content: space-between;
      align-items: center;
    }

    .${scopeClass} .hf-mini {
      font-size: 10px;
      color: var(--hf-text-dim);
      margin-top: 3px;
      line-height: 1.4;
    }
  `;
    const style = document.createElement("style");
    style.id = STYLE_ID;
    style.textContent = css;
    document.head.appendChild(style);
  }
  var theme = {
    tokens,
    injectStyles
  };

  // src/shared/storage.js
  var PREFIX = "smart.torn";
  var LEGACY_PREFIX = "hf.torn";
  function namespaced(key) {
    return `${PREFIX}.${key}`;
  }
  function legacyNamespaced(key) {
    return `${LEGACY_PREFIX}.${key}`;
  }
  function getLocal(key, fallback = null) {
    try {
      const raw = localStorage.getItem(namespaced(key));
      if (raw !== null) {
        return JSON.parse(raw);
      }
      const legacyRaw = localStorage.getItem(legacyNamespaced(key));
      if (legacyRaw !== null) {
        localStorage.setItem(namespaced(key), legacyRaw);
        return JSON.parse(legacyRaw);
      }
      return fallback;
    } catch {
      return fallback;
    }
  }
  function setLocal(key, value) {
    localStorage.setItem(namespaced(key), JSON.stringify(value));
  }
  function removeLocal(key) {
    localStorage.removeItem(namespaced(key));
    localStorage.removeItem(legacyNamespaced(key));
  }
  var DB_NAME = "smart-torn";
  var LEGACY_DB_NAME = "heartflower-torn";
  var DB_VERSION = 1;
  var dbPromise = null;
  function openDbByName(name) {
    return new Promise((resolve, reject) => {
      const request = indexedDB.open(name, DB_VERSION);
      request.onupgradeneeded = () => {
        const db = request.result;
        if (!db.objectStoreNames.contains("races")) {
          const store = db.createObjectStore("races", { keyPath: "id" });
          store.createIndex("timestamp", "timestamp", { unique: false });
          store.createIndex("track", "track", { unique: false });
          store.createIndex("carKey", "carKey", { unique: false });
        }
      };
      request.onsuccess = () => resolve(request.result);
      request.onerror = () => reject(request.error ?? new Error("IndexedDB open failed"));
    });
  }
  async function migrateLegacyDb(target) {
    if (typeof indexedDB.databases !== "function") {
    }
    let legacy;
    try {
      legacy = await openDbByName(LEGACY_DB_NAME);
    } catch {
      return;
    }
    if (!legacy.objectStoreNames.contains("races") || !target.objectStoreNames.contains("races")) {
      legacy.close();
      return;
    }
    const rows = await new Promise((resolve, reject) => {
      const tx = legacy.transaction("races", "readonly");
      const request = tx.objectStore("races").getAll();
      request.onsuccess = () => resolve(
        /** @type {Record<string, unknown>[]} */
        request.result ?? []
      );
      request.onerror = () => reject(request.error ?? new Error("Legacy IDB read failed"));
    });
    if (rows.length > 0) {
      await new Promise((resolve, reject) => {
        const tx = target.transaction("races", "readwrite");
        const store = tx.objectStore("races");
        for (const row of rows) {
          store.put(row);
        }
        tx.oncomplete = () => resolve();
        tx.onerror = () => reject(tx.error ?? new Error("Legacy IDB migrate failed"));
      });
    }
    legacy.close();
  }
  function openDb() {
    if (dbPromise) {
      return dbPromise;
    }
    dbPromise = (async () => {
      const db = await openDbByName(DB_NAME);
      const migratedKey = "idb.migratedFromHeartflower";
      if (getLocal(migratedKey, false) !== true) {
        try {
          await migrateLegacyDb(db);
        } catch {
        }
        setLocal(migratedKey, true);
      }
      return db;
    })();
    return dbPromise;
  }
  async function withStore(storeName, mode, run) {
    const db = await openDb();
    return new Promise((resolve, reject) => {
      const tx = db.transaction(storeName, mode);
      const store = tx.objectStore(storeName);
      const request = run(store);
      request.onsuccess = () => resolve(request.result);
      request.onerror = () => reject(request.error ?? new Error("IndexedDB request failed"));
    });
  }
  async function replaceAllRecords(storeName, records) {
    const db = await openDb();
    return new Promise((resolve, reject) => {
      const tx = db.transaction(storeName, "readwrite");
      const store = tx.objectStore(storeName);
      store.clear();
      for (const record of records) {
        store.put(record);
      }
      tx.oncomplete = () => resolve();
      tx.onerror = () => reject(tx.error ?? new Error("IndexedDB replaceAll failed"));
    });
  }
  async function putRecord(storeName, record) {
    await withStore(storeName, "readwrite", (store) => store.put(record));
  }
  async function deleteRecord(storeName, key) {
    await withStore(storeName, "readwrite", (store) => store.delete(key));
  }
  async function getRecord(storeName, key) {
    return withStore(storeName, "readonly", (store) => store.get(key));
  }
  async function getAllRecords(storeName, query, limit) {
    const db = await openDb();
    return new Promise((resolve, reject) => {
      const tx = db.transaction(storeName, "readonly");
      const store = tx.objectStore(storeName);
      const request = store.getAll(query, limit);
      request.onsuccess = () => resolve(
        /** @type {Record<string, unknown>[]} */
        request.result ?? []
      );
      request.onerror = () => reject(request.error ?? new Error("IndexedDB getAll failed"));
    });
  }
  async function getByIndex(storeName, indexName, key) {
    return withStore(storeName, "readonly", (store) => store.index(indexName).getAll(key));
  }
  var storage = {
    PREFIX,
    LEGACY_PREFIX,
    getLocal,
    setLocal,
    removeLocal,
    putRecord,
    deleteRecord,
    getRecord,
    getAllRecords,
    getByIndex,
    replaceAllRecords
  };

  // src/shared/dom.js
  function sleep(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }
  async function waitFor(selector, options = {}) {
    const { timeout = 1e4, interval = 100, root = document } = options;
    const start = Date.now();
    while (Date.now() - start < timeout) {
      const el = root.querySelector(selector);
      if (el) {
        return el;
      }
      await sleep(interval);
    }
    throw new Error(`waitFor timeout: ${selector}`);
  }
  async function waitForOptional(selector, options = {}) {
    try {
      return await waitFor(selector, options);
    } catch {
      return null;
    }
  }
  function isHeaderBarNode(node) {
    if (!(node instanceof HTMLElement)) {
      return node.parentElement ? isHeaderBarNode(node.parentElement) : false;
    }
    return Boolean(node.closest(
      'a[class*="bar-link"], [class*="bar-stats"], #header, header, [class*="topBar"], [class*="top-bar"], [class*="status-bar"]'
    ));
  }
  function isHeaderBarOnlyMutations(mutations) {
    return mutations.length > 0 && mutations.every((mutation) => isHeaderBarNode(mutation.target));
  }
  function observeMutations(root, callback, options = {}) {
    const { debounce = 50, ignoreHeaderBars = true } = options;
    let timer = null;
    const observer = new MutationObserver((mutations) => {
      if (ignoreHeaderBars && isHeaderBarOnlyMutations(mutations)) {
        return;
      }
      if (timer) {
        clearTimeout(timer);
      }
      timer = setTimeout(callback, debounce);
    });
    observer.observe(root, { childList: true, subtree: true });
    callback();
    return () => {
      if (timer) {
        clearTimeout(timer);
      }
      observer.disconnect();
    };
  }
  function onAnchorClick(callback) {
    const handler = (event) => {
      const target = (
        /** @type {Element} */
        event.target
      );
      if (target.tagName === "A" || target.closest("a")) {
        setTimeout(callback, 150);
      }
    };
    document.body.addEventListener("click", handler);
    return () => document.body.removeEventListener("click", handler);
  }
  function normalizeText(text) {
    return text.replace(/\s+/g, " ").trim();
  }
  var dom = {
    sleep,
    waitFor,
    waitForOptional,
    observeMutations,
    onAnchorClick,
    normalizeText
  };

  // src/shared/mount.js
  var MOUNT_PRESETS = {
    racing: {
      waitFor: ["#racingMainContainer"],
      target: "#racingMainContainer",
      position: "before"
    },
    gym: {
      waitFor: ["#gymroot"],
      target: "#gymroot",
      position: "prepend"
    },
    gymBar: {
      waitFor: ["#gymroot"],
      target: "#gymroot",
      position: "before"
    },
    stocksBar: {
      waitFor: ["#stockmarketroot"],
      target: "#stockmarketroot",
      position: "before"
    },
    disposalBar: {
      waitFor: ['[class*="disposal-root"]'],
      target: '[class*="disposal-root"]',
      position: "before"
    },
    contentTitle: {
      waitFor: [".content-title", ".body > .content-title", "div.content-title"],
      target: ".content-title",
      position: "after"
    }
  };
  function resolveMountConfig(mount2) {
    if (!mount2) {
      return MOUNT_PRESETS.contentTitle;
    }
    if (typeof mount2 === "string") {
      if (mount2 === "page") {
        return MOUNT_PRESETS.contentTitle;
      }
      return MOUNT_PRESETS[mount2] ?? MOUNT_PRESETS.contentTitle;
    }
    return mount2;
  }
  function findMountTarget(config) {
    const pageReady = config.waitFor.some((selector) => document.querySelector(selector));
    if (!pageReady) {
      return null;
    }
    return document.querySelector(config.target);
  }
  function insertAt(target, element, position) {
    switch (position) {
      case "before":
        target.insertAdjacentElement("beforebegin", element);
        break;
      case "after":
        target.insertAdjacentElement("afterend", element);
        break;
      case "prepend":
        target.prepend(element);
        break;
      case "append":
        target.append(element);
        break;
    }
  }
  function isMountedAt(element, target, position) {
    if (!element.isConnected) {
      return false;
    }
    switch (position) {
      case "before":
        return element.nextElementSibling === target;
      case "after":
        return element.previousElementSibling === target;
      case "prepend":
        return target.firstElementChild === element;
      case "append":
        return target.lastElementChild === element;
      default:
        return false;
    }
  }
  function mountElement(config, element) {
    const target = findMountTarget(config);
    if (!target) {
      return false;
    }
    if (isMountedAt(element, target, config.position)) {
      return true;
    }
    insertAt(target, element, config.position);
    return true;
  }
  function getMountWatchRoot(config) {
    const target = findMountTarget(config);
    if (!target) {
      return null;
    }
    if (config.position === "before" || config.position === "after") {
      return target.parentElement;
    }
    return target;
  }
  function isOwnPanelMutation(mutations) {
    return mutations.every((mutation) => {
      if (mutation.target instanceof HTMLElement && mutation.target.closest(".hf-torn")) {
        return true;
      }
      for (const node of mutation.addedNodes) {
        if (node instanceof HTMLElement && (node.classList.contains("hf-torn") || node.closest(".hf-torn"))) {
          continue;
        }
        if (node.nodeType === Node.TEXT_NODE && node.parentElement?.closest(".hf-torn")) {
          continue;
        }
        return false;
      }
      for (const node of mutation.removedNodes) {
        if (node instanceof HTMLElement && (node.classList.contains("hf-torn") || node.closest(".hf-torn"))) {
          continue;
        }
        if (node.nodeType === Node.TEXT_NODE && node.parentElement?.closest(".hf-torn")) {
          continue;
        }
        return false;
      }
      return true;
    });
  }
  function watchMount(config, getElement) {
    let stopped = false;
    const start = Date.now();
    const timeoutMs = 15e3;
    let observer = null;
    let debounceTimer = null;
    let watchedRoot = null;
    const disconnectObserver = () => {
      observer?.disconnect();
      observer = null;
      watchedRoot = null;
    };
    const tryMount = () => {
      if (stopped) {
        return;
      }
      const element = getElement();
      mountElement(config, element);
    };
    const scheduleTryMount = () => {
      if (debounceTimer !== null) {
        window.clearTimeout(debounceTimer);
      }
      debounceTimer = window.setTimeout(() => {
        debounceTimer = null;
        tryMount();
      }, 80);
    };
    const attachObserver = () => {
      const watchRoot = getMountWatchRoot(config);
      if (!watchRoot || watchRoot === watchedRoot) {
        return Boolean(watchRoot);
      }
      disconnectObserver();
      watchedRoot = watchRoot;
      observer = new MutationObserver((mutations) => {
        if (isOwnPanelMutation(mutations)) {
          return;
        }
        scheduleTryMount();
      });
      observer.observe(watchRoot, {
        childList: true,
        subtree: config.position === "prepend" || config.position === "append"
      });
      return true;
    };
    const poll = window.setInterval(() => {
      tryMount();
      attachObserver();
      if (findMountTarget(config) || Date.now() - start > timeoutMs) {
        window.clearInterval(poll);
      }
    }, 250);
    tryMount();
    attachObserver();
    return () => {
      stopped = true;
      window.clearInterval(poll);
      if (debounceTimer !== null) {
        window.clearTimeout(debounceTimer);
      }
      disconnectObserver();
    };
  }
  var mount = {
    MOUNT_PRESETS,
    resolveMountConfig,
    findMountTarget,
    insertAt,
    isMountedAt,
    mountElement,
    watchMount
  };

  // src/shared/api.js
  var API_BASE = "https://api.torn.com/v2";
  var SETTINGS_KEY = "smart.torn.settings";
  var LEGACY_SETTINGS_KEY = "hf.torn.settings";
  function loadSettings() {
    try {
      let raw = GM_getValue(SETTINGS_KEY, null);
      if (raw == null || raw === "" || raw === "{}") {
        const legacy = GM_getValue(LEGACY_SETTINGS_KEY, null);
        if (legacy != null && legacy !== "" && legacy !== "{}") {
          GM_setValue(SETTINGS_KEY, typeof legacy === "string" ? legacy : JSON.stringify(legacy));
          raw = legacy;
        }
      }
      const parsed = typeof raw === "string" ? JSON.parse(raw || "{}") : raw ?? {};
      return {
        apiKey: String(parsed?.apiKey ?? "").trim()
      };
    } catch {
      return { apiKey: "" };
    }
  }
  function saveSettings(patch) {
    const current = loadSettings();
    const next = JSON.stringify({ ...current, ...patch });
    GM_setValue(SETTINGS_KEY, next);
  }
  function parseTornError(json) {
    if (!json || typeof json !== "object" || !("error" in json)) {
      return null;
    }
    const err = (
      /** @type {{ error?: unknown }} */
      json.error
    );
    if (typeof err === "string") {
      return new Error(err);
    }
    if (err && typeof err === "object") {
      const detail = (
        /** @type {{ error?: string, message?: string, code?: number }} */
        err
      );
      return new Error(detail.error || detail.message || `Torn API error (${detail.code ?? "unknown"})`);
    }
    return new Error("Torn API error");
  }
  function tornGet(path, params, apiKey) {
    const url = new URL(`${API_BASE}/${path.replace(/^\//, "")}`);
    url.searchParams.set("key", apiKey);
    for (const [key, value] of Object.entries(params ?? {})) {
      if (value !== void 0 && value !== "") {
        url.searchParams.set(key, String(value));
      }
    }
    return new Promise((resolve, reject) => {
      GM_xmlhttpRequest({
        method: "GET",
        url: url.toString(),
        timeout: 2e4,
        onload: (response) => {
          try {
            const json = JSON.parse(response.responseText || "{}");
            const apiError = parseTornError(json);
            if (apiError) {
              reject(apiError);
              return;
            }
            resolve(json);
          } catch (error) {
            reject(error instanceof Error ? error : new Error("Invalid API response"));
          }
        },
        onerror: () => reject(new Error("Network error")),
        ontimeout: () => reject(new Error("API request timed out"))
      });
    });
  }
  var api = {
    loadSettings,
    saveSettings,
    parseTornError,
    tornGet,
    SETTINGS_KEY,
    LEGACY_SETTINGS_KEY
  };

  // src/shared/ui.js
  function panelCollapsedKey(panelId) {
    return `panel.${panelId}.collapsed`;
  }
  function readPanelCollapsed(panelId, legacyKeys = []) {
    const stored = getLocal(panelCollapsedKey(panelId), void 0);
    if (stored === true || stored === "true") {
      return true;
    }
    if (stored === false || stored === "false") {
      return false;
    }
    for (const key of legacyKeys) {
      const raw = localStorage.getItem(key);
      if (raw === "true") {
        setLocal(panelCollapsedKey(panelId), true);
        return true;
      }
      if (raw === "false") {
        setLocal(panelCollapsedKey(panelId), false);
        return false;
      }
    }
    return false;
  }
  var PANELS = /* @__PURE__ */ new Map();
  function createPanel(options) {
    const existing = PANELS.get(options.id);
    if (existing?.element.isConnected) {
      return existing;
    }
    if (existing) {
      PANELS.delete(options.id);
    }
    injectStyles();
    const mountOption = options.mount ?? "racing";
    const isFixed = mountOption === "fixed";
    const mountConfig = isFixed ? null : resolveMountConfig(mountOption);
    const pageBody = options.pageBody ?? "cap";
    const growPageBody = !isFixed && pageBody === "grow";
    const root = document.createElement("section");
    root.id = options.id;
    root.className = isFixed ? "hf-torn hf-panel hf-panel--fixed" : `hf-torn hf-panel hf-panel--page${growPageBody ? " hf-panel--grow" : ""}`;
    applyPanelLayout(root, isFixed, options.position ?? "bottom-right");
    const header = document.createElement("div");
    header.className = "hf-panel-header";
    header.style.cssText = `
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 9px 13px;
    cursor: pointer;
    user-select: none;
  `;
    const titleWrap = document.createElement("div");
    titleWrap.style.display = "flex";
    titleWrap.style.alignItems = "center";
    titleWrap.style.gap = "10px";
    titleWrap.style.flex = "1";
    titleWrap.style.minWidth = "0";
    if (options.badge) {
      const badge = document.createElement("span");
      badge.className = "hf-badge";
      badge.textContent = options.badge;
      titleWrap.appendChild(badge);
    } else if (options.title) {
      const title = document.createElement("div");
      title.textContent = options.title;
      title.style.fontWeight = "700";
      title.style.fontSize = "13px";
      title.style.color = "var(--hf-accent-text)";
      titleWrap.appendChild(title);
    }
    const right = document.createElement("div");
    right.className = "hf-panel-right";
    right.style.cssText = `
    display: flex;
    align-items: center;
    gap: 8px;
    margin-left: auto;
    flex-shrink: 0;
  `;
    const subtitle = document.createElement("div");
    subtitle.className = "hf-muted hf-panel-subtitle";
    subtitle.style.cssText = `
    font-size: 11px;
    max-width: min(280px, 40vw);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    pointer-events: none;
  `;
    const gear = document.createElement("button");
    gear.type = "button";
    gear.className = "hf-panel-gear";
    gear.title = "Settings";
    gear.innerHTML = "&#9881;";
    gear.style.cssText = `
    font-size: 15px;
    color: var(--hf-text-muted);
    background: none;
    border: none;
    cursor: pointer;
    padding: 0 4px;
    line-height: 1;
    flex-shrink: 0;
    position: relative;
    z-index: 2;
  `;
    const openSettings = (event) => {
      event.preventDefault();
      event.stopPropagation();
      options.onSettings?.();
    };
    gear.addEventListener("mousedown", openSettings);
    gear.addEventListener("click", openSettings);
    const toggle = document.createElement("span");
    toggle.className = "hf-panel-chev";
    toggle.textContent = "\u25BC";
    toggle.style.cssText = "font-size:12px;color:var(--hf-text-muted);transition:transform .2s;flex-shrink:0;";
    right.appendChild(subtitle);
    if (options.onSettings) {
      right.appendChild(gear);
    }
    right.appendChild(toggle);
    header.appendChild(titleWrap);
    header.appendChild(right);
    const tabBar = document.createElement("div");
    tabBar.className = "hf-panel-tabs";
    tabBar.style.cssText = `
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    padding: 6px 8px;
  `;
    const body = document.createElement("div");
    body.className = "hf-panel-body";
    if (growPageBody) {
      body.style.maxHeight = "none";
      body.style.overflow = "visible";
    }
    root.appendChild(header);
    if (options.tabs.length > 1) {
      root.appendChild(tabBar);
    }
    root.appendChild(body);
    let collapsed = readPanelCollapsed(options.id, options.collapsedLegacyKeys);
    let activeTabId = options.tabs[0]?.id ?? "";
    const tabButtons = /* @__PURE__ */ new Map();
    const setCollapsed = (value) => {
      collapsed = value;
      const hideBody = collapsed;
      if (options.tabs.length > 1) {
        tabBar.style.display = collapsed ? "none" : "flex";
      }
      body.style.display = hideBody ? "none" : "block";
      toggle.style.transform = collapsed ? "rotate(-90deg)" : "";
      root.classList.toggle("is-collapsed", collapsed);
      setLocal(panelCollapsedKey(options.id), value);
    };
    let renderGeneration = 0;
    const renderActiveTab = async () => {
      const generation = ++renderGeneration;
      const tab = options.tabs.find((item) => item.id === activeTabId);
      body.replaceChildren();
      if (!tab) {
        body.textContent = "No tab selected.";
        return;
      }
      const content = await tab.render();
      if (generation !== renderGeneration) {
        return;
      }
      body.replaceChildren();
      body.appendChild(content);
    };
    const setTab = (id) => {
      activeTabId = id;
      tabButtons.forEach((btn, tabId) => {
        btn.classList.toggle("is-active", tabId === id);
      });
      renderActiveTab();
    };
    if (options.tabs.length > 1) {
      for (const tab of options.tabs) {
        const btn = document.createElement("button");
        btn.type = "button";
        btn.className = "hf-btn";
        btn.textContent = tab.label;
        btn.addEventListener("click", (event) => {
          event.stopPropagation();
          setTab(tab.id);
        });
        tabButtons.set(tab.id, btn);
        tabBar.appendChild(btn);
      }
    }
    header.addEventListener("click", (event) => {
      if (
        /** @type {Element} */
        event.target.closest(".hf-panel-gear")
      ) {
        return;
      }
      if (
        /** @type {Element} */
        event.target.closest("button")
      ) {
        return;
      }
      setCollapsed(!collapsed);
    });
    gear.addEventListener("mouseenter", () => {
      gear.style.color = "#fff";
    });
    gear.addEventListener("mouseleave", () => {
      gear.style.color = "var(--hf-text-muted)";
    });
    setTab(activeTabId);
    setCollapsed(collapsed);
    let stopWatching = null;
    const ensureMounted = () => {
      if (isFixed) {
        if (root.parentElement !== document.body) {
          document.body.appendChild(root);
        }
        return;
      }
      if (mountConfig) {
        mountElement(mountConfig, root);
      }
    };
    if (isFixed) {
      ensureMounted();
    } else if (mountConfig) {
      stopWatching = watchMount(mountConfig, () => root);
    }
    const panel2 = {
      element: root,
      setTab,
      getActiveTabId: () => activeTabId,
      refresh: () => {
        if (options.getSubtitle) {
          subtitle.textContent = options.getSubtitle() ?? "";
        }
        renderActiveTab();
      },
      setSubtitle: (text) => {
        subtitle.textContent = text;
      },
      ensureMounted,
      destroy: () => {
        stopWatching?.();
        root.remove();
        PANELS.delete(options.id);
      }
    };
    if (options.getSubtitle) {
      subtitle.textContent = options.getSubtitle() ?? "";
    }
    PANELS.set(options.id, panel2);
    return panel2;
  }
  function applyPanelLayout(el, isFixed, position) {
    if (!isFixed) {
      el.style.cssText = `
      position: static;
      width: 100%;
      display: flex;
      flex-direction: column;
      overflow: hidden;
    `;
      return;
    }
    el.style.cssText = `
    position: fixed;
    z-index: 9999;
    width: min(420px, calc(100vw - 24px));
    max-height: min(70vh, 560px);
    display: flex;
    flex-direction: column;
    overflow: hidden;
  `;
    applyFixedPosition(el, position);
  }
  function applyFixedPosition(el, position) {
    el.style.top = "";
    el.style.right = "";
    el.style.bottom = "";
    el.style.left = "";
    switch (position) {
      case "top-right":
        el.style.top = "12px";
        el.style.right = "12px";
        break;
      case "bottom-left":
        el.style.bottom = "12px";
        el.style.left = "12px";
        break;
      case "bottom-right":
      default:
        el.style.bottom = "12px";
        el.style.right = "12px";
        break;
    }
  }
  function emptyState(message) {
    const div = document.createElement("div");
    div.className = "hf-muted";
    div.textContent = message;
    return div;
  }
  var ui = {
    createPanel,
    emptyState
  };

  // src/shared/percent.js
  function parsePercent(raw, fallback = 0) {
    if (raw === null || raw === void 0 || raw === "") {
      return fallback;
    }
    if (typeof raw === "number") {
      return Number.isFinite(raw) ? raw : fallback;
    }
    const cleaned = String(raw).trim().replace("%", "").replace(",", ".");
    const value = parseFloat(cleaned);
    return Number.isFinite(value) ? value : fallback;
  }
  function weightToPercent(weight) {
    return Math.round(weight * 1e3) / 10;
  }
  function weightSharePercent(weight, weights) {
    const sum = Object.values(weights).reduce((total, w) => total + (w > 0 ? w : 0), 0);
    if (sum <= 0 || weight <= 0) {
      return 0;
    }
    return weight / sum * 100;
  }
  function percentToWeight(raw, fallback = 0) {
    const pct = parsePercent(raw, fallback * 100);
    return pct / 100;
  }
  function weightsToShares(weights) {
    const sum = Object.values(weights).reduce((total, w) => total + (w > 0 ? w : 0), 0);
    if (sum <= 0) {
      return {};
    }
    const out = {};
    for (const [stat, w] of Object.entries(weights)) {
      out[stat] = w > 0 ? w / sum * 100 : 0;
    }
    return out;
  }
  function normalizeWeightsToShares(weights) {
    const sum = Object.values(weights).reduce((total, w) => total + (w > 0 ? w : 0), 0);
    if (sum <= 0) {
      return weights;
    }
    const out = {};
    for (const [stat, w] of Object.entries(weights)) {
      out[stat] = w > 0 ? w / sum * 100 : 0;
    }
    return out;
  }
  function parseShareInputs(inputsByStat) {
    const raw = {};
    let sum = 0;
    for (const [stat, value] of Object.entries(inputsByStat)) {
      const pct = Math.max(0, parsePercent(value, 0));
      raw[stat] = pct;
      sum += pct;
    }
    if (sum <= 0) {
      return raw;
    }
    const out = {};
    for (const [stat, pct] of Object.entries(raw)) {
      out[stat] = pct > 0 ? pct / sum * 100 : 0;
    }
    return out;
  }
  function bonusesToMultiplier(bonuses) {
    const f = parsePercent(bonuses?.faction, 0);
    const e = parsePercent(bonuses?.education, 0);
    const p = parsePercent(bonuses?.property, 0);
    return (1 + f / 100) * (1 + e / 100) * (1 + p / 100);
  }
  function multiplierToTotalPercent(multiplier) {
    if (!Number.isFinite(multiplier) || multiplier <= 1) {
      return 0;
    }
    return Math.round((multiplier - 1) * 1e3) / 10;
  }
  function formatPercent(pct, digits = 1) {
    const rounded = Math.round(pct * Math.pow(10, digits)) / Math.pow(10, digits);
    const text = Number.isInteger(rounded) ? String(rounded) : rounded.toFixed(digits);
    return `${text}%`;
  }
  function formatWeightSummary(weights, shortLabels) {
    const parts = Object.entries(weights).filter(([, w]) => w > 0).sort((a, b) => b[1] - a[1]).map(([stat, w]) => `${shortLabels[stat] ?? stat} ${formatPercent(weightSharePercent(w, weights), 0)}`);
    return parts.join(" \xB7 ");
  }
  var percent = {
    parsePercent,
    weightToPercent,
    weightSharePercent,
    percentToWeight,
    weightsToShares,
    normalizeWeightsToShares,
    parseShareInputs,
    bonusesToMultiplier,
    multiplierToTotalPercent,
    formatPercent,
    formatWeightSummary
  };

  // src/shared/bars.js
  var FILL_SELECTORS = [
    '[class*="bar-fill"]',
    '[class*="barFill"]',
    '[class*="progress-fill"]',
    '[class*="progressFill"]',
    '[class*="progressBar"]',
    '[class*="progress-bar"]'
  ].join(", ");
  function applyBarFill(box, pct) {
    const clamped = Math.max(0, Math.min(100, pct));
    let updated = false;
    box.querySelectorAll(FILL_SELECTORS).forEach((fill) => {
      if (!(fill instanceof HTMLElement)) {
        return;
      }
      fill.style.width = `${clamped}%`;
      fill.style.transform = "";
      updated = true;
    });
    const progressbar = box.matches('[role="progressbar"]') ? box : box.querySelector('[role="progressbar"]');
    if (progressbar instanceof HTMLElement) {
      progressbar.setAttribute("aria-valuenow", String(Math.round(clamped)));
      const inner = progressbar.firstElementChild;
      if (inner instanceof HTMLElement) {
        inner.style.width = `${clamped}%`;
        inner.style.transform = `scaleX(${clamped / 100})`;
        inner.style.transformOrigin = "left center";
        updated = true;
      }
    }
    if (!updated) {
      for (const child of box.children) {
        if (!(child instanceof HTMLElement) || child.tagName === "P") {
          continue;
        }
        if (child.querySelector(FILL_SELECTORS)) {
          continue;
        }
        child.style.width = `${clamped}%`;
        child.style.transform = `scaleX(${clamped / 100})`;
        child.style.transformOrigin = "left center";
        updated = true;
      }
    }
  }
  function applyBarVisual(box, bar) {
    if (!box || !bar?.max) {
      return;
    }
    const val = box.querySelector('p[class*="bar-value"], [class*="bar-value"]');
    if (val) {
      val.textContent = `${bar.current}/${bar.max}`;
    }
    const pct = bar.max > 0 ? bar.current / bar.max * 100 : 0;
    applyBarFill(box, pct);
    box.querySelectorAll("[aria-valuemax]").forEach((el) => {
      el.setAttribute("aria-valuenow", String(bar.current));
      el.setAttribute("aria-valuemax", String(bar.max));
    });
  }
  function findBarBox(label) {
    let labels = document.querySelectorAll('p[class*="bar-name"]');
    if (!labels.length) {
      labels = document.querySelectorAll(".wai");
    }
    for (const el of labels) {
      if (el.textContent.trim().replace(":", "") === label) {
        return el.closest('[class*="bar-stats"]') || el.parentElement;
      }
    }
    const slug = label.toLowerCase();
    const link = document.querySelector(
      `a.bar-link[class*="${slug}"], a[class*="bar-link"][class*="${slug}"]`
    );
    return link;
  }
  function syncBarVisuals(bars2) {
    if (bars2.energy) {
      applyBarVisual(findBarBox("Energy"), bars2.energy);
    }
    if (bars2.happy) {
      applyBarVisual(findBarBox("Happy"), bars2.happy);
    }
    if (bars2.nerve) {
      applyBarVisual(findBarBox("Nerve"), bars2.nerve);
    }
    if (bars2.life) {
      applyBarVisual(findBarBox("Life"), bars2.life);
    }
  }
  var bars = {
    applyBarVisual,
    findBarBox,
    syncBarVisuals
  };

  // src/shared/index.js
  var HF = {
    version: "1.0.0",
    theme,
    storage,
    dom,
    mount,
    api,
    ui,
    percent,
    bars
  };

  // src/stocks/legacy-app.js
  function bootstrapStocksApp(opts = {}) {
    var $;
    const MOUNT_SELECTOR = opts.mountRoot || "#smart-stocks-root";
    function resolveJQuery() {
      if (typeof jQuery !== "undefined" && jQuery.fn) {
        return jQuery;
      }
      if (typeof unsafeWindow !== "undefined" && unsafeWindow.jQuery && unsafeWindow.jQuery.fn) {
        return unsafeWindow.jQuery;
      }
      if (typeof window !== "undefined" && window.jQuery && window.jQuery.fn) {
        return window.jQuery;
      }
      return null;
    }
    function mountVaultHtml(html) {
      const cleaned = html.replace(/<div class="alfa-vault-header">[\s\S]*?<\/div>\s*<\/div>\s*/g, "");
      $(MOUNT_SELECTOR).html(cleaned);
    }
    const STOCK_DATA = {
      "ASS": { base: 1e6, type: "A" },
      "BAG": { base: 3e6, type: "A" },
      "CNC": { base: 75e5, type: "A" },
      "EWM": { base: 1e6, type: "A" },
      "ELT": { base: 5e6, type: "P" },
      "EVL": { base: 1e5, type: "A" },
      "FHG": { base: 2e6, type: "A" },
      "GRN": { base: 5e5, type: "A" },
      "CBD": { base: 35e4, type: "A" },
      "HRG": { base: 1e7, type: "A" },
      "IIL": { base: 1e6, type: "P" },
      "IOU": { base: 3e6, type: "A" },
      "IST": { base: 1e5, type: "P" },
      "LAG": { base: 75e4, type: "A" },
      "LOS": { base: 75e5, type: "P" },
      "LSC": { base: 5e5, type: "A" },
      "MCS": { base: 35e4, type: "A" },
      "MSG": { base: 3e5, type: "P" },
      "MUN": { base: 5e6, type: "A" },
      "PRN": { base: 1e6, type: "A" },
      "PTS": { base: 1e7, type: "A" },
      "SYM": { base: 5e5, type: "A" },
      "SYS": { base: 3e6, type: "P" },
      "TCP": { base: 1e6, type: "P" },
      "TMI": { base: 6e6, type: "A" },
      "TGP": { base: 25e5, type: "P" },
      "TCT": { base: 1e5, type: "A" },
      "TSB": { base: 3e6, type: "A" },
      "TCC": { base: 75e5, type: "A" },
      "THS": { base: 15e4, type: "A" },
      "TCI": { base: 15e5, type: "P" },
      "TCM": { base: 1e6, type: "P" },
      "WSU": { base: 1e6, type: "P" },
      "WLT": { base: 9e6, type: "P" },
      "YAZ": { base: 1e6, type: "P" }
    };
    let STOCK_ID_MAP = {
      1: "TCI",
      2: "TCC",
      3: "SYS",
      4: "LAG",
      5: "IOU",
      6: "GRN",
      7: "THS",
      8: "CBD",
      9: "TCT",
      10: "EVL",
      11: "MCS",
      12: "WSU",
      13: "IIL",
      14: "FHG",
      15: "SYM",
      16: "LSC",
      17: "PRN",
      18: "EWM",
      19: "TCM",
      20: "MSG",
      21: "MUN",
      22: "YAZ",
      23: "IST",
      24: "BAG",
      25: "ASS",
      26: "CNC",
      27: "TMI",
      28: "ELT",
      29: "HRG",
      30: "TGP",
      31: "TSB",
      32: "WLT",
      33: "LOS",
      34: "TCP",
      35: "PTS"
    };
    let SYMBOL_TO_ID = {};
    for (const [id, sym] of Object.entries(STOCK_ID_MAP)) {
      SYMBOL_TO_ID[sym] = parseInt(id);
    }
    function buildStockIdMap() {
      for (const [sym, domId] of Object.entries(stockId)) {
        const numId = parseInt(domId.replace("stock_", ""));
        if (!isNaN(numId)) {
          STOCK_ID_MAP[numId] = sym;
          SYMBOL_TO_ID[sym] = numId;
        }
      }
    }
    const LOG_TYPE_BUY = 5510;
    const LOG_TYPE_SELL = 5511;
    let portfolioData = {
      transactions: [],
      // All stock transactions from API log
      lastSyncTimestamp: 0,
      // Last transaction timestamp we synced
      costBasis: {},
      // Per-stock cost basis tracking { symbol: { totalShares: 0, totalCost: 0, lots: [] } }
      realizedPL: 0,
      // Total realized profit/loss
      lastFullSync: 0
      // Timestamp of last full sync
    };
    try {
      let savedPortfolio = JSON.parse(localStorage.getItem("alfa_portfolio_data"));
      if (savedPortfolio) {
        portfolioData = { ...portfolioData, ...savedPortfolio };
      }
    } catch (e) {
      console.error("Failed to load portfolio data:", e);
    }
    let swingTradeData = {
      activePositions: [],
      // Current open swing trades
      completedTrades: [],
      // Historical completed trades
      watchlist: [],
      // Stocks being watched for entry
      settings: {
        defaultTargetPercent: 3,
        // Default take profit %
        defaultStopPercent: 2,
        // Default stop loss %
        minSwingPercent: 0.5,
        // Minimum price swing to trigger signal
        rsiOversold: 30,
        // RSI threshold for buy signals
        rsiOverbought: 70,
        // RSI threshold for sell signals
        bollingerEntryThreshold: 0.2,
        // Entry when below this position (0-1)
        bollingerExitThreshold: 0.8,
        // Exit when above this position (0-1)
        adxMinThreshold: 25,
        // ADX must be >= this for full RSI weight (filters weak trends)
        adxPartialThreshold: 20,
        // Between 20-25, RSI gets 50% weight; below 20, RSI ignored
        volumeMinMultiplier: 1,
        // Volume must be >= 7d avg for full signal (filters fakeouts)
        volumePartialMultiplier: 0.8,
        // Between 0.8-1, partial weight; below 0.8, discount
        atrTargetMultiplier: 2,
        // Target = ATR% * this (e.g. 2x ATR for take profit)
        atrStopMultiplier: 1.5,
        // Stop = ATR% * this (e.g. 1.5x ATR for stop loss)
        enableAlerts: true,
        // Show visual alerts for signals
        autoTrack: false
        // Auto-add buy transactions as positions
      },
      lastSignalCheck: 0
    };
    try {
      let savedSwingData = JSON.parse(localStorage.getItem("alfa_swing_trades"));
      if (savedSwingData) {
        swingTradeData = { ...swingTradeData, ...savedSwingData };
        swingTradeData.completedTrades.forEach((t) => {
          if (!t.tags) t.tags = [];
        });
      }
    } catch (e) {
      console.error("Failed to load swing trade data:", e);
    }
    let lastClosedPosition = null;
    let undoCloseTimeout = null;
    function saveSwingTradeData() {
      localStorage.setItem("alfa_swing_trades", JSON.stringify(swingTradeData));
    }
    function generateTradeId() {
      return `ST_${Date.now()}_${Math.random().toString(36).substr(2, 5)}`;
    }
    function addSwingPosition(symbol, entryPrice, shares, targetPercent = null, stopPercent = null, notes = "", targetPrice = null, stopPrice = null) {
      const settings = swingTradeData.settings;
      targetPercent = targetPercent ?? settings.defaultTargetPercent;
      stopPercent = stopPercent ?? settings.defaultStopPercent;
      let finalTargetPrice, finalStopPrice, finalTargetPercent, finalStopPercent;
      if (targetPrice != null && targetPrice > 0) {
        finalTargetPrice = targetPrice;
        finalTargetPercent = entryPrice > 0 ? (targetPrice / entryPrice - 1) * 100 : 0;
      } else {
        finalTargetPrice = entryPrice * (1 + targetPercent / 100);
        finalTargetPercent = targetPercent;
      }
      if (stopPrice != null && stopPrice > 0) {
        finalStopPrice = stopPrice;
        finalStopPercent = entryPrice > 0 ? (1 - stopPrice / entryPrice) * 100 : 0;
      } else {
        finalStopPrice = entryPrice * (1 - stopPercent / 100);
        finalStopPercent = stopPercent;
      }
      const position = {
        id: generateTradeId(),
        symbol: symbol.toUpperCase(),
        type: "long",
        entryPrice,
        entryDate: Date.now(),
        shares,
        targetPrice: finalTargetPrice,
        stopPrice: finalStopPrice,
        targetPercent: finalTargetPercent,
        stopPercent: finalStopPercent,
        status: "active",
        notes,
        currentPrice: entryPrice,
        currentPL: 0,
        currentPLPercent: 0
      };
      swingTradeData.activePositions.push(position);
      saveSwingTradeData();
      return position;
    }
    function updatePositionPrice(positionId, currentPrice, opts2 = {}) {
      const persist = opts2.persist !== false;
      const position = swingTradeData.activePositions.find((p) => p.id === positionId);
      if (!position) return null;
      position.currentPrice = currentPrice;
      position.currentPL = (currentPrice - position.entryPrice) * position.shares;
      position.currentPLPercent = (currentPrice - position.entryPrice) / position.entryPrice * 100;
      let statusChanged = false;
      if (currentPrice >= position.targetPrice && position.status === "active") {
        position.status = "target_hit";
        statusChanged = true;
      } else if (currentPrice <= position.stopPrice && position.status === "active") {
        position.status = "stopped";
        statusChanged = true;
      }
      if (persist && statusChanged) saveSwingTradeData();
      return position;
    }
    function updateAllPositionPrices() {
      let statusChanged = false;
      for (const position of swingTradeData.activePositions) {
        const currentPrice = getPrice(position.symbol);
        if (currentPrice > 0) {
          const before = position.status;
          updatePositionPrice(position.id, currentPrice, { persist: false });
          if (position.status !== before) statusChanged = true;
        }
      }
      if (statusChanged) saveSwingTradeData();
    }
    function closeSwingPosition(positionId, exitPrice = null, exitReason = "manual_close", sharesToClose = null) {
      const posIndex = swingTradeData.activePositions.findIndex((p) => p.id === positionId);
      if (posIndex === -1) return null;
      const position = swingTradeData.activePositions[posIndex];
      exitPrice = exitPrice || position.currentPrice || getPrice(position.symbol);
      const isFullClose = sharesToClose == null || sharesToClose >= position.shares;
      const closeShares = isFullClose ? position.shares : Math.min(Math.max(1, Math.floor(sharesToClose)), position.shares - 1);
      const grossProceeds = exitPrice * closeShares;
      const fee = grossProceeds * 1e-3;
      const netProceeds = grossProceeds - fee;
      const cost = position.entryPrice * closeShares;
      const completedTrade = {
        id: isFullClose ? position.id : generateTradeId(),
        symbol: position.symbol,
        type: position.type,
        entryPrice: position.entryPrice,
        entryDate: position.entryDate,
        exitPrice,
        exitDate: Date.now(),
        shares: closeShares,
        grossPnl: grossProceeds - cost,
        fee,
        pnl: netProceeds - cost,
        pnlPercent: cost > 0 ? (netProceeds - cost) / cost * 100 : 0,
        holdingDays: Math.ceil((Date.now() - position.entryDate) / (1e3 * 60 * 60 * 24)),
        exitReason,
        notes: position.notes || "",
        tags: position.tags || []
      };
      if (isFullClose) {
        lastClosedPosition = { position: JSON.parse(JSON.stringify(position)), completedTrade };
        if (undoCloseTimeout) clearTimeout(undoCloseTimeout);
        undoCloseTimeout = setTimeout(() => {
          lastClosedPosition = null;
        }, 15e3);
        swingTradeData.activePositions.splice(posIndex, 1);
      } else {
        position.shares -= closeShares;
      }
      swingTradeData.completedTrades.unshift(completedTrade);
      if (swingTradeData.completedTrades.length > 100) {
        swingTradeData.completedTrades = swingTradeData.completedTrades.slice(0, 100);
      }
      saveSwingTradeData();
      return completedTrade;
    }
    function recalculateCompletedTrade(trade) {
      const cost = trade.entryPrice * trade.shares;
      const grossProceeds = trade.exitPrice * trade.shares;
      const fee = grossProceeds * 1e-3;
      const netProceeds = grossProceeds - fee;
      trade.grossPnl = grossProceeds - cost;
      trade.fee = fee;
      trade.pnl = netProceeds - cost;
      trade.pnlPercent = cost > 0 ? (netProceeds - cost) / cost * 100 : 0;
      trade.holdingDays = Math.ceil((trade.exitDate - trade.entryDate) / (1e3 * 60 * 60 * 24));
      return trade;
    }
    function undoClosePosition() {
      if (!lastClosedPosition) return false;
      const { position, completedTrade } = lastClosedPosition;
      const idx = swingTradeData.completedTrades.findIndex((t) => t.id === completedTrade.id);
      if (idx === -1) return false;
      swingTradeData.completedTrades.splice(idx, 1);
      swingTradeData.activePositions.unshift(position);
      lastClosedPosition = null;
      if (undoCloseTimeout) {
        clearTimeout(undoCloseTimeout);
        undoCloseTimeout = null;
      }
      saveSwingTradeData();
      return true;
    }
    function showUndoCloseBanner() {
      const existing = document.getElementById("swing-undo-banner");
      if (existing) existing.remove();
      const banner = document.createElement("div");
      banner.id = "swing-undo-banner";
      banner.className = "swing-undo-banner";
      banner.innerHTML = `Position closed. <button id="swing-undo-btn" class="swing-undo-btn">Undo</button>`;
      const swingEl = document.querySelector(".swing-content") || document.querySelector("#swing-content");
      const parent = swingEl || document.body;
      if (parent) {
        parent.insertBefore(banner, parent.firstChild);
        banner.querySelector("#swing-undo-btn").onclick = () => {
          if (undoClosePosition()) {
            $("#swing-positions-list").html(renderSwingPositions());
            refreshSwingJournal();
            bindSwingPositionActions();
          }
          banner.remove();
        };
      }
    }
    function deleteJournalEntry(tradeId) {
      const idx = swingTradeData.completedTrades.findIndex((t) => t.id === tradeId);
      if (idx === -1) return false;
      swingTradeData.completedTrades.splice(idx, 1);
      saveSwingTradeData();
      return true;
    }
    function getSwingTradeStats(period = "all") {
      const startTimestamp = getPeriodStartTimestamp(period);
      const trades = swingTradeData.completedTrades.filter((t) => t.exitDate >= startTimestamp);
      if (trades.length === 0) {
        return { totalTrades: 0, winRate: 0, avgPnl: 0, avgPnlPercent: 0, totalPnl: 0, avgHoldDays: 0, bestTrade: null, worstTrade: null };
      }
      const winners = trades.filter((t) => t.pnl > 0);
      const totalPnl = trades.reduce((sum, t) => sum + t.pnl, 0);
      const totalPnlPercent = trades.reduce((sum, t) => sum + t.pnlPercent, 0);
      const avgHoldDays = trades.reduce((sum, t) => sum + t.holdingDays, 0) / trades.length;
      const sortedByPnl = [...trades].sort((a, b) => b.pnl - a.pnl);
      return {
        totalTrades: trades.length,
        winners: winners.length,
        losers: trades.length - winners.length,
        winRate: winners.length / trades.length * 100,
        avgPnl: totalPnl / trades.length,
        avgPnlPercent: totalPnlPercent / trades.length,
        totalPnl,
        avgHoldDays,
        bestTrade: sortedByPnl[0],
        worstTrade: sortedByPnl[sortedByPnl.length - 1]
      };
    }
    function analyzeSwingOpportunity(analysis) {
      if (!analysis) return null;
      const settings = swingTradeData.settings;
      const signals = [];
      let score = 0;
      const adx = analysis.adx ?? 25;
      const adxMin = settings.adxMinThreshold ?? 25;
      const adxPartial = settings.adxPartialThreshold ?? 20;
      const rsiAdxFactor = adx >= adxMin ? 1 : adx >= adxPartial ? 0.5 : 0;
      let rsiWeight = 0;
      if (analysis.rsi < settings.rsiOversold) {
        rsiWeight = 30;
        signals.push({ type: "rsi", direction: "bullish", message: `RSI oversold (${analysis.rsi.toFixed(0)})${rsiAdxFactor < 1 ? " [weak trend]" : ""}`, weight: 30 });
      } else if (analysis.rsi < 40) {
        rsiWeight = 15;
        signals.push({ type: "rsi", direction: "bullish", message: `RSI low (${analysis.rsi.toFixed(0)})${rsiAdxFactor < 1 ? " [weak trend]" : ""}`, weight: 15 });
      } else if (analysis.rsi > settings.rsiOverbought) {
        rsiWeight = -30;
        signals.push({ type: "rsi", direction: "bearish", message: `RSI overbought (${analysis.rsi.toFixed(0)})${rsiAdxFactor < 1 ? " [weak trend]" : ""}`, weight: -30 });
      } else if (analysis.rsi > 60) {
        rsiWeight = -15;
        signals.push({ type: "rsi", direction: "bearish", message: `RSI elevated (${analysis.rsi.toFixed(0)})${rsiAdxFactor < 1 ? " [weak trend]" : ""}`, weight: -15 });
      }
      score += rsiWeight * rsiAdxFactor;
      if (analysis.bollingerPosition <= settings.bollingerEntryThreshold) {
        signals.push({ type: "bollinger", direction: "bullish", message: `Near lower Bollinger band`, weight: 25 });
        score += 25;
      } else if (analysis.bollingerPosition >= settings.bollingerExitThreshold) {
        signals.push({ type: "bollinger", direction: "bearish", message: `Near upper Bollinger band`, weight: -25 });
        score -= 25;
      }
      if (analysis.support > 0 && analysis.price <= analysis.support * 1.02) {
        signals.push({ type: "support", direction: "bullish", message: `At support level ($${analysis.support.toFixed(2)})`, weight: 20 });
        score += 20;
      }
      if (analysis.resistance > 0 && analysis.price >= analysis.resistance * 0.98) {
        signals.push({ type: "resistance", direction: "bearish", message: `At resistance ($${analysis.resistance.toFixed(2)})`, weight: -20 });
        score -= 20;
      }
      if (analysis.momentum7d < -3) {
        signals.push({ type: "momentum", direction: "bullish", message: `Momentum reversal potential (${analysis.momentum7d.toFixed(1)}%)`, weight: 15 });
        score += 15;
      } else if (analysis.momentum7d > 5) {
        signals.push({ type: "momentum", direction: "bearish", message: `Extended momentum (${analysis.momentum7d.toFixed(1)}%)`, weight: -15 });
        score -= 15;
      }
      if (analysis.dipFrom7d < -3) {
        signals.push({ type: "sma", direction: "bullish", message: `${analysis.dipFrom7d.toFixed(1)}% below 7-day avg`, weight: 20 });
        score += 20;
      } else if (analysis.dipFrom7d > 3) {
        signals.push({ type: "sma", direction: "bearish", message: `+${analysis.dipFrom7d.toFixed(1)}% above 7-day avg`, weight: -20 });
        score -= 20;
      }
      const volMult = analysis.volumeMultiplier ?? 1;
      const volMin = settings.volumeMinMultiplier ?? 1;
      const volPartial = settings.volumePartialMultiplier ?? 0.8;
      const volumeFactor = volMult >= volMin ? 1 : volMult >= volPartial ? 0.5 : 0;
      score *= volumeFactor;
      let overallSignal = "neutral";
      if (score >= 50) overallSignal = "strong_buy";
      else if (score >= 25) overallSignal = "buy";
      else if (score >= 10) overallSignal = "watch";
      else if (score <= -50) overallSignal = "strong_sell";
      else if (score <= -25) overallSignal = "sell";
      else if (score <= -10) overallSignal = "take_profit";
      const atr = analysis.atr ?? 0;
      const atrPercent = analysis.price > 0 && atr > 0 ? atr / analysis.price * 100 : 0;
      const atrTargetMult = settings.atrTargetMultiplier ?? 2;
      const atrStopMult = settings.atrStopMultiplier ?? 1.5;
      const suggestedTarget = Math.max(
        settings.defaultTargetPercent,
        atrPercent * atrTargetMult,
        // ATR-based: 2x ATR % for target
        analysis.volatility * 1.5,
        // 1.5x volatility
        analysis.resistance > 0 ? (analysis.resistance - analysis.price) / analysis.price * 100 : 0
      );
      const suggestedStop = Math.max(
        settings.defaultStopPercent,
        atrPercent * atrStopMult,
        // ATR-based: 1.5x ATR % for stop
        analysis.volatility * 1.2,
        // 1.2x volatility for stop
        analysis.support > 0 ? (analysis.price - analysis.support) / analysis.price * 100 + 0.5 : 0
      );
      return {
        symbol: analysis.symbol,
        price: analysis.price,
        score,
        signal: overallSignal,
        signals,
        suggestedTarget: Math.round(Math.min(suggestedTarget, 10) * 2) / 2,
        // Cap at 10%, round to 0.5%
        suggestedStop: Math.round(Math.min(suggestedStop, 5) * 2) / 2,
        // Cap at 5%, round to 0.5%
        volatility: analysis.volatility,
        rsi: analysis.rsi,
        adx,
        atr: analysis.atr,
        volumeMultiplier: analysis.volumeMultiplier,
        bollingerPosition: analysis.bollingerPosition,
        support: analysis.support,
        resistance: analysis.resistance
      };
    }
    async function scanSwingOpportunities() {
      const opportunities = [];
      const symbols = (vaultConfig.stocks || []).filter((sym) => STOCK_DATA[sym]);
      if (symbols.length === 0) {
        swingTradeData.lastSignalCheck = Date.now();
        swingTradeData.lastSignalBySymbol = {};
        return opportunities;
      }
      for (let i = 0; i < symbols.length; i++) {
        const sym = symbols[i];
        let analysis = vaultAnalysisCache[sym];
        let fetched = false;
        if (!analysis) {
          analysis = await analyzeStockForVault(sym);
          fetched = true;
        }
        if (analysis) {
          const opportunity = analyzeSwingOpportunity(analysis);
          if (opportunity) {
            opportunity.hasActivePosition = swingTradeData.activePositions.some((p) => p.symbol === sym);
            opportunity.currentShares = getOwnedShares(sym);
            opportunities.push(opportunity);
          }
        }
        if (fetched && i < symbols.length - 1) {
          await new Promise((r) => setTimeout(r, 100));
        }
      }
      opportunities.sort((a, b) => b.score - a.score);
      swingTradeData.lastSignalCheck = Date.now();
      swingTradeData.lastSignalBySymbol = {};
      opportunities.forEach((o) => {
        swingTradeData.lastSignalBySymbol[o.symbol] = o;
      });
      return opportunities;
    }
    async function getTopSwingSignals(limit = 5) {
      const opportunities = await scanSwingOpportunities();
      const buySignals = opportunities.filter((o) => ["strong_buy", "buy", "watch"].includes(o.signal)).slice(0, limit);
      const sellSignals = opportunities.filter((o) => ["strong_sell", "sell", "take_profit"].includes(o.signal)).slice(0, limit);
      return { buySignals, sellSignals, all: opportunities };
    }
    async function refreshPositionSignals() {
      const positions = swingTradeData.activePositions;
      if (positions.length === 0) return;
      if (!swingTradeData.lastSignalBySymbol) swingTradeData.lastSignalBySymbol = {};
      const symbols = [...new Set(positions.map((p) => p.symbol))];
      for (const sym of symbols) {
        let analysis = vaultAnalysisCache[sym];
        if (!analysis) analysis = await analyzeStockForVault(sym);
        if (analysis) {
          const opportunity = analyzeSwingOpportunity(analysis);
          if (opportunity) swingTradeData.lastSignalBySymbol[sym] = opportunity;
        }
      }
      swingTradeData.lastSignalCheck = Date.now();
    }
    function formatSignalAge(msAgo) {
      if (msAgo < 6e4) return "&lt;1m";
      if (msAgo < 36e5) return Math.floor(msAgo / 6e4) + "m";
      if (msAgo < 864e5) return Math.floor(msAgo / 36e5) + "h";
      return Math.floor(msAgo / 864e5) + "d";
    }
    const SIGNAL_STALE_MS = 30 * 60 * 1e3;
    function renderSwingPositions() {
      updateAllPositionPrices();
      const positions = swingTradeData.activePositions;
      if (positions.length === 0) {
        return `<div class="swing-empty">
            <div style="font-size:24px; margin-bottom:8px;">\u{1F4CA}</div>
            <div>No active swing trades</div>
            <div style="font-size:10px; color:#666; margin-top:5px;">Add a position or use signals to find opportunities</div>
        </div>`;
      }
      let totalPL = 0;
      let totalValue = 0;
      const positionRows = positions.map((pos) => {
        const currentPrice = getPrice(pos.symbol) || pos.currentPrice;
        const pl = (currentPrice - pos.entryPrice) * pos.shares;
        const plPercent = (currentPrice - pos.entryPrice) / pos.entryPrice * 100;
        const value = currentPrice * pos.shares;
        totalPL += pl;
        totalValue += value;
        const priceRange = pos.targetPrice - pos.stopPrice;
        const currentProgress = currentPrice - pos.stopPrice;
        const progressPercent = Math.max(0, Math.min(100, currentProgress / priceRange * 100));
        const statusClass = pos.status === "target_hit" ? "target-hit" : pos.status === "stopped" ? "stopped" : "";
        const statusIcon = pos.status === "target_hit" ? "\u{1F3AF}" : pos.status === "stopped" ? "\u{1F6D1}" : "";
        const entryDate = new Date(pos.entryDate).toLocaleDateString();
        const holdDays = Math.ceil((Date.now() - pos.entryDate) / (1e3 * 60 * 60 * 24));
        const signalColors = { strong_buy: "#00e676", buy: "#66bb6a", watch: "#ffd54f", neutral: "#888", take_profit: "#ffb74d", sell: "#ff7043", strong_sell: "#ef5350" };
        const signalLabels = { strong_buy: "STRONG BUY", buy: "BUY", watch: "WATCH", neutral: "NEUTRAL", take_profit: "TAKE PROFIT", sell: "SELL", strong_sell: "STRONG SELL" };
        const opportunity = swingTradeData.lastSignalBySymbol?.[pos.symbol];
        const msAgo = swingTradeData.lastSignalCheck ? Date.now() - swingTradeData.lastSignalCheck : Infinity;
        const isStale = msAgo > SIGNAL_STALE_MS;
        let signalBadge = "";
        if (opportunity && opportunity.signal !== "neutral") {
          const ageStr = formatSignalAge(msAgo);
          const staleClass = isStale ? " swing-signal-stale" : "";
          signalBadge = `<span class="swing-pos-signal${staleClass}" style="background:${signalColors[opportunity.signal]}22; color:${signalColors[opportunity.signal]};" title="${isStale ? "Stale - click Refresh signals" : ""}">${signalLabels[opportunity.signal]} (${ageStr})</span>`;
        }
        return `
        <div class="swing-position ${statusClass}" data-id="${pos.id}">
            <div class="swing-pos-header">
                <div class="swing-pos-title-group">
                    <span class="swing-pos-symbol">${pos.symbol}</span>
                    ${signalBadge}
                </div>
                <div class="swing-pos-header-right">
                    <span class="swing-pos-pl ${pl >= 0 ? "positive" : "negative"}">${pl >= 0 ? "+" : ""}${formatMoney(pl)} (${plPercent >= 0 ? "+" : ""}${plPercent.toFixed(2)}%)</span>
                    ${statusIcon ? `<span class="swing-pos-status">${statusIcon}</span>` : ""}
                </div>
            </div>
            <div class="swing-pos-details">
                <div class="swing-pos-row">
                    <span>Entry: $${pos.entryPrice.toFixed(2)}</span>
                    <span>Current: $${currentPrice.toFixed(2)}</span>
                    <span>${pos.shares.toLocaleString()} shares</span>
                </div>
                <div class="swing-pos-row">
                    <span class="swing-stop">Stop: $${pos.stopPrice.toFixed(2)} (-${pos.stopPercent}%)</span>
                    <span class="swing-target">Target: $${pos.targetPrice.toFixed(2)} (+${pos.targetPercent}%)</span>
                </div>
                <div class="swing-pos-progress">
                    <div class="swing-progress-bar">
                        <div class="swing-progress-fill" style="width:${progressPercent}%"></div>
                        <div class="swing-progress-marker" style="left:${progressPercent}%"></div>
                    </div>
                    <div class="swing-progress-labels">
                        <span>Stop</span>
                        <span>Target</span>
                    </div>
                </div>
                <div class="swing-pos-meta">
                    <span>Opened: ${entryDate} (${holdDays}d)</span>
                    <span>Value: ${formatMoney(value)}</span>
                </div>
            </div>
            <div class="swing-pos-actions">
                <button class="swing-close-btn" data-id="${pos.id}" data-sym="${pos.symbol}">Close Position</button>
                <button class="swing-merge-btn" data-id="${pos.id}" title="Add shares to this position">Merge</button>
                <button class="swing-edit-btn" data-id="${pos.id}">Edit</button>
            </div>
        </div>`;
      }).join("");
      const summaryHtml = `
    <div class="swing-positions-summary">
        <div class="swing-summary-item">
            <span class="swing-summary-label">Active Positions</span>
            <span class="swing-summary-value">${positions.length}</span>
        </div>
        <div class="swing-summary-item ${totalPL >= 0 ? "positive" : "negative"}">
            <span class="swing-summary-label">Open P&L</span>
            <span class="swing-summary-value">${totalPL >= 0 ? "+" : ""}${formatMoney(totalPL)}</span>
        </div>
        <div class="swing-summary-item">
            <span class="swing-summary-label">Total Value</span>
            <span class="swing-summary-value">${formatMoney(totalValue)}</span>
        </div>
    </div>`;
      return summaryHtml + positionRows;
    }
    function renderSwingSignals(opportunities = null) {
      if (!opportunities) {
        return `<div class="swing-empty">
            <div style="font-size:24px; margin-bottom:8px;">\u{1F50D}</div>
            <div>Click "Scan" to analyze vault stocks</div>
        </div>`;
      }
      const signalColors = {
        "strong_buy": "#00e676",
        "buy": "#66bb6a",
        "watch": "#ffd54f",
        "neutral": "#888",
        "take_profit": "#ffb74d",
        "sell": "#ff7043",
        "strong_sell": "#ef5350"
      };
      const signalLabels = {
        "strong_buy": "\u{1F7E2} STRONG BUY",
        "buy": "\u{1F7E2} BUY",
        "watch": "\u{1F7E1} WATCH",
        "neutral": "\u26AA NEUTRAL",
        "take_profit": "\u{1F7E0} TAKE PROFIT",
        "sell": "\u{1F534} SELL",
        "strong_sell": "\u{1F534} STRONG SELL"
      };
      const interesting = opportunities.filter((o) => o.signal !== "neutral");
      if (interesting.length === 0) {
        return `<div class="swing-empty">
            <div style="font-size:24px; margin-bottom:8px;">\u{1F634}</div>
            <div>No strong signals right now</div>
            <div style="font-size:10px; color:#666; margin-top:5px;">Vault stocks only \u2014 add stocks in Settings \u2192 Vault</div>
        </div>`;
      }
      const buySignals = interesting.filter((o) => o.score > 0).slice(0, 8);
      const sellSignals = interesting.filter((o) => o.score < 0).slice(0, 8);
      let html = "";
      if (buySignals.length > 0) {
        html += `<div class="swing-signal-group">
            <div class="swing-signal-group-title" style="color:#66bb6a;">\u{1F4C8} Buy Opportunities</div>
            ${buySignals.map((sig) => `
            <div class="swing-signal-card buy" data-sym="${sig.symbol}">
                <div class="swing-signal-header">
                    <span class="swing-signal-symbol">${sig.symbol}</span>
                    <span class="swing-signal-badge" style="background:${signalColors[sig.signal]}22; color:${signalColors[sig.signal]};">${signalLabels[sig.signal]}</span>
                    <span class="swing-signal-score">Score: ${sig.score}</span>
                </div>
                <div class="swing-signal-price">$${sig.price.toFixed(2)}</div>
                <div class="swing-signal-reasons">
                    ${sig.signals.filter((s) => s.direction === "bullish").map((s) => `<span class="swing-reason bullish">\u2713 ${s.message}</span>`).join("")}
                </div>
                <div class="swing-signal-targets">
                    <span>Target: +${sig.suggestedTarget.toFixed(1)}% ($${(sig.price * (1 + sig.suggestedTarget / 100)).toFixed(2)})</span>
                    <span>Stop: -${sig.suggestedStop.toFixed(1)}% ($${(sig.price * (1 - sig.suggestedStop / 100)).toFixed(2)})</span>
                </div>
                <div class="swing-signal-actions">
                    ${!sig.hasActivePosition ? `<button class="swing-signal-track-btn" data-sym="${sig.symbol}" data-price="${sig.price}" data-target="${sig.suggestedTarget}" data-stop="${sig.suggestedStop}">Track Position</button>` : '<span class="swing-already-tracking">Already tracking</span>'}
                </div>
            </div>`).join("")}
        </div>`;
      }
      if (sellSignals.length > 0) {
        html += `<div class="swing-signal-group">
            <div class="swing-signal-group-title" style="color:#ef5350;">\u{1F4C9} Take Profit / Exit Signals</div>
            ${sellSignals.map((sig) => `
            <div class="swing-signal-card sell" data-sym="${sig.symbol}">
                <div class="swing-signal-header">
                    <span class="swing-signal-symbol">${sig.symbol}</span>
                    <span class="swing-signal-badge" style="background:${signalColors[sig.signal]}22; color:${signalColors[sig.signal]};">${signalLabels[sig.signal]}</span>
                    <span class="swing-signal-score">Score: ${sig.score}</span>
                </div>
                <div class="swing-signal-price">$${sig.price.toFixed(2)}</div>
                <div class="swing-signal-reasons">
                    ${sig.signals.filter((s) => s.direction === "bearish").map((s) => `<span class="swing-reason bearish">\u26A0 ${s.message}</span>`).join("")}
                </div>
                ${sig.currentShares > 0 ? `<div class="swing-signal-holding">You own ${sig.currentShares.toLocaleString()} shares</div>` : ""}
            </div>`).join("")}
        </div>`;
      }
      return html;
    }
    function renderSwingStats() {
      const stats = getSwingTradeStats("all");
      if (stats.totalTrades === 0) {
        return `<div class="swing-stats-empty">No completed trades yet</div>`;
      }
      return `
    <div class="swing-stats-grid">
        <div class="swing-stat-card">
            <span class="swing-stat-label">Total Trades</span>
            <span class="swing-stat-value">${stats.totalTrades}</span>
        </div>
        <div class="swing-stat-card ${stats.winRate >= 50 ? "positive" : "negative"}">
            <span class="swing-stat-label">Win Rate</span>
            <span class="swing-stat-value">${stats.winRate.toFixed(1)}%</span>
            <span class="swing-stat-sub">${stats.winners}W / ${stats.losers}L</span>
        </div>
        <div class="swing-stat-card ${stats.totalPnl >= 0 ? "positive" : "negative"}">
            <span class="swing-stat-label">Total P&L</span>
            <span class="swing-stat-value">${stats.totalPnl >= 0 ? "+" : ""}${formatMoneyWhole(stats.totalPnl)}</span>
        </div>
        <div class="swing-stat-card">
            <span class="swing-stat-label">Avg P&L</span>
            <span class="swing-stat-value ${stats.avgPnl >= 0 ? "positive" : "negative"}">${stats.avgPnl >= 0 ? "+" : ""}${formatMoneyWhole(stats.avgPnl)}</span>
            <span class="swing-stat-sub">(${stats.avgPnlPercent >= 0 ? "+" : ""}${stats.avgPnlPercent.toFixed(2)}%)</span>
        </div>
        <div class="swing-stat-card">
            <span class="swing-stat-label">Avg Hold</span>
            <span class="swing-stat-value">${stats.avgHoldDays.toFixed(1)} days</span>
        </div>
        ${stats.bestTrade ? `
        <div class="swing-stat-card positive">
            <span class="swing-stat-label">Best Trade</span>
            <span class="swing-stat-value">${stats.bestTrade.symbol}</span>
            <span class="swing-stat-sub">+${formatMoneyWhole(stats.bestTrade.pnl)}</span>
        </div>
        ` : ""}
    </div>`;
    }
    const JOURNAL_PRESET_TAGS = ["swing", "scalp", "mistake", "lesson", "rebalance", "vault"];
    function renderSwingJournalEntry(trade, editingTradeId = null) {
      const isEditing = editingTradeId === trade.id;
      const entryDate = new Date(trade.entryDate).toLocaleDateString();
      const exitDate = new Date(trade.exitDate).toLocaleDateString();
      const exitReasonLabels = { "target_hit": "\u{1F3AF} Target", "stopped": "\u{1F6D1} Stopped", "manual_close": "\u270B Manual" };
      const tags = trade.tags || [];
      if (isEditing) {
        return `
        <div class="swing-journal-entry swing-journal-entry-editing ${trade.pnl >= 0 ? "winner" : "loser"}" data-id="${trade.id}">
            <div class="swing-journal-inline-edit">
                <div class="swing-journal-inline-row">
                    <span class="swing-journal-symbol">${trade.symbol}</span>
                    <div class="swing-journal-inline-fields">
                        <label>Entry $</label><input type="number" class="swing-inline-input" data-field="entryPrice" step="0.01" value="${trade.entryPrice}">
                        <label>Exit $</label><input type="number" class="swing-inline-input" data-field="exitPrice" step="0.01" value="${trade.exitPrice}">
                        <label>Shares</label><input type="number" class="swing-inline-input" data-field="shares" value="${trade.shares}">
                    </div>
                </div>
                <div class="swing-journal-inline-actions">
                    <button class="swing-inline-save alfa-mini-btn" style="border-color:#66bb6a; color:#66bb6a;">Save</button>
                    <button class="swing-inline-cancel alfa-mini-btn" style="border-color:#888; color:#888;">Cancel</button>
                </div>
            </div>
        </div>`;
      }
      const notesRaw = trade.notes || "";
      const notesPreview = notesRaw ? notesRaw.length > 40 ? notesRaw.substring(0, 40) + "\u2026" : notesRaw : "";
      const notesEscaped = notesPreview.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
      return `
        <div class="swing-journal-entry ${trade.pnl >= 0 ? "winner" : "loser"}" data-id="${trade.id}">
            <div class="swing-journal-header">
                <span class="swing-journal-symbol">${trade.symbol}</span>
                <div class="swing-journal-header-right">
                    <span class="swing-journal-pnl ${trade.pnl >= 0 ? "positive" : "negative"}">${trade.pnl >= 0 ? "+" : ""}${formatMoneyWhole(trade.pnl)} (${trade.pnlPercent >= 0 ? "+" : ""}${trade.pnlPercent.toFixed(2)}%)</span>
                    <div class="swing-journal-actions">
                        <button class="swing-journal-edit-btn" data-id="${trade.id}" title="Quick edit">\u270F\uFE0F</button>
                        <button class="swing-journal-full-edit-btn" data-id="${trade.id}" title="Full edit">\u{1F4DD}</button>
                        <button class="swing-journal-delete-btn" data-id="${trade.id}" title="Delete">\u{1F5D1}\uFE0F</button>
                    </div>
                </div>
            </div>
            <div class="swing-journal-details">
                <span>Entry: $${trade.entryPrice.toFixed(2)} \u2192 Exit: $${trade.exitPrice.toFixed(2)}</span>
                <span>${trade.shares.toLocaleString()} shares</span>
            </div>
            <div class="swing-journal-meta">
                <span>${entryDate} \u2192 ${exitDate} (${trade.holdingDays}d)</span>
                <span>${exitReasonLabels[trade.exitReason] || trade.exitReason}</span>
                ${trade.fee > 0 ? `<span style="color:#ff9800;">Fee: ${formatMoneyWhole(trade.fee)}</span>` : ""}
            </div>
            ${tags.length ? `<div class="swing-journal-tags">${tags.map((t) => `<span class="swing-journal-tag">${(t || "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")}</span>`).join("")}</div>` : ""}
            ${notesPreview ? `<div class="swing-journal-notes-preview" title="${(trade.notes || "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;")}">${notesEscaped}</div>` : ""}
        </div>`;
    }
    function renderSwingJournal(editingTradeId = null) {
      const trades = swingTradeData.completedTrades.slice(0, 20);
      if (trades.length === 0) {
        return `<div class="swing-empty">
            <div style="font-size:24px; margin-bottom:8px;">\u{1F4D3}</div>
            <div>No completed trades</div>
        </div>`;
      }
      return trades.map((trade) => renderSwingJournalEntry(trade, editingTradeId)).join("");
    }
    function bindSwingPositionActions() {
      $(".swing-close-btn").off("click").on("click", function() {
        const id = $(this).data("id");
        const pos = swingTradeData.activePositions.find((p) => p.id === id);
        if (pos) openClosePositionModal(pos);
      });
      $(".swing-merge-btn").off("click").on("click", function() {
        openMergeIntoPositionModal($(this).data("id"));
      });
      $(".swing-edit-btn").off("click").on("click", function() {
        const id = $(this).data("id");
        openEditPositionModal(id);
      });
    }
    function bindSwingJournalActions(editingTradeId = null) {
      $(".swing-journal-edit-btn").off("click").on("click", function() {
        const id = $(this).data("id");
        $("#swing-journal-list").html(renderSwingJournal(id));
        bindSwingJournalActions(id);
      });
      $(".swing-journal-full-edit-btn").off("click").on("click", function() {
        openEditJournalEntryModal($(this).data("id"));
      });
      $(".swing-journal-delete-btn").off("click").on("click", function() {
        const id = $(this).data("id");
        const trade = swingTradeData.completedTrades.find((t) => t.id === id);
        if (trade && confirm(`Delete journal entry for ${trade.symbol}?
P&L: ${formatMoney(trade.pnl)}`)) {
          deleteJournalEntry(id);
          refreshSwingJournal();
        }
      });
      $(".swing-inline-save").off("click").on("click", function() {
        const entry = $(this).closest(".swing-journal-entry");
        const id = entry.data("id");
        const trade = swingTradeData.completedTrades.find((t) => t.id === id);
        if (!trade) return;
        trade.entryPrice = parseFloat(entry.find('[data-field="entryPrice"]').val()) || trade.entryPrice;
        trade.exitPrice = parseFloat(entry.find('[data-field="exitPrice"]').val()) || trade.exitPrice;
        trade.shares = parseInt(entry.find('[data-field="shares"]').val()) || trade.shares;
        recalculateCompletedTrade(trade);
        saveSwingTradeData();
        refreshSwingJournal();
      });
      $(".swing-inline-cancel").off("click").on("click", function() {
        refreshSwingJournal();
      });
    }
    function refreshSwingJournal() {
      $("#swing-stats").html(renderSwingStats());
      $("#swing-journal-list").html(renderSwingJournal());
      bindSwingJournalActions();
    }
    function openEditJournalEntryModal(tradeId) {
      const trade = swingTradeData.completedTrades.find((t) => t.id === tradeId);
      if (!trade) return;
      const tags = trade.tags || [];
      const tagsStr = tags.join(", ");
      const exitReasonOptions = [
        { v: "target_hit", l: "\u{1F3AF} Target" },
        { v: "stopped", l: "\u{1F6D1} Stopped" },
        { v: "manual_close", l: "\u270B Manual" }
      ].map((o) => `<option value="${o.v}" ${trade.exitReason === o.v ? "selected" : ""}>${o.l}</option>`).join("");
      const html = `
    <div class="add-position-form">
        <div class="form-group">
            <label>Stock: ${trade.symbol}</label>
            <input type="hidden" id="journal-edit-id" value="${trade.id}">
        </div>
        <div class="form-row">
            <div class="form-group">
                <label>Entry Price ($)</label>
                <input type="number" id="journal-entry-price" class="alfa-input" step="0.01" value="${trade.entryPrice}">
            </div>
            <div class="form-group">
                <label>Exit Price ($)</label>
                <input type="number" id="journal-exit-price" class="alfa-input" step="0.01" value="${trade.exitPrice}">
            </div>
        </div>
        <div class="form-row">
            <div class="form-group">
                <label>Shares</label>
                <input type="number" id="journal-shares" class="alfa-input" value="${trade.shares}">
            </div>
            <div class="form-group">
                <label>Exit Reason</label>
                <select id="journal-exit-reason" class="alfa-select">${exitReasonOptions}</select>
            </div>
        </div>
        <div class="form-row">
            <div class="form-group">
                <label>Entry Date</label>
                <input type="text" id="journal-entry-date" class="alfa-input" value="${new Date(trade.entryDate).toISOString().slice(0, 10)}" placeholder="YYYY-MM-DD">
            </div>
            <div class="form-group">
                <label>Exit Date</label>
                <input type="text" id="journal-exit-date" class="alfa-input" value="${new Date(trade.exitDate).toISOString().slice(0, 10)}" placeholder="YYYY-MM-DD">
            </div>
        </div>
        <div class="form-group">
            <label>Tags (comma-separated)</label>
            <input type="text" id="journal-tags" class="alfa-input" value="${tagsStr}" placeholder="swing, lesson, mistake">
            <span class="form-hint">Presets: ${JOURNAL_PRESET_TAGS.join(", ")}</span>
        </div>
        <div class="form-group">
            <label>Notes</label>
            <textarea id="journal-notes" class="alfa-input journal-notes-textarea" rows="4" placeholder="Lessons learned...">${(trade.notes || "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")}</textarea>
        </div>
        <div class="form-actions">
            <button id="journal-edit-save" class="alfa-main-btn" style="border-color:#caa14a; color:#caa14a; flex:1;">Save</button>
            <button id="journal-edit-cancel" class="alfa-main-btn" style="border-color:#888; color:#888;">Cancel</button>
        </div>
    </div>`;
      createModal("\u270F\uFE0F Edit Journal Entry", html);
      $("#journal-edit-save").on("click", function() {
        const id = $("#journal-edit-id").val();
        const t = swingTradeData.completedTrades.find((x) => x.id === id);
        if (!t) return;
        t.entryPrice = parseFloat($("#journal-entry-price").val()) || t.entryPrice;
        t.exitPrice = parseFloat($("#journal-exit-price").val()) || t.exitPrice;
        t.shares = parseInt($("#journal-shares").val()) || t.shares;
        t.exitReason = $("#journal-exit-reason").val() || t.exitReason;
        const ed = $("#journal-entry-date").val();
        const exd = $("#journal-exit-date").val();
        if (ed) t.entryDate = new Date(ed).getTime();
        if (exd) t.exitDate = new Date(exd).getTime();
        t.notes = $("#journal-notes").val().trim();
        const tagsInput = $("#journal-tags").val().trim();
        t.tags = tagsInput ? tagsInput.split(",").map((s) => s.trim()).filter(Boolean) : [];
        recalculateCompletedTrade(t);
        saveSwingTradeData();
        closeModal();
        refreshSwingJournal();
      });
      $("#journal-edit-cancel").on("click", closeModal);
    }
    function bindSwingSignalActions() {
      $(".swing-signal-track-btn").off("click").on("click", function() {
        const sym = $(this).data("sym");
        const price = parseFloat($(this).data("price"));
        const target = parseFloat($(this).data("target"));
        const stop = parseFloat($(this).data("stop"));
        openAddPositionModal(sym, price, target, stop);
      });
    }
    function updateSignalCardsForTrackedSymbol(symbol) {
      $(".swing-signal-card.buy[data-sym='" + symbol + "']").each(function() {
        const $btn = $(this).find(".swing-signal-track-btn");
        if ($btn.length) {
          $btn.replaceWith('<span class="swing-already-tracking">Already tracking</span>');
        }
      });
    }
    function openAddPositionModal(prefillSym = "", prefillPrice = 0, prefillTarget = 3, prefillStop = 2) {
      const stockOptions = Object.keys(STOCK_DATA).sort().map(
        (sym) => `<option value="${sym}" ${sym === prefillSym ? "selected" : ""}>${sym}</option>`
      ).join("");
      let currentPrice = prefillPrice;
      if (prefillSym && !currentPrice) {
        currentPrice = getPrice(prefillSym) || 0;
      }
      let currentShares = 0;
      if (prefillSym) {
        currentShares = getOwnedShares(prefillSym);
      }
      const html = `
    <div class="add-position-form">
        <div class="form-group">
            <label>Stock Symbol</label>
            <select id="pos-symbol" class="alfa-select">
                <option value="">Select Stock...</option>
                ${stockOptions}
            </select>
        </div>
        
        <div id="pos-merge-notice" class="pos-merge-notice" style="display:none;">
            <span>You already have an open position for <strong id="pos-merge-sym"></strong>.</span>
            <div class="pos-merge-buttons">
                <button id="pos-merge-btn" class="alfa-mini-btn" style="border-color:#caa14a; color:#caa14a;">Add to existing</button>
                <button id="pos-create-new-btn" class="alfa-mini-btn" style="border-color:#888; color:#888;">Create new position</button>
            </div>
        </div>
        
        <div class="form-row">
            <div class="form-group">
                <label>Entry Price ($)</label>
                <input type="number" id="pos-entry-price" class="alfa-input" step="0.01" value="${currentPrice.toFixed(2)}" placeholder="0.00">
                <span class="form-hint" id="pos-current-price">Current: $${currentPrice.toFixed(2)}</span>
            </div>
            <div class="form-group">
                <label id="pos-shares-label">Shares</label>
                <input type="number" id="pos-shares" class="alfa-input" value="${currentShares}" placeholder="0">
                <span class="form-hint" id="pos-owned-shares">Owned: ${currentShares.toLocaleString()}</span>
            </div>
        </div>

        <div class="form-group pos-source-modes">
            <label class="quick-trade-label">Position source</label>
            <div class="pos-source-options">
                <label class="pos-source-option"><input type="radio" name="pos-source" value="track" ${currentShares > 0 ? "checked" : ""}> Track owned</label>
                <label class="pos-source-option"><input type="radio" name="pos-source" value="buy" ${currentShares > 0 ? "" : "checked"}> Buy with cash</label>
                <label class="pos-source-option"><input type="radio" name="pos-source" value="track_buy"> Track + buy more</label>
            </div>
            <div id="pos-buy-extra-wrap" class="form-group" style="display:none; margin-top:4px;">
                <label id="pos-buy-amount-label">Amount to buy ($)</label>
                <input type="text" id="pos-buy-amount" class="alfa-input" value="" placeholder="e.g. 100k, 1m">
                <span class="form-hint" id="pos-cash-hint">Cash: -- \u2248 0 shares</span>
            </div>
            <div id="pos-trade-status" class="gamble-status" style="margin-top:6px;"></div>
        </div>
        
        <div class="form-row">
            <div class="form-group">
                <label id="pos-target-label">Target % Gain</label>
                <div class="form-row" style="gap:8px; align-items:center;">
                    <input type="number" id="pos-target" class="alfa-input" step="0.01" value="${prefillTarget}" placeholder="3" style="flex:1;">
                    <select id="pos-target-unit" class="alfa-select" style="width:56px;">
                        <option value="pct">%</option>
                        <option value="dol">$</option>
                    </select>
                </div>
                <span class="form-hint" id="pos-target-hint">Target: $${(currentPrice * (1 + prefillTarget / 100)).toFixed(2)}</span>
            </div>
            <div class="form-group">
                <label id="pos-stop-label">Stop % Loss</label>
                <div class="form-row" style="gap:8px; align-items:center;">
                    <input type="number" id="pos-stop" class="alfa-input" step="0.01" value="${prefillStop}" placeholder="2" style="flex:1;">
                    <select id="pos-stop-unit" class="alfa-select" style="width:56px;">
                        <option value="pct">%</option>
                        <option value="dol">$</option>
                    </select>
                </div>
                <span class="form-hint" id="pos-stop-hint">Stop: $${(currentPrice * (1 - prefillStop / 100)).toFixed(2)}</span>
            </div>
        </div>
        
        <div class="form-group">
            <label>Notes (optional)</label>
            <textarea id="pos-notes" class="alfa-input journal-notes-textarea" rows="2" placeholder="Why are you taking this trade?"></textarea>
        </div>
        
        <div class="form-summary" id="pos-summary">
            <div class="summary-row">
                <span>Position Value:</span>
                <span id="pos-value">$${(currentPrice * currentShares).toLocaleString("en-US", { maximumFractionDigits: 2 })}</span>
            </div>
            <div class="summary-row" id="pos-buy-cost-row" style="display:none;">
                <span>Buy Cost:</span>
                <span id="pos-buy-cost">$0</span>
            </div>
            <div class="summary-row">
                <span>Potential Profit:</span>
                <span id="pos-potential-profit" class="positive">+$${(currentPrice * (prefillTarget / 100) * currentShares).toFixed(2)}</span>
            </div>
            <div class="summary-row">
                <span>Max Loss:</span>
                <span id="pos-max-loss" class="negative">-$${(currentPrice * (prefillStop / 100) * currentShares).toFixed(2)}</span>
            </div>
            <div class="summary-row">
                <span>Risk/Reward:</span>
                <span id="pos-risk-reward">${(prefillTarget / prefillStop).toFixed(2)}:1</span>
            </div>
        </div>
        
        <div class="form-actions">
            <button id="pos-submit" class="alfa-main-btn" style="border-color:#66bb6a; color:#66bb6a; flex:1;">Add Position</button>
            <button id="pos-cancel" class="alfa-main-btn" style="border-color:#888; color:#888;">Cancel</button>
        </div>
    </div>`;
      createModal("\u{1F4CA} Add Swing Position", html);
      (function enhancePosSymbolSelect() {
        const $sel = $("#pos-symbol");
        if (!$sel.length || $sel.data("enhanced")) return;
        $sel.data("enhanced", true);
        const $wrap = $('<div class="pos-symbol-wrap"></div>');
        $sel.before($wrap);
        $wrap.append($sel.addClass("pos-symbol-native"));
        const selectedLabel = () => {
          const t = $sel.find("option:selected").text();
          return t || "Select Stock...";
        };
        const $btn = $(`<button type="button" class="alfa-select pos-symbol-trigger">${selectedLabel()}</button>`);
        const $menu = $('<div class="pos-symbol-menu" hidden></div>');
        $wrap.append($btn, $menu);
        function rebuildMenu() {
          $menu.empty();
          $sel.find("option").each(function() {
            const val = String(this.value);
            const text = $(this).text();
            const $item = $(`<button type="button" class="pos-symbol-option" data-value="${val}"></button>`).text(text);
            if (val === String($sel.val() || "")) $item.addClass("is-selected");
            $menu.append($item);
          });
        }
        function closeMenu() {
          $menu.prop("hidden", true);
          $btn.attr("aria-expanded", "false");
        }
        function openMenu() {
          rebuildMenu();
          $menu.prop("hidden", false);
          $btn.attr("aria-expanded", "true");
        }
        $btn.on("click", function(e) {
          e.preventDefault();
          e.stopPropagation();
          if ($menu.prop("hidden")) openMenu();
          else closeMenu();
        });
        $menu.on("click", ".pos-symbol-option", function(e) {
          e.preventDefault();
          e.stopPropagation();
          const val = String($(this).data("value") ?? "");
          $sel.val(val).trigger("change");
          $btn.text($(this).text());
          closeMenu();
        });
        $("#alfa-modal-overlay").on("mousedown.posSymbol", function(e) {
          if (!$(e.target).closest(".pos-symbol-wrap").length) closeMenu();
        });
      })();
      function checkMergeNotice() {
        const sym = $("#pos-symbol").val();
        const existing = swingTradeData.activePositions.find((p) => p.symbol === sym);
        if (existing) {
          $("#pos-merge-notice").show();
          $("#pos-merge-sym").text(sym);
        } else {
          $("#pos-merge-notice").hide();
        }
      }
      $("#pos-merge-btn").on("click", function() {
        const sym = $("#pos-symbol").val();
        const existing = swingTradeData.activePositions.find((p) => p.symbol === sym);
        if (existing) {
          closeModal();
          openMergeIntoPositionModal(existing.id);
        }
      });
      $("#pos-create-new-btn").on("click", function() {
        $("#pos-merge-notice").hide();
      });
      function getPosSourceMode() {
        return $("input[name='pos-source']:checked").val() || "track";
      }
      function syncPosSourceUI(opts2 = {}) {
        const mode = getPosSourceMode();
        const sym = $("#pos-symbol").val();
        const owned = sym ? getOwnedShares(sym) : 0;
        const cash = getMoneyFast() || 0;
        $("#pos-owned-shares").text(`Owned: ${owned.toLocaleString()}`);
        $("#pos-cash-hint").text(`Cash: ${formatMoney(cash)} \u2248 0 shares`);
        if (mode === "track") {
          $("#pos-shares-label").text("Shares to track");
          $("#pos-shares").closest(".form-group").show();
          $("#pos-buy-extra-wrap").hide();
          $("#pos-buy-cost-row").hide();
          $("#pos-submit").text("Add Position");
          if (opts2.resetShares) $("#pos-shares").val(owned > 0 ? owned : 0);
        } else if (mode === "buy") {
          $("#pos-shares").closest(".form-group").hide();
          $("#pos-buy-extra-wrap").show();
          $("#pos-buy-cost-row").show();
          $("#pos-submit").text("Buy & Add");
          if (opts2.resetShares) $("#pos-buy-amount").val(cash > 0 ? String(Math.round(cash)) : "");
        } else {
          $("#pos-shares-label").text("Shares to track");
          $("#pos-shares").closest(".form-group").show();
          $("#pos-buy-extra-wrap").show();
          $("#pos-buy-cost-row").show();
          $("#pos-submit").text("Buy & Add");
          if (opts2.resetShares) {
            $("#pos-shares").val(owned > 0 ? owned : 0);
            $("#pos-buy-amount").val("");
          }
        }
        updatePositionSummary();
      }
      $("#pos-symbol").on("change", function() {
        const sym = $(this).val();
        if (sym) {
          const price = getPrice(sym) || 0;
          const owned = getOwnedShares(sym);
          $("#pos-entry-price").val(price.toFixed(2));
          $("#pos-current-price").text(`Current: $${price.toFixed(2)}`);
          $("input[name='pos-source'][value='" + (owned > 0 ? "track" : "buy") + "']").prop("checked", true);
          syncPosSourceUI({ resetShares: true });
          checkMergeNotice();
        } else {
          $("#pos-merge-notice").hide();
        }
      });
      checkMergeNotice();
      syncPosSourceUI({ resetShares: false });
      $("input[name='pos-source']").on("change", function() {
        syncPosSourceUI({ resetShares: true });
      });
      $("#pos-entry-price, #pos-shares, #pos-buy-amount, #pos-target, #pos-stop, #pos-target-unit, #pos-stop-unit").on("input change", updatePositionSummary);
      function getTargetStopPrices() {
        const price = parseFloat($("#pos-entry-price").val()) || 0;
        const targetVal = parseFloat($("#pos-target").val());
        const stopVal = parseFloat($("#pos-stop").val());
        const targetUnit = $("#pos-target-unit").val();
        const stopUnit = $("#pos-stop-unit").val();
        let targetPrice, stopPrice, targetPercent, stopPercent;
        if (targetUnit === "dol" && targetVal != null && !isNaN(targetVal)) {
          targetPrice = targetVal;
          targetPercent = price > 0 ? (targetPrice / price - 1) * 100 : 0;
        } else {
          targetPercent = targetVal != null && !isNaN(targetVal) ? targetVal : 3;
          targetPrice = price * (1 + targetPercent / 100);
        }
        if (stopUnit === "dol" && stopVal != null && !isNaN(stopVal)) {
          stopPrice = stopVal;
          stopPercent = price > 0 ? (1 - stopPrice / price) * 100 : 0;
        } else {
          stopPercent = stopVal != null && !isNaN(stopVal) ? stopVal : 2;
          stopPrice = price * (1 - stopPercent / 100);
        }
        return { price, targetPrice, stopPrice, targetPercent, stopPercent };
      }
      function resolvePosBuyShares(tradePrice) {
        const mode = getPosSourceMode();
        if (mode !== "buy" && mode !== "track_buy") return 0;
        if (tradePrice <= 0) return 0;
        const raw = String($("#pos-buy-amount").val() || "").trim();
        if (!raw) return 0;
        const dollars = parseTornNumber(raw);
        if (!dollars || dollars <= 0 || isNaN(dollars)) return 0;
        return Math.floor(dollars / tradePrice);
      }
      function updatePositionSummary() {
        const { price, targetPrice, stopPrice, targetPercent, stopPercent } = getTargetStopPrices();
        const mode = getPosSourceMode();
        const trackShares = parseInt($("#pos-shares").val(), 10) || 0;
        const tradePrice = getPriceForTrade($("#pos-symbol").val()) || price;
        const buyShares = resolvePosBuyShares(tradePrice);
        const totalShares = mode === "buy" ? buyShares : mode === "track_buy" ? trackShares + buyShares : trackShares;
        const value = tradePrice * totalShares;
        const potentialProfit = (targetPrice - tradePrice) * totalShares;
        const maxLoss = (tradePrice - stopPrice) * totalShares;
        const riskReward = tradePrice - stopPrice > 0 ? (targetPrice - tradePrice) / (tradePrice - stopPrice) : 0;
        const buyCost = buyShares * tradePrice;
        $("#pos-target-hint").text($("#pos-target-unit").val() === "pct" ? `Target: $${targetPrice.toFixed(2)}` : `Target: ${targetPercent.toFixed(2)}%`);
        $("#pos-stop-hint").text($("#pos-stop-unit").val() === "pct" ? `Stop: $${stopPrice.toFixed(2)}` : `Stop: ${stopPercent.toFixed(2)}%`);
        $("#pos-value").text(`$${value.toLocaleString("en-US", { maximumFractionDigits: 2 })}`);
        $("#pos-buy-cost").text(formatMoney(buyCost));
        $("#pos-potential-profit").text(`+$${potentialProfit.toFixed(2)}`);
        $("#pos-max-loss").text(`-$${maxLoss.toFixed(2)}`);
        $("#pos-risk-reward").text(`${riskReward.toFixed(2)}:1`);
        const cash = getMoneyFast() || 0;
        $("#pos-cash-hint").text(`Cash: ${formatMoney(cash)} \u2248 ${buyShares.toLocaleString()} shares`);
      }
      $("#pos-target-unit").on("change", function() {
        const price = parseFloat($("#pos-entry-price").val()) || 0;
        const targetVal = parseFloat($("#pos-target").val());
        if (price <= 0) return;
        if ($(this).val() === "dol") {
          const pct = targetVal != null && !isNaN(targetVal) ? targetVal : 3;
          $("#pos-target").val((price * (1 + pct / 100)).toFixed(2));
        } else {
          const dol = targetVal != null && !isNaN(targetVal) ? targetVal : price * 1.03;
          $("#pos-target").val(price > 0 ? ((dol / price - 1) * 100).toFixed(2) : 3);
        }
        updatePositionSummary();
      });
      $("#pos-stop-unit").on("change", function() {
        const price = parseFloat($("#pos-entry-price").val()) || 0;
        const stopVal = parseFloat($("#pos-stop").val());
        if (price <= 0) return;
        if ($(this).val() === "dol") {
          const pct = stopVal != null && !isNaN(stopVal) ? stopVal : 2;
          $("#pos-stop").val((price * (1 - pct / 100)).toFixed(2));
        } else {
          const dol = stopVal != null && !isNaN(stopVal) ? stopVal : price * 0.98;
          $("#pos-stop").val(price > 0 ? ((1 - dol / price) * 100).toFixed(2) : 2);
        }
        updatePositionSummary();
      });
      $("#pos-submit").on("click", async function() {
        const sym = $("#pos-symbol").val();
        const entryInput = parseFloat($("#pos-entry-price").val()) || 0;
        const mode = getPosSourceMode();
        const trackShares = parseInt($("#pos-shares").val(), 10) || 0;
        const notes = $("#pos-notes").val().trim();
        const { targetPrice, stopPrice, targetPercent, stopPercent } = getTargetStopPrices();
        const $btn = $(this);
        const $status = $("#pos-trade-status");
        if (!sym) {
          alert("Please select a stock");
          return;
        }
        if (entryInput <= 0) {
          alert("Please enter a valid entry price");
          return;
        }
        let finalShares = 0;
        let finalEntry = entryInput;
        if (mode === "track") {
          if (trackShares <= 0) {
            alert("Please enter number of shares to track");
            return;
          }
          const owned = getOwnedShares(sym);
          if (trackShares > owned) {
            alert("You only own " + owned.toLocaleString() + " shares. Switch to Buy, or lower shares.");
            return;
          }
          finalShares = trackShares;
          finalEntry = entryInput;
        } else if (mode === "buy") {
          const tradePrice = getPriceForTrade(sym);
          if (tradePrice <= 0) {
            alert("Price unavailable");
            return;
          }
          const buyShares = resolvePosBuyShares(tradePrice);
          if (buyShares <= 0) {
            alert("Enter a dollar amount to buy (e.g. 100k)");
            return;
          }
          const cash = getMoneyFast() || 0;
          const cost = buyShares * tradePrice;
          if (cost > cash) {
            alert("Insufficient cash. Need " + formatMoney(cost - cash) + " more.");
            return;
          }
          $btn.prop("disabled", true).text("Buying...");
          $status.html('<span style="color:#ffb74d;">Buying ' + buyShares.toLocaleString() + " shares...</span>");
          try {
            await postTradeAsync(sym, buyShares, "buyShares");
            finalShares = buyShares;
            finalEntry = tradePrice;
          } catch (e) {
            console.error("Swing buy error:", e);
            $status.html('<span style="color:#ef5350;">Buy failed: ' + (e.message || e) + "</span>");
            $btn.prop("disabled", false).text("Buy & Add");
            return;
          }
        } else {
          const tradePrice = getPriceForTrade(sym) || entryInput;
          const buyShares = resolvePosBuyShares(tradePrice);
          if (trackShares <= 0 && buyShares <= 0) {
            alert("Enter shares to track and/or a buy amount");
            return;
          }
          if (trackShares > 0) {
            const owned = getOwnedShares(sym);
            if (trackShares > owned) {
              alert("You only own " + owned.toLocaleString() + " shares to track.");
              return;
            }
          }
          if (buyShares > 0) {
            if (tradePrice <= 0) {
              alert("Price unavailable");
              return;
            }
            const cash = getMoneyFast() || 0;
            const cost = buyShares * tradePrice;
            if (cost > cash) {
              alert("Insufficient cash. Need " + formatMoney(cost - cash) + " more.");
              return;
            }
            $btn.prop("disabled", true).text("Buying...");
            $status.html('<span style="color:#ffb74d;">Buying ' + buyShares.toLocaleString() + " shares...</span>");
            try {
              await postTradeAsync(sym, buyShares, "buyShares");
              finalShares = trackShares + buyShares;
              finalEntry = finalShares > 0 ? (trackShares * entryInput + buyShares * tradePrice) / finalShares : tradePrice;
            } catch (e) {
              console.error("Swing buy error:", e);
              $status.html('<span style="color:#ef5350;">Buy failed: ' + (e.message || e) + "</span>");
              $btn.prop("disabled", false).text("Buy & Add");
              return;
            }
          } else {
            finalShares = trackShares;
            finalEntry = entryInput;
          }
        }
        if (finalShares <= 0) {
          alert("Please enter a valid amount");
          return;
        }
        addSwingPosition(sym, finalEntry, finalShares, targetPercent, stopPercent, notes, targetPrice, stopPrice);
        closeModal();
        updateVaultDisplay(true);
        $("#swing-positions-list").html(renderSwingPositions());
        bindSwingPositionActions();
        updateSignalCardsForTrackedSymbol(sym);
      });
      $("#pos-cancel").on("click", closeModal);
    }
    function getSwingBenefitLockedShares(sym, owned) {
      const checkbox = $("#alfa-lock-toggle");
      const lockBlocks = checkbox.length > 0 ? checkbox.is(":checked") : localStorage.getItem("alfa_vault_lock") === "true";
      if (!lockBlocks) return 0;
      const stockData = STOCK_DATA[sym];
      if (!stockData || !owned) return 0;
      const tierInfo = getBenefitTier(sym, owned);
      if (stockData.type === "P") {
        return owned >= stockData.base ? stockData.base : 0;
      }
      if (tierInfo.tier > 0) {
        const minSharesForCurrentTier = stockData.base * (Math.pow(2, tierInfo.tier) - 1);
        return Math.min(minSharesForCurrentTier, owned);
      }
      return 0;
    }
    function openClosePositionModal(pos) {
      if (!pos) return;
      const currentPrice = getPriceForTrade(pos.symbol) || getPrice(pos.symbol) || pos.currentPrice || pos.entryPrice;
      const totalShares = pos.shares;
      const owned = getOwnedShares(pos.symbol);
      const benefitLocked = getSwingBenefitLockedShares(pos.symbol, owned);
      const maxOwnedSellable = Math.max(0, owned);
      const exitReasonOptions = [
        { v: "manual_close", l: "\u270B Manual" },
        { v: "target_hit", l: "\u{1F3AF} Target" },
        { v: "stopped", l: "\u{1F6D1} Stopped" }
      ].map((o) => `<option value="${o.v}">${o.l}</option>`).join("");
      const html = `
    <div class="quick-trade-container">
        <div class="quick-trade-header">
            <span class="quick-trade-symbol">${pos.symbol}</span>
            <span class="quick-trade-price">${formatMoney(currentPrice)}</span>
        </div>

        <div class="quick-trade-info">
            <div class="quick-trade-info-row">
                <span>Entry Price</span>
                <span>${formatMoney(pos.entryPrice)}</span>
            </div>
            <div class="quick-trade-info-row">
                <span>Total Shares</span>
                <span>${pos.shares.toLocaleString()}</span>
            </div>
            <div class="quick-trade-info-row">
                <span>Gross Proceeds</span>
                <span id="close-pos-gross">-</span>
            </div>
            <div class="quick-trade-info-row">
                <span>Fee (0.1%)</span>
                <span id="close-pos-fee" style="color:#888;">-</span>
            </div>
        </div>

        <div class="quick-trade-calc">
            <div class="quick-trade-calc-row highlight">
                <span>Net P&L</span>
                <span id="close-pos-pnl">-</span>
            </div>
        </div>

        <div class="quick-trade-input-section">
            <label class="quick-trade-label">Shares to close</label>
            <div class="form-row" style="gap:8px; align-items:center;">
                <input type="number" id="close-pos-shares" class="alfa-input" min="1" max="${totalShares}" value="${totalShares}" placeholder="${totalShares}" style="flex:1;">
                <span class="form-hint" style="margin:0;">of ${totalShares.toLocaleString()}</span>
            </div>
            <span class="form-hint" id="close-pos-partial-hint" style="display:none;">Partial close: position will stay open with remaining shares.</span>
            <div class="quick-trade-hint" id="close-pos-tier-hint" style="display:none;"></div>
        </div>

        <div class="quick-trade-input-section">
            <label class="quick-trade-label">Exit Reason</label>
            <select id="close-pos-exit-reason" class="alfa-select" style="width:100%;">${exitReasonOptions}</select>
        </div>

        <div class="quick-trade-input-section">
            <label class="swing-sell-toggle">
                <input type="checkbox" id="close-pos-sell" ${maxOwnedSellable > 0 ? "checked" : ""} ${maxOwnedSellable <= 0 ? "disabled" : ""}>
                <span>Sell shares on close</span>
            </label>
            <div class="quick-trade-hint" id="close-pos-sell-hint">${maxOwnedSellable > 0 ? "Sells the full close amount at market, then closes tracking." : "No owned shares to sell. Uncheck sell to close tracking only."}</div>
        </div>

        <div id="close-pos-status" class="gamble-status" style="margin-top:8px;"></div>

        <div class="quick-trade-actions">
            <button id="close-pos-confirm" class="alfa-main-btn qb-btn-sell">Close & Sell</button>
            <button id="close-pos-cancel" class="alfa-main-btn qb-btn-close">Cancel</button>
        </div>
    </div>`;
      createModal(`Close ${pos.symbol} Position`, html);
      function updateCloseSummary() {
        let sh = parseInt($("#close-pos-shares").val(), 10);
        if (isNaN(sh) || sh < 1) sh = 1;
        if (sh > totalShares) sh = totalShares;
        $("#close-pos-shares").val(sh);
        const sellOn = $("#close-pos-sell").is(":checked");
        const sellShares = sellOn ? Math.min(sh, maxOwnedSellable) : sh;
        const grossProceeds = currentPrice * sellShares;
        const fee = grossProceeds * 1e-3;
        const netProceeds = grossProceeds - fee;
        const cost = pos.entryPrice * sellShares;
        const netPnl = netProceeds - cost;
        const pnlPercent = cost > 0 ? (netProceeds - cost) / cost * 100 : 0;
        const isPositive = netPnl >= 0;
        $("#close-pos-gross").text(formatMoney(grossProceeds));
        $("#close-pos-fee").text("-" + formatMoney(fee));
        $("#close-pos-pnl").attr("style", "color:" + (isPositive ? "#8bc34a" : "#ef5350") + ";").text((isPositive ? "+" : "") + formatMoney(netPnl) + " (" + (pnlPercent >= 0 ? "+" : "") + pnlPercent.toFixed(1) + "%)");
        if (sh < totalShares) {
          $("#close-pos-partial-hint").show();
        } else {
          $("#close-pos-partial-hint").hide();
        }
        const dipsIntoTier = sellOn && benefitLocked > 0 && sellShares > Math.max(0, owned - benefitLocked);
        if (dipsIntoTier) {
          $("#close-pos-tier-hint").html(`\u26A0 Selling ${sellShares.toLocaleString()} may drop your benefit tier (tier needs ${benefitLocked.toLocaleString()} shares).`).show();
        } else {
          $("#close-pos-tier-hint").hide().text("");
        }
        if (sellOn && sellShares < sh) {
          $("#close-pos-sell-hint").text(`You own ${owned.toLocaleString()} \u2014 will sell ${sellShares.toLocaleString()} and close that many.`);
        } else if (sellOn) {
          $("#close-pos-sell-hint").text(`Will sell all ${sellShares.toLocaleString()} shares at market, then close tracking.`);
        } else {
          $("#close-pos-sell-hint").text("Closes tracking only \u2014 no market sell.");
        }
        $("#close-pos-confirm").text(sellOn ? "Close & Sell" : "Close Position");
      }
      updateCloseSummary();
      $("#close-pos-shares").on("input change", updateCloseSummary);
      $("#close-pos-sell").on("change", updateCloseSummary);
      $("#close-pos-confirm").on("click", async function() {
        let sh = parseInt($("#close-pos-shares").val(), 10);
        if (isNaN(sh) || sh < 1) {
          alert("Enter at least 1 share to close.");
          return;
        }
        if (sh > totalShares) {
          alert("Shares to close cannot exceed " + totalShares.toLocaleString() + ".");
          return;
        }
        const exitReason = $("#close-pos-exit-reason").val() || "manual_close";
        const sellOn = $("#close-pos-sell").is(":checked");
        const $btn = $(this);
        const $status = $("#close-pos-status");
        const $cancel = $("#close-pos-cancel");
        if (sellOn) {
          const toSell = Math.min(sh, maxOwnedSellable);
          if (toSell <= 0) {
            $status.html('<span style="color:#ef5350;">No owned shares to sell. Uncheck sell to close tracking only.</span>');
            return;
          }
          $btn.prop("disabled", true).text("Selling...");
          $cancel.prop("disabled", true);
          $status.html('<span style="color:#ffb74d;">Placing sell order...</span>');
          try {
            await postTradeAsync(pos.symbol, toSell, "sellShares");
            const sharesToClose2 = toSell >= totalShares ? null : toSell;
            closeSwingPosition(pos.id, currentPrice, exitReason, sharesToClose2);
            closeModal();
            updateVaultDisplay(true);
            $("#swing-positions-list").html(renderSwingPositions());
            bindSwingPositionActions();
            refreshSwingJournal();
          } catch (e) {
            console.error("Swing close sell error:", e);
            $status.html(`<span style="color:#ef5350;">Sell failed: ${e.message || e}</span>`);
            $btn.prop("disabled", false).text("Close & Sell");
            $cancel.prop("disabled", false);
          }
          return;
        }
        const sharesToClose = sh >= totalShares ? null : sh;
        closeModal();
        closeSwingPosition(pos.id, null, exitReason, sharesToClose);
        $("#swing-positions-list").html(renderSwingPositions());
        bindSwingPositionActions();
        refreshSwingJournal();
        showUndoCloseBanner();
      });
      $("#close-pos-cancel").on("click", closeModal);
    }
    function openMergeIntoPositionModal(existingPositionId) {
      const pos = swingTradeData.activePositions.find((p) => p.id === existingPositionId);
      if (!pos) return;
      const totalOwned = getOwnedShares(pos.symbol);
      const looseShares = Math.max(0, totalOwned - pos.shares);
      const costBasisPrice = getLooseSharesAvgPrice(pos.symbol, looseShares);
      const currentPrice = getPrice(pos.symbol) || pos.entryPrice;
      const prefillPrice = costBasisPrice || currentPrice;
      const priceSource = costBasisPrice ? "cost basis" : "current (sync portfolio for cost basis)";
      const html = `
    <div class="add-position-form">
        <div class="form-group">
            <label>Merge into existing ${pos.symbol} position</label>
            <div class="merge-existing-info">
                Position: ${pos.shares.toLocaleString()} shares @ $${pos.entryPrice.toFixed(2)} (avg)
                <br>Target: ${pos.targetPercent}% | Stop: ${pos.stopPercent}%
            </div>
        </div>
        <div class="form-group merge-shares-context">
            <span>You own <strong>${totalOwned.toLocaleString()}</strong> total</span>
            <span>\u2192 <strong>${looseShares.toLocaleString()}</strong> loose shares available to merge</span>
        </div>
        ${looseShares === 0 ? `
        <div class="merge-no-loose">
            No loose shares to merge. Your position (${pos.shares.toLocaleString()}) matches or exceeds total owned (${totalOwned.toLocaleString()}).
        </div>
        ` : ""}
        <div class="form-row">
            <div class="form-group">
                <label>Price per share ($)</label>
                <input type="number" id="merge-price" class="alfa-input" step="0.01" value="${prefillPrice.toFixed(2)}">
                <span class="form-hint" id="merge-price-hint">${costBasisPrice ? "From " + priceSource : priceSource}</span>
            </div>
            <div class="form-group">
                <label>Shares to add</label>
                <input type="number" id="merge-shares" class="alfa-input" value="${looseShares}" placeholder="0">
                <button id="merge-use-all" class="alfa-mini-btn" style="border-color:#caa14a; color:#caa14a; margin-top:4px;">Use all loose</button>
            </div>
        </div>
        <div class="form-group" id="merge-result" style="display:none;">
            <label>New weighted average</label>
            <div class="merge-result-value">
                <span id="merge-new-avg">-</span> / <span id="merge-new-shares">-</span> shares
            </div>
        </div>
        <div class="form-actions">
            <button id="merge-submit" class="alfa-main-btn" style="border-color:#66bb6a; color:#66bb6a; flex:1;" ${looseShares === 0 ? "disabled" : ""}>Merge</button>
            <button id="merge-cancel" class="alfa-main-btn" style="border-color:#888; color:#888;">Cancel</button>
        </div>
    </div>`;
      createModal("\u2795 Merge into Position", html);
      $("#merge-use-all").on("click", function() {
        $("#merge-shares").val(looseShares);
        updateMergePreview();
      });
      function updateMergePreview() {
        const newPrice = parseFloat($("#merge-price").val()) || 0;
        const newShares = parseInt($("#merge-shares").val()) || 0;
        if (newShares <= 0) {
          $("#merge-result").hide();
          return;
        }
        const oldCost = pos.entryPrice * pos.shares;
        const newCost = newPrice * newShares;
        const totalShares = pos.shares + newShares;
        const newAvg = totalShares > 0 ? (oldCost + newCost) / totalShares : pos.entryPrice;
        $("#merge-new-avg").text(`$${newAvg.toFixed(2)}`);
        $("#merge-new-shares").text(totalShares.toLocaleString());
        $("#merge-result").show();
      }
      $("#merge-price, #merge-shares").on("input change", updateMergePreview);
      if (looseShares > 0) updateMergePreview();
      $("#merge-submit").on("click", function() {
        const newPrice = parseFloat($("#merge-price").val()) || 0;
        const newShares = parseInt($("#merge-shares").val()) || 0;
        if (newShares <= 0) {
          alert("Enter shares to add");
          return;
        }
        if (newPrice <= 0) {
          alert("Enter valid price");
          return;
        }
        const oldCost = pos.entryPrice * pos.shares;
        const newCost = newPrice * newShares;
        const totalShares = pos.shares + newShares;
        pos.entryPrice = (oldCost + newCost) / totalShares;
        pos.shares = totalShares;
        pos.targetPrice = pos.entryPrice * (1 + pos.targetPercent / 100);
        pos.stopPrice = pos.entryPrice * (1 - pos.stopPercent / 100);
        saveSwingTradeData();
        closeModal();
        $("#swing-positions-list").html(renderSwingPositions());
        bindSwingPositionActions();
      });
      $("#merge-cancel").on("click", closeModal);
    }
    function openEditPositionModal(positionId) {
      const pos = swingTradeData.activePositions.find((p) => p.id === positionId);
      if (!pos) return;
      const html = `
    <div class="add-position-form">
        <div class="form-group">
            <label>Stock: ${pos.symbol}</label>
            <input type="hidden" id="edit-pos-id" value="${pos.id}">
        </div>
        
        <div class="form-row">
            <div class="form-group">
                <label>Entry Price ($)</label>
                <input type="number" id="edit-entry-price" class="alfa-input" step="0.01" value="${pos.entryPrice.toFixed(2)}">
            </div>
            <div class="form-group">
                <label>Shares</label>
                <input type="number" id="edit-shares" class="alfa-input" value="${pos.shares}">
            </div>
        </div>
        
        <div class="form-row">
            <div class="form-group">
                <label>Target</label>
                <div class="form-row" style="gap:8px; align-items:center;">
                    <input type="number" id="edit-target" class="alfa-input" step="0.01" value="${(pos.targetPercent != null ? pos.targetPercent : pos.entryPrice && pos.targetPrice ? (pos.targetPrice / pos.entryPrice - 1) * 100 : 3).toFixed(2)}" style="flex:1;">
                    <select id="edit-target-unit" class="alfa-select" style="width:56px;">
                        <option value="pct">%</option>
                        <option value="dol">$</option>
                    </select>
                </div>
                <span class="form-hint" id="edit-target-hint">Target: $${(pos.targetPrice != null ? pos.targetPrice : pos.entryPrice * (1 + (pos.targetPercent || 0) / 100)).toFixed(2)}</span>
            </div>
            <div class="form-group">
                <label>Stop</label>
                <div class="form-row" style="gap:8px; align-items:center;">
                    <input type="number" id="edit-stop" class="alfa-input" step="0.01" value="${(pos.stopPercent != null ? pos.stopPercent : pos.entryPrice && pos.stopPrice ? (1 - pos.stopPrice / pos.entryPrice) * 100 : 2).toFixed(2)}" style="flex:1;">
                    <select id="edit-stop-unit" class="alfa-select" style="width:56px;">
                        <option value="pct">%</option>
                        <option value="dol">$</option>
                    </select>
                </div>
                <span class="form-hint" id="edit-stop-hint">Stop: $${(pos.stopPrice != null ? pos.stopPrice : pos.entryPrice * (1 - (pos.stopPercent || 0) / 100)).toFixed(2)}</span>
            </div>
        </div>
        
        <div class="form-group">
            <label>Notes</label>
            <textarea id="edit-notes" class="alfa-input journal-notes-textarea" rows="4" placeholder="Trade notes...">${(pos.notes || "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")}</textarea>
        </div>
        
        <div class="form-actions">
            <button id="edit-save" class="alfa-main-btn" style="border-color:#caa14a; color:#caa14a; flex:1;">Save Changes</button>
            <button id="edit-cancel" class="alfa-main-btn" style="border-color:#888; color:#888;">Cancel</button>
        </div>
    </div>`;
      createModal("\u270F\uFE0F Edit Position", html);
      function editGetTargetStopPrices() {
        const entry = parseFloat($("#edit-entry-price").val()) || pos.entryPrice;
        const targetVal = parseFloat($("#edit-target").val());
        const stopVal = parseFloat($("#edit-stop").val());
        const targetUnit = $("#edit-target-unit").val();
        const stopUnit = $("#edit-stop-unit").val();
        let targetPrice, stopPrice, targetPercent, stopPercent;
        if (targetUnit === "dol" && targetVal != null && !isNaN(targetVal)) {
          targetPrice = targetVal;
          targetPercent = entry > 0 ? (targetPrice / entry - 1) * 100 : pos.targetPercent;
        } else {
          targetPercent = targetVal != null && !isNaN(targetVal) ? targetVal : pos.targetPercent;
          targetPrice = entry * (1 + targetPercent / 100);
        }
        if (stopUnit === "dol" && stopVal != null && !isNaN(stopVal)) {
          stopPrice = stopVal;
          stopPercent = entry > 0 ? (1 - stopPrice / entry) * 100 : pos.stopPercent;
        } else {
          stopPercent = stopVal != null && !isNaN(stopVal) ? stopVal : pos.stopPercent;
          stopPrice = entry * (1 - stopPercent / 100);
        }
        return { targetPrice, stopPrice, targetPercent, stopPercent };
      }
      function editUpdateHints() {
        const { targetPrice, stopPrice, targetPercent, stopPercent } = editGetTargetStopPrices();
        $("#edit-target-hint").text($("#edit-target-unit").val() === "pct" ? `Target: $${targetPrice.toFixed(2)}` : `Target: ${targetPercent.toFixed(2)}%`);
        $("#edit-stop-hint").text($("#edit-stop-unit").val() === "pct" ? `Stop: $${stopPrice.toFixed(2)}` : `Stop: ${stopPercent.toFixed(2)}%`);
      }
      $("#edit-entry-price, #edit-target, #edit-stop, #edit-target-unit, #edit-stop-unit").on("input change", editUpdateHints);
      $("#edit-target-unit").on("change", function() {
        const entry = parseFloat($("#edit-entry-price").val()) || pos.entryPrice;
        const targetVal = parseFloat($("#edit-target").val());
        if (entry <= 0) return;
        if ($(this).val() === "dol") {
          const pct = targetVal != null && !isNaN(targetVal) ? targetVal : pos.targetPercent;
          $("#edit-target").val((entry * (1 + pct / 100)).toFixed(2));
        } else {
          const dol = targetVal != null && !isNaN(targetVal) ? targetVal : pos.targetPrice;
          $("#edit-target").val(entry > 0 ? ((dol / entry - 1) * 100).toFixed(2) : pos.targetPercent);
        }
        editUpdateHints();
      });
      $("#edit-stop-unit").on("change", function() {
        const entry = parseFloat($("#edit-entry-price").val()) || pos.entryPrice;
        const stopVal = parseFloat($("#edit-stop").val());
        if (entry <= 0) return;
        if ($(this).val() === "dol") {
          const pct = stopVal != null && !isNaN(stopVal) ? stopVal : pos.stopPercent;
          $("#edit-stop").val((entry * (1 - pct / 100)).toFixed(2));
        } else {
          const dol = stopVal != null && !isNaN(stopVal) ? stopVal : pos.stopPrice;
          $("#edit-stop").val(entry > 0 ? ((1 - dol / entry) * 100).toFixed(2) : pos.stopPercent);
        }
        editUpdateHints();
      });
      $("#edit-save").on("click", function() {
        const id = $("#edit-pos-id").val();
        const position = swingTradeData.activePositions.find((p) => p.id === id);
        if (!position) return;
        position.entryPrice = parseFloat($("#edit-entry-price").val()) || position.entryPrice;
        position.shares = parseInt($("#edit-shares").val()) || position.shares;
        const { targetPrice, stopPrice, targetPercent, stopPercent } = editGetTargetStopPrices();
        position.targetPrice = targetPrice;
        position.stopPrice = stopPrice;
        position.targetPercent = targetPercent;
        position.stopPercent = stopPercent;
        position.notes = $("#edit-notes").val().trim();
        saveSwingTradeData();
        closeModal();
        $("#swing-positions-list").html(renderSwingPositions());
        bindSwingPositionActions();
      });
      $("#edit-cancel").on("click", closeModal);
    }
    function savePortfolioData() {
      try {
        localStorage.setItem("alfa_portfolio_data", JSON.stringify(portfolioData));
      } catch (e) {
        console.error("Failed to save portfolio data:", e);
      }
    }
    async function fetchStockTransactionLogs(fullSync = false, statusCallback = null) {
      const key = localStorage.getItem("alfa_vault_apikey");
      if (!key) {
        console.error("No API key for portfolio sync");
        return { success: false, error: "No API key" };
      }
      const results = { buys: [], sells: [], newTransactions: 0, pagesScanned: 0, totalLogsScanned: 0 };
      let hasMore = true;
      let nextUrl = null;
      let pagesWithNoStocks = 0;
      const maxPagesWithNoStocks = 50;
      const fromTimestamp = fullSync ? 0 : portfolioData.lastSyncTimestamp;
      try {
        let baseUrl = `https://api.torn.com/v2/user/log?key=${key}&limit=100&sort=desc&log=5510,5511`;
        if (fromTimestamp > 0) {
          baseUrl += `&from=${fromTimestamp}`;
        }
        let useTypeFilter = true;
        let response = await fetch(baseUrl);
        let data = await response.json();
        if (data.error || !data.log) {
          useTypeFilter = false;
          baseUrl = `https://api.torn.com/v2/user/log?key=${key}&limit=100&sort=desc`;
          if (fromTimestamp > 0) {
            baseUrl += `&from=${fromTimestamp}`;
          }
          if (statusCallback) statusCallback("Using unfiltered log fetch (slower)...");
        }
        while (hasMore) {
          results.pagesScanned++;
          if (statusCallback) {
            const stockCount = results.buys.length + results.sells.length;
            statusCallback(`Scanning logs... (${stockCount} stock transactions found, page ${results.pagesScanned})`);
          }
          if (results.pagesScanned > 1 || !useTypeFilter) {
            const url = nextUrl || baseUrl;
            response = await fetch(url);
            data = await response.json();
          }
          if (data.error) {
            console.error("API Error:", data.error);
            if (useTypeFilter && results.pagesScanned === 1) {
              useTypeFilter = false;
              baseUrl = `https://api.torn.com/v2/user/log?key=${key}&limit=100&sort=desc`;
              if (fromTimestamp > 0) {
                baseUrl += `&from=${fromTimestamp}`;
              }
              continue;
            }
            return { success: false, error: data.error.error || "API Error" };
          }
          if (!data.log || data.log.length === 0) {
            hasMore = false;
            break;
          }
          results.totalLogsScanned += data.log.length;
          let foundStocksThisPage = 0;
          for (const entry of data.log) {
            if (entry.details && (entry.details.id === LOG_TYPE_BUY || entry.details.id === LOG_TYPE_SELL)) {
              const symbol = STOCK_ID_MAP[entry.data.stock];
              if (!symbol) continue;
              const transaction = {
                id: entry.id,
                timestamp: entry.timestamp,
                type: entry.details.id === LOG_TYPE_BUY ? "buy" : "sell",
                symbol,
                stockId: entry.data.stock,
                shares: entry.data.amount,
                totalValue: entry.data.worth,
                pricePerShare: parseFloat(String(entry.data.price).replace(/,/g, "")),
                fees: entry.data.fees || 0,
                profit: entry.data.profit || 0
                // Only present on sells
              };
              if (transaction.type === "buy") {
                results.buys.push(transaction);
              } else {
                results.sells.push(transaction);
              }
              results.newTransactions++;
              foundStocksThisPage++;
            }
          }
          if (!useTypeFilter) {
            if (foundStocksThisPage === 0) {
              pagesWithNoStocks++;
              if (pagesWithNoStocks >= maxPagesWithNoStocks && results.newTransactions > 0) {
                if (statusCallback) {
                  statusCallback(`Reached end of stock history (${maxPagesWithNoStocks} pages with no stocks)`);
                }
                hasMore = false;
                break;
              }
            } else {
              pagesWithNoStocks = 0;
            }
          }
          if (data._metadata && data._metadata.links && data._metadata.links.prev) {
            const prevLink = data._metadata.links.prev;
            if (prevLink.includes("?")) {
              nextUrl = prevLink + `&key=${key}`;
            } else {
              nextUrl = prevLink + `?key=${key}`;
            }
          } else {
            hasMore = false;
          }
          if (hasMore) {
            await new Promise((r) => setTimeout(r, 150));
          }
        }
        if (statusCallback) {
          statusCallback(`Found ${results.newTransactions} stock transactions in ${results.pagesScanned} pages`);
        }
        return { success: true, ...results };
      } catch (e) {
        console.error("Transaction fetch error:", e);
        return { success: false, error: e.message };
      }
    }
    function processTransactions(fetchResult) {
      if (!fetchResult.success) return;
      const allTransactions = [...fetchResult.buys, ...fetchResult.sells];
      allTransactions.sort((a, b) => a.timestamp - b.timestamp);
      const existingIds = new Set(portfolioData.transactions.map((t) => t.id));
      const newTransactions = allTransactions.filter((t) => !existingIds.has(t.id));
      if (newTransactions.length === 0) {
        return { added: 0 };
      }
      portfolioData.transactions.push(...newTransactions);
      portfolioData.transactions.sort((a, b) => a.timestamp - b.timestamp);
      if (portfolioData.transactions.length > 0) {
        const latest = portfolioData.transactions[portfolioData.transactions.length - 1];
        portfolioData.lastSyncTimestamp = latest.timestamp;
      }
      recalculateCostBasis();
      savePortfolioData();
      return { added: newTransactions.length };
    }
    function getLooseSharesAvgPrice(symbol, looseShares) {
      const basis = portfolioData.costBasis[symbol];
      if (!basis?.lots?.length || looseShares <= 0) return null;
      let sharesNeeded = looseShares;
      let totalCost = 0;
      let sharesTaken = 0;
      for (let i = basis.lots.length - 1; i >= 0 && sharesNeeded > 0; i--) {
        const lot = basis.lots[i];
        const take = Math.min(lot.shares, sharesNeeded);
        totalCost += take * lot.pricePerShare;
        sharesTaken += take;
        sharesNeeded -= take;
      }
      return sharesTaken > 0 ? totalCost / sharesTaken : null;
    }
    function recalculateCostBasis() {
      portfolioData.costBasis = {};
      portfolioData.realizedPL = 0;
      for (const tx of portfolioData.transactions) {
        const sym = tx.symbol;
        if (!portfolioData.costBasis[sym]) {
          portfolioData.costBasis[sym] = {
            totalShares: 0,
            totalCost: 0,
            lots: [],
            // FIFO lots: [{ shares, pricePerShare, timestamp }]
            realizedPL: 0,
            totalBought: 0,
            totalSold: 0
          };
        }
        const basis = portfolioData.costBasis[sym];
        if (tx.type === "buy") {
          basis.lots.push({
            shares: tx.shares,
            pricePerShare: tx.pricePerShare,
            timestamp: tx.timestamp
          });
          basis.totalShares += tx.shares;
          basis.totalCost += tx.totalValue;
          basis.totalBought += tx.totalValue;
        } else {
          let sharesToSell = tx.shares;
          let costOfSold = 0;
          while (sharesToSell > 0 && basis.lots.length > 0) {
            const lot = basis.lots[0];
            if (lot.shares <= sharesToSell) {
              costOfSold += lot.shares * lot.pricePerShare;
              sharesToSell -= lot.shares;
              basis.lots.shift();
            } else {
              costOfSold += sharesToSell * lot.pricePerShare;
              lot.shares -= sharesToSell;
              sharesToSell = 0;
            }
          }
          basis.totalShares -= tx.shares;
          basis.totalCost -= costOfSold;
          basis.totalSold += tx.totalValue;
          basis.realizedPL += tx.profit;
          portfolioData.realizedPL += tx.profit;
        }
      }
    }
    function getPeriodStartTimestamp(period) {
      const now = Math.floor(Date.now() / 1e3);
      const today = /* @__PURE__ */ new Date();
      switch (period) {
        case "day":
          const startOfDay = new Date(today.getFullYear(), today.getMonth(), today.getDate());
          return Math.floor(startOfDay.getTime() / 1e3);
        case "week":
          return now - 7 * 24 * 60 * 60;
        case "month":
          return now - 30 * 24 * 60 * 60;
        case "year":
          return now - 365 * 24 * 60 * 60;
        case "ytd":
          const startOfYear = new Date(today.getFullYear(), 0, 1);
          return Math.floor(startOfYear.getTime() / 1e3);
        case "all":
        default:
          return 0;
      }
    }
    function calculatePeriodPL(period = "all") {
      const startTimestamp = getPeriodStartTimestamp(period);
      const periodTransactions = portfolioData.transactions.filter((tx) => tx.timestamp >= startTimestamp);
      const result = {
        period,
        startTimestamp,
        buys: periodTransactions.filter((tx) => tx.type === "buy"),
        sells: periodTransactions.filter((tx) => tx.type === "sell"),
        totalBuyValue: 0,
        totalSellValue: 0,
        realizedPL: 0,
        fees: 0,
        netInvested: 0
        // Buys - Sells (net cash flow into stocks)
      };
      for (const tx of result.buys) {
        result.totalBuyValue += tx.totalValue;
      }
      for (const tx of result.sells) {
        result.totalSellValue += tx.totalValue;
        result.realizedPL += tx.profit || 0;
        result.fees += tx.fees || 0;
      }
      result.netInvested = result.totalBuyValue - result.totalSellValue;
      return result;
    }
    function getPeriodLabel(period) {
      switch (period) {
        case "day":
          return "Today";
        case "week":
          return "7 Days";
        case "month":
          return "30 Days";
        case "year":
          return "1 Year";
        case "ytd":
          return "YTD";
        case "all":
          return "All Time";
        default:
          return period;
      }
    }
    function calculatePortfolioPL() {
      const result = {
        totalCostBasis: 0,
        totalCurrentValue: 0,
        unrealizedPL: 0,
        unrealizedPLPercent: 0,
        realizedPL: portfolioData.realizedPL,
        totalFees: 0,
        perStock: {},
        hasIncompleteData: false
        // Flag when we have sells without matching buys
      };
      const allSymbols = /* @__PURE__ */ new Set([
        ...Object.keys(portfolioData.costBasis),
        ...Object.keys(STOCK_DATA)
      ]);
      for (const sym of allSymbols) {
        const basis = portfolioData.costBasis[sym] || {
          totalShares: 0,
          totalCost: 0,
          realizedPL: 0,
          totalBought: 0,
          totalSold: 0,
          lots: []
        };
        const actualShares = getOwnedShares(sym);
        const currentPrice = getPrice(sym);
        const currentValue = actualShares * currentPrice;
        if (actualShares <= 0 && basis.totalBought === 0 && basis.realizedPL === 0) continue;
        const trackedShares = Math.max(0, basis.totalShares);
        let avgCostPerShare = 0;
        let costBasisForHoldings = 0;
        if (actualShares > 0 && basis.lots && basis.lots.length > 0) {
          let sharesAccounted = 0;
          for (const lot of basis.lots) {
            if (sharesAccounted >= actualShares) break;
            const lotShares = Math.min(lot.shares, actualShares - sharesAccounted);
            costBasisForHoldings += lotShares * lot.pricePerShare;
            sharesAccounted += lotShares;
          }
          if (actualShares > sharesAccounted) {
            costBasisForHoldings += (actualShares - sharesAccounted) * currentPrice;
          }
          avgCostPerShare = actualShares > 0 ? costBasisForHoldings / actualShares : 0;
        } else if (trackedShares > 0 && basis.totalCost > 0) {
          avgCostPerShare = basis.totalCost / trackedShares;
          if (actualShares <= trackedShares) {
            costBasisForHoldings = actualShares * avgCostPerShare;
          } else {
            costBasisForHoldings = basis.totalCost + (actualShares - trackedShares) * currentPrice;
          }
        }
        if (basis.totalShares < 0 || actualShares > 0 && trackedShares === 0 && basis.totalBought === 0) {
          result.hasIncompleteData = true;
        }
        const unrealizedPL = currentValue - costBasisForHoldings;
        const unrealizedPLPercent = costBasisForHoldings > 0 ? unrealizedPL / costBasisForHoldings * 100 : 0;
        result.perStock[sym] = {
          shares: actualShares,
          trackedShares,
          avgCost: avgCostPerShare,
          totalCost: costBasisForHoldings,
          currentPrice,
          currentValue,
          unrealizedPL,
          unrealizedPLPercent,
          realizedPL: basis.realizedPL,
          totalBought: basis.totalBought,
          totalSold: basis.totalSold,
          hasIncompleteData: basis.totalShares < 0 || actualShares > 0 && basis.totalBought === 0
        };
        if (actualShares > 0) {
          result.totalCostBasis += costBasisForHoldings;
          result.totalCurrentValue += currentValue;
        }
      }
      result.unrealizedPL = result.totalCurrentValue - result.totalCostBasis;
      result.unrealizedPLPercent = result.totalCostBasis > 0 ? result.unrealizedPL / result.totalCostBasis * 100 : 0;
      for (const tx of portfolioData.transactions) {
        if (tx.type === "sell") {
          result.totalFees += tx.fees || 0;
        }
      }
      return result;
    }
    async function fullPortfolioSync(statusCallback = null) {
      if (statusCallback) statusCallback("Starting full sync...");
      const result = await fetchStockTransactionLogs(true, statusCallback);
      if (!result.success) {
        if (statusCallback) statusCallback(`Sync failed: ${result.error}`);
        return { success: false, error: result.error };
      }
      const allTransactions = [...result.buys || [], ...result.sells || []];
      allTransactions.sort((a, b) => a.timestamp - b.timestamp);
      if (allTransactions.length === 0) {
        if (statusCallback) {
          statusCallback("Sync returned 0 transactions \u2014 keeping existing portfolio data");
        }
        return { success: false, error: "No transactions returned", keptExisting: true };
      }
      portfolioData.transactions = allTransactions;
      portfolioData.lastSyncTimestamp = allTransactions[allTransactions.length - 1].timestamp;
      recalculateCostBasis();
      portfolioData.lastFullSync = Date.now();
      savePortfolioData();
      if (statusCallback) {
        statusCallback(`Synced ${allTransactions.length} transactions`);
      }
      return { success: true, transactions: allTransactions.length };
    }
    async function incrementalPortfolioSync(statusCallback = null) {
      if (statusCallback) statusCallback("Checking for new transactions...");
      const result = await fetchStockTransactionLogs(false, statusCallback);
      if (result.success) {
        const processed = processTransactions(result);
        if (statusCallback) {
          if (processed && processed.added > 0) {
            statusCallback(`Added ${processed.added} new transactions`);
          } else {
            statusCallback("Portfolio up to date");
          }
        }
        return { success: true, added: processed ? processed.added : 0 };
      } else {
        if (statusCallback) statusCallback(`Sync failed: ${result.error}`);
        return { success: false, error: result.error };
      }
    }
    const ADVISOR_ITEMS = {
      364: "Box of Grenades",
      365: "Box of Medical Supplies",
      366: "Erotic DVD",
      367: "Feathery Hotel Coupon",
      368: "Lawyer's Business Card",
      369: "Lottery Voucher",
      370: "Drug Pack",
      817: "Six-Pack of Alcohol",
      818: "Six-Pack of Energy Drink",
      1057: "Gentleman's Cache",
      1112: "Elegant Cache",
      1113: "Naughty Cache",
      1114: "Elderly Cache",
      1115: "Denim Cache",
      1116: "Wannabe Cache",
      1117: "Cutesy Cache"
    };
    const TCC_CACHE_IDS = [1057, 1112, 1113, 1114, 1115, 1116, 1117];
    const ADVISOR_DATA = {
      "MUN": { type: "item", id: 818, freq: 7 },
      "ASS": { type: "item", id: 817, freq: 7 },
      "HRG": { type: "manual", label: "Avg Property Value", freq: 31 },
      "LSC": { type: "item", id: 369, freq: 7 },
      "LAG": { type: "item", id: 368, freq: 7 },
      "FHG": { type: "item", id: 367, freq: 7 },
      "PRN": { type: "item", id: 366, freq: 7 },
      "SYM": { type: "item", id: 370, freq: 7 },
      "TCC": { type: "average", ids: TCC_CACHE_IDS, freq: 31 },
      "THS": { type: "item", id: 365, freq: 7 },
      "EWM": { type: "item", id: 364, freq: 7 },
      "BAG": { type: "passive" },
      "CNC": { type: "cash", val: 8e7, freq: 31 },
      "TSB": { type: "cash", val: 5e7, freq: 31 },
      "TMI": { type: "cash", val: 25e6, freq: 31 },
      "IOU": { type: "cash", val: 12e6, freq: 31 },
      "GRN": { type: "cash", val: 4e6, freq: 31 },
      "TCT": { type: "cash", val: 1e6, freq: 31 }
    };
    let lastNwCache = null;
    let lastSync = 0;
    let itemPrices = {};
    try {
      itemPrices = JSON.parse(localStorage.getItem("alfa_advisor_prices")) || {};
    } catch (e) {
      itemPrices = {};
    }
    let networthSettings = { sources: { inventory: false, points: true, stocks: true }, excludedStocks: [], excludeMode: "all" };
    try {
      let savedNW = JSON.parse(localStorage.getItem("alfa_advisor_networth"));
      if (savedNW) {
        networthSettings = savedNW;
        if (!networthSettings.excludeMode) networthSettings.excludeMode = "all";
      }
    } catch (e) {
    }
    const BANK_BASE_RATES = { "1w": 0.7917, "2w": 1.7833, "1m": 4.3, "2m": 9.8, "3m": 16.5 };
    let bankSettings = { roi_1w: 0, roi_2w: 0, roi_1m: 0, roi_2m: 0, roi_3m: 0, active_period: "2w" };
    try {
      let savedBank = JSON.parse(localStorage.getItem("alfa_advisor_bank"));
      if (savedBank) bankSettings = savedBank;
    } catch (e) {
    }
    let stocks = {}, stockId = {}, stockRows = {}, localShareCache = {};
    const TORNSY_API = "https://tornsy.com/api";
    const CACHE_TTL = {
      stocks: 60 * 1e3,
      // 1 minute (matches Tornsy update frequency)
      ohlc: 5 * 60 * 1e3,
      // 5 minutes (historical data doesn't change much)
      analysis: 5 * 60 * 1e3
      // 5 minutes (computed indicators)
    };
    const DEFAULT_VAULT_CONFIG = {
      stocks: [],
      lockedStocks: [],
      // Stocks protected from vault spread/withdraw
      depositStrategy: "swing_strategy",
      // swing_strategy | equal | dip_weighted | rsi_weighted | roi_priority
      withdrawStrategy: "proportional",
      // loose_first | proportional | worst_roi | best_performers | swing_strategy
      keepAmount: 5e3,
      skipOvervalued: false,
      // Smart spread: skip stocks with SELL/TAKE PROFIT signals
      minimalMode: false,
      // Show only Vault Spread, Rebalance, Withdraw + Lock Blocks/Smart Mode
      modules: { portfolio: true, gamble: true, swing: true, stocks: true },
      // Visibility: Settings → Modules
      excludeLockedBlocksFromValue: false
      // When true, Total Vault Value = loose + swing only (excludes locked blocks)
    };
    const VAULT_SWING_PRESET_SYMBOLS = ["ASS", "BAG", "CBD", "CNC", "ELT", "EWM", "FHG", "GRN", "IOU", "IST", "LAG", "LSC", "MCS", "MUN", "PRN", "SYM", "SYS", "TCC", "TCI", "TCM", "TCP", "TCT", "TGP", "THS", "TMI", "TSB", "WLT"];
    let vaultConfig = {};
    try {
      let saved = JSON.parse(localStorage.getItem("alfa_vault_config"));
      if (saved) vaultConfig = { ...DEFAULT_VAULT_CONFIG, ...saved };
      else vaultConfig = { ...DEFAULT_VAULT_CONFIG };
    } catch (e) {
      vaultConfig = { ...DEFAULT_VAULT_CONFIG };
    }
    let vaultAnalysisCache = {};
    const DEFAULT_GAMBLE_CONFIG = {
      targetStock: "",
      depositPresets: [
        { label: "50k", amount: 5e4 },
        { label: "100k", amount: 1e5 },
        { label: "500k", amount: 5e5 },
        { label: "1m", amount: 1e6 },
        { label: "All", amount: -1 }
      ],
      withdrawPresets: [
        { label: "50k", amount: 5e4 },
        { label: "100k", amount: 1e5 },
        { label: "500k", amount: 5e5 },
        { label: "1m", amount: 1e6 },
        { label: "All", amount: -1 }
      ],
      requireConfirmation: false
    };
    let gambleConfig = {};
    try {
      let saved = JSON.parse(localStorage.getItem("alfa_gamble_config"));
      if (saved) gambleConfig = { ...DEFAULT_GAMBLE_CONFIG, ...saved };
      else gambleConfig = { ...DEFAULT_GAMBLE_CONFIG };
    } catch (e) {
      gambleConfig = { ...DEFAULT_GAMBLE_CONFIG };
    }
    function saveGambleConfig() {
      try {
        localStorage.setItem("alfa_gamble_config", JSON.stringify(gambleConfig));
      } catch (e) {
        console.error("Failed to save gamble config:", e);
      }
    }
    function getGambleTargetStock() {
      if (gambleConfig.targetStock && vaultConfig.stocks.includes(gambleConfig.targetStock)) {
        return gambleConfig.targetStock;
      }
      return vaultConfig.stocks[0] || "";
    }
    let vaultCacheMem = null;
    let vaultCacheFlushTimer = null;
    function loadVaultCacheMem() {
      if (vaultCacheMem) return vaultCacheMem;
      try {
        vaultCacheMem = JSON.parse(localStorage.getItem("alfa_vault_cache")) || {};
      } catch (e) {
        vaultCacheMem = {};
      }
      return vaultCacheMem;
    }
    function scheduleVaultCacheFlush() {
      if (vaultCacheFlushTimer) return;
      vaultCacheFlushTimer = setTimeout(() => {
        vaultCacheFlushTimer = null;
        try {
          localStorage.setItem("alfa_vault_cache", JSON.stringify(vaultCacheMem || {}));
        } catch (e) {
          console.error("Cache write error:", e);
        }
      }, 500);
    }
    function getCached(key, maxAge) {
      try {
        const cache = loadVaultCacheMem();
        const entry = cache[key];
        if (entry && Date.now() - entry.timestamp < maxAge) {
          return entry.data;
        }
      } catch (e) {
        console.error("Cache read error:", e);
      }
      return null;
    }
    function setCache(key, data) {
      try {
        const cache = loadVaultCacheMem();
        cache[key] = { data, timestamp: Date.now() };
        scheduleVaultCacheFlush();
      } catch (e) {
        console.error("Cache write error:", e);
      }
    }
    function clearVaultCache() {
      vaultCacheMem = {};
      if (vaultCacheFlushTimer) {
        clearTimeout(vaultCacheFlushTimer);
        vaultCacheFlushTimer = null;
      }
      try {
        localStorage.removeItem("alfa_vault_cache");
      } catch (e) {
      }
    }
    let _gmXhrFn = void 0;
    let _tornsyUnavailableWarned = false;
    function resolveGmXhr() {
      if (_gmXhrFn !== void 0) return _gmXhrFn;
      try {
        if (typeof GM_xmlhttpRequest === "function") {
          _gmXhrFn = GM_xmlhttpRequest;
          return _gmXhrFn;
        }
      } catch (e) {
      }
      try {
        if (typeof GM !== "undefined" && GM && typeof GM.xmlHttpRequest === "function") {
          _gmXhrFn = GM.xmlHttpRequest.bind(GM);
          return _gmXhrFn;
        }
      } catch (e) {
      }
      try {
        if (typeof unsafeWindow !== "undefined" && unsafeWindow && typeof unsafeWindow.GM_xmlhttpRequest === "function") {
          _gmXhrFn = unsafeWindow.GM_xmlhttpRequest;
          return _gmXhrFn;
        }
      } catch (e) {
      }
      _gmXhrFn = null;
      return null;
    }
    function isGmXhrAvailable() {
      return !!resolveGmXhr();
    }
    function warnTornsyUnavailable(extra) {
      if (_tornsyUnavailableWarned) return;
      _tornsyUnavailableWarned = true;
      const msg = extra || "Tornsy unavailable (no GM_xmlhttpRequest). Using DOM prices; signal weighting skipped. Desktop Tampermonkey/Violentmonkey recommended.";
      console.warn("[Smart Stock Vault]", msg);
      try {
        const el = $("#vault-status");
        if (el && el.length) {
          el.html('<span style="color:#ffb74d;">' + msg + "</span>");
        }
      } catch (e) {
      }
    }
    function gmFetch(url) {
      return new Promise((resolve) => {
        const xhr = resolveGmXhr();
        if (!xhr) {
          warnTornsyUnavailable();
          resolve(null);
          return;
        }
        try {
          xhr({
            method: "GET",
            url,
            onload: function(response) {
              try {
                const data = JSON.parse(response.responseText);
                resolve(data);
              } catch (e) {
                console.error("JSON parse error:", e);
                resolve(null);
              }
            },
            onerror: function(error) {
              console.error("GM_xmlhttpRequest error:", error);
              resolve(null);
            }
          });
        } catch (e) {
          console.error("GM_xmlhttpRequest invoke error:", e);
          warnTornsyUnavailable("Tornsy request failed to start: " + (e && e.message ? e.message : e));
          resolve(null);
        }
      });
    }
    async function fetchWithCache(url, cacheKey, maxAge) {
      const cached = getCached(cacheKey, maxAge);
      if (cached) return cached;
      try {
        let data;
        if (url.includes("tornsy.com")) {
          if (!isGmXhrAvailable()) {
            warnTornsyUnavailable();
            return null;
          }
          data = await gmFetch(url);
        } else {
          const res = await fetch(url);
          data = await res.json();
        }
        if (data) setCache(cacheKey, data);
        return data;
      } catch (e) {
        console.error("Fetch error:", e);
        return null;
      }
    }
    async function fetchTornsyStocks() {
      const url = `${TORNSY_API}/stocks?interval=d1,d7,d14,d30`;
      return await fetchWithCache(url, "tornsy_stocks", CACHE_TTL.stocks);
    }
    async function fetchTornsyOHLC(symbol, interval = "d1", limit = 30) {
      const url = `${TORNSY_API}/${symbol.toLowerCase()}?interval=${interval}&limit=${limit}`;
      return await fetchWithCache(url, `tornsy_ohlc_${symbol}_${interval}`, CACHE_TTL.ohlc);
    }
    function calculateSMA(ohlcData, period) {
      if (!ohlcData || ohlcData.length < period) return 0;
      const closes = ohlcData.slice(-period).map((d) => parseFloat(d[4] || d[1]));
      return closes.reduce((a, b) => a + b, 0) / period;
    }
    function calculateEMA(ohlcData, period) {
      if (!ohlcData || ohlcData.length < period) return 0;
      const closes = ohlcData.map((d) => parseFloat(d[4] || d[1]));
      const multiplier = 2 / (period + 1);
      let ema = closes.slice(0, period).reduce((a, b) => a + b, 0) / period;
      for (let i = period; i < closes.length; i++) {
        ema = (closes[i] - ema) * multiplier + ema;
      }
      return ema;
    }
    function calculateRSI(ohlcData, period = 14) {
      if (!ohlcData || ohlcData.length < period + 1) return 50;
      const closes = ohlcData.map((d) => parseFloat(d[4] || d[1]));
      let gains = 0, losses = 0;
      for (let i = 1; i <= period; i++) {
        const change = closes[i] - closes[i - 1];
        if (change > 0) gains += change;
        else losses += Math.abs(change);
      }
      let avgGain = gains / period;
      let avgLoss = losses / period;
      for (let i = period + 1; i < closes.length; i++) {
        const change = closes[i] - closes[i - 1];
        if (change > 0) {
          avgGain = (avgGain * (period - 1) + change) / period;
          avgLoss = avgLoss * (period - 1) / period;
        } else {
          avgGain = avgGain * (period - 1) / period;
          avgLoss = (avgLoss * (period - 1) + Math.abs(change)) / period;
        }
      }
      if (avgLoss === 0) return 100;
      const rs = avgGain / avgLoss;
      return 100 - 100 / (1 + rs);
    }
    function calculateVolatility(ohlcData, period = 14) {
      if (!ohlcData || ohlcData.length < period + 1) return 0;
      const closes = ohlcData.slice(-period - 1).map((d) => parseFloat(d[4] || d[1]));
      const returns = [];
      for (let i = 1; i < closes.length; i++) {
        const ret = (closes[i] - closes[i - 1]) / closes[i - 1];
        returns.push(ret);
      }
      const mean = returns.reduce((a, b) => a + b, 0) / returns.length;
      const variance = returns.reduce((a, b) => a + Math.pow(b - mean, 2), 0) / returns.length;
      return Math.sqrt(variance) * 100;
    }
    function calculateBollingerBands(ohlcData, period = 20, stdDev = 2) {
      if (!ohlcData || ohlcData.length < period) return { upper: 0, middle: 0, lower: 0, position: 0.5 };
      const closes = ohlcData.slice(-period).map((d) => parseFloat(d[4] || d[1]));
      const middle = closes.reduce((a, b) => a + b, 0) / period;
      const squaredDiffs = closes.map((c) => Math.pow(c - middle, 2));
      const variance = squaredDiffs.reduce((a, b) => a + b, 0) / period;
      const sd = Math.sqrt(variance);
      const upper = middle + stdDev * sd;
      const lower = middle - stdDev * sd;
      const currentPrice = closes[closes.length - 1];
      const position = upper - lower > 0 ? (currentPrice - lower) / (upper - lower) : 0.5;
      return { upper, middle, lower, position: Math.max(0, Math.min(1, position)) };
    }
    function calculateMomentum(ohlcData, period = 7) {
      if (!ohlcData || ohlcData.length < period + 1) return 0;
      const closes = ohlcData.map((d) => parseFloat(d[4] || d[1]));
      const currentPrice = closes[closes.length - 1];
      const pastPrice = closes[closes.length - 1 - period];
      if (pastPrice === 0) return 0;
      return (currentPrice - pastPrice) / pastPrice * 100;
    }
    function calculateADXAndATR(ohlcData, period = 14) {
      const defaultResult = { adx: 25, atr: 0 };
      if (!ohlcData || ohlcData.length < period + 2) return defaultResult;
      const highs = ohlcData.map((d) => parseFloat(d[2] || d[1]));
      const lows = ohlcData.map((d) => parseFloat(d[3] || d[1]));
      const closes = ohlcData.map((d) => parseFloat(d[4] || d[1]));
      const tr = [];
      const plusDM = [];
      const minusDM = [];
      for (let i = 1; i < ohlcData.length; i++) {
        const high = highs[i], low = lows[i], prevHigh = highs[i - 1], prevLow = lows[i - 1], prevClose = closes[i - 1];
        tr.push(Math.max(high - low, Math.abs(high - prevClose), Math.abs(low - prevClose)));
        const upMove = high - prevHigh;
        const downMove = prevLow - low;
        plusDM.push(upMove > downMove && upMove > 0 ? upMove : 0);
        minusDM.push(downMove > upMove && downMove > 0 ? downMove : 0);
      }
      function wilderSmooth(arr) {
        const result = [];
        let sum = 0;
        for (let i = 0; i < arr.length; i++) {
          if (i < period) {
            sum += arr[i];
            result.push(i === period - 1 ? sum / period : 0);
          } else {
            const smoothed = (result[i - 1] * (period - 1) + arr[i]) / period;
            result.push(smoothed);
          }
        }
        return result;
      }
      const smoothedTR = wilderSmooth(tr);
      const smoothedPlusDM = wilderSmooth(plusDM);
      const smoothedMinusDM = wilderSmooth(minusDM);
      const dxValues = [];
      for (let i = period; i < smoothedTR.length; i++) {
        const atr2 = smoothedTR[i];
        if (atr2 <= 0) {
          dxValues.push(0);
          continue;
        }
        const plusDI = 100 * (smoothedPlusDM[i] / atr2);
        const minusDI = 100 * (smoothedMinusDM[i] / atr2);
        const diSum = plusDI + minusDI;
        const dx = diSum > 0 ? 100 * Math.abs(plusDI - minusDI) / diSum : 0;
        dxValues.push(dx);
      }
      if (dxValues.length < period) return defaultResult;
      let adxSum = dxValues.slice(0, period).reduce((a, b) => a + b, 0) / period;
      for (let i = period; i < dxValues.length; i++) {
        adxSum = (adxSum * (period - 1) + dxValues[i]) / period;
      }
      const atr = smoothedTR[smoothedTR.length - 1] || 0;
      return {
        adx: Math.min(100, Math.max(0, adxSum)),
        atr
      };
    }
    function calculateADX(ohlcData, period = 14) {
      const result = calculateADXAndATR(ohlcData, period);
      return typeof result === "object" ? result.adx : result;
    }
    function calculateVolumeMultiplier(ohlcData, period = 7) {
      if (!ohlcData || ohlcData.length < period + 1) return 1;
      const volumes = ohlcData.map((d) => parseFloat(d[5] || 0));
      const currentVolume = volumes[volumes.length - 1];
      const prevVolumes = volumes.slice(-period - 1, -1);
      const avgVolume = prevVolumes.reduce((a, b) => a + b, 0) / period;
      if (avgVolume <= 0 || currentVolume <= 0) return 1;
      return currentVolume / avgVolume;
    }
    function findSupportResistance(ohlcData) {
      if (!ohlcData || ohlcData.length === 0) return { support: 0, resistance: 0 };
      let high = -Infinity, low = Infinity;
      for (const candle of ohlcData) {
        const candleHigh = parseFloat(candle[2] || candle[1]);
        const candleLow = parseFloat(candle[3] || candle[1]);
        if (candleHigh > high) high = candleHigh;
        if (candleLow < low) low = candleLow;
      }
      return { support: low, resistance: high };
    }
    function getSignalFromAnalysis(analysis) {
      let signal = "hold";
      if (analysis.rsi > 70 && analysis.dipFrom7d > 5) signal = "sell";
      else if (analysis.rsi > 60 || analysis.dipFrom7d > 3) signal = "take_profit";
      else if (analysis.rsi < 30 && analysis.dipFrom7d < -3) signal = "buy";
      else if (analysis.rsi < 40 || analysis.dipFrom7d < -2) signal = "good";
      const adx = analysis.adx ?? 25;
      const adxMin = swingTradeData?.settings?.adxMinThreshold ?? 25;
      const adxPartial = swingTradeData?.settings?.adxPartialThreshold ?? 20;
      if (adx < adxPartial) {
        if (signal === "sell") signal = "take_profit";
        else if (signal === "take_profit" || signal === "buy" || signal === "good") signal = "hold";
      } else if (adx < adxMin) {
        if (signal === "sell") signal = "take_profit";
        else if (signal === "take_profit") signal = "hold";
        else if (signal === "buy") signal = "good";
      }
      const volMult = analysis.volumeMultiplier ?? 1;
      const volMin = swingTradeData?.settings?.volumeMinMultiplier ?? 1;
      const volPartial = swingTradeData?.settings?.volumePartialMultiplier ?? 0.8;
      if (volMult < volPartial) {
        if (signal === "sell") signal = "take_profit";
        else if (signal === "take_profit" || signal === "buy" || signal === "good") signal = "hold";
      } else if (volMult < volMin) {
        if (signal === "take_profit") signal = "hold";
        else if (signal === "buy") signal = "good";
      }
      return signal;
    }
    async function analyzeStockForVault(symbol) {
      const cached = getCached(`analysis_${symbol}`, CACHE_TTL.analysis);
      if (cached) {
        vaultAnalysisCache[symbol] = cached;
        return cached;
      }
      if (!isGmXhrAvailable()) {
        warnTornsyUnavailable();
        return null;
      }
      try {
        const ohlcResult = await fetchTornsyOHLC(symbol, "d1", 30);
        if (!ohlcResult || !ohlcResult.data || ohlcResult.data.length === 0) {
          return null;
        }
        const ohlcData = ohlcResult.data;
        const currentPrice = parseFloat(ohlcData[ohlcData.length - 1][4] || ohlcData[ohlcData.length - 1][1]);
        const sma7 = calculateSMA(ohlcData, 7);
        const sma14 = calculateSMA(ohlcData, 14);
        const sma30 = calculateSMA(ohlcData, Math.min(30, ohlcData.length));
        const rsi = calculateRSI(ohlcData, 14);
        const volatility = calculateVolatility(ohlcData, 14);
        const bollinger = calculateBollingerBands(ohlcData, 20, 2);
        const momentum7d = calculateMomentum(ohlcData, 7);
        const { support, resistance } = findSupportResistance(ohlcData);
        const { adx, atr } = calculateADXAndATR(ohlcData, 14);
        const volumeMultiplier = calculateVolumeMultiplier(ohlcData, 7);
        const analysis = {
          symbol,
          price: currentPrice,
          sma7,
          sma14,
          sma30,
          dipFrom7d: sma7 > 0 ? (currentPrice - sma7) / sma7 * 100 : 0,
          dipFrom14d: sma14 > 0 ? (currentPrice - sma14) / sma14 * 100 : 0,
          dipFrom30d: sma30 > 0 ? (currentPrice - sma30) / sma30 * 100 : 0,
          rsi,
          volatility,
          bollingerPosition: bollinger.position,
          bollingerUpper: bollinger.upper,
          bollingerLower: bollinger.lower,
          momentum7d,
          support,
          resistance,
          adx,
          atr,
          volumeMultiplier,
          signal: "neutral",
          timestamp: Date.now()
        };
        analysis.signal = getSignalFromAnalysis(analysis);
        setCache(`analysis_${symbol}`, analysis);
        vaultAnalysisCache[symbol] = analysis;
        return analysis;
      } catch (e) {
        console.error(`Analysis error for ${symbol}:`, e);
        return null;
      }
    }
    async function analyzeAllVaultStocks(statusCallback) {
      const results = {};
      const stocksToAnalyze = vaultConfig.stocks.length > 0 ? vaultConfig.stocks : Object.keys(STOCK_DATA);
      for (let i = 0; i < stocksToAnalyze.length; i++) {
        const sym = stocksToAnalyze[i];
        if (statusCallback) statusCallback(`Analyzing ${sym}... (${i + 1}/${stocksToAnalyze.length})`);
        const analysis = await analyzeStockForVault(sym);
        if (analysis) {
          results[sym] = analysis;
        }
        if (i < stocksToAnalyze.length - 1) {
          await new Promise((r) => setTimeout(r, 100));
        }
      }
      vaultAnalysisCache = results;
      return results;
    }
    async function getQuickStockAnalysis() {
      const stocksData = await fetchTornsyStocks();
      if (!stocksData || !stocksData.data) return {};
      const results = {};
      const intervals = stocksData.intervals || {};
      for (const stock of stocksData.data) {
        if (stock.index) continue;
        const sym = stock.stock;
        const currentPrice = parseFloat(stock.price);
        let price7d = currentPrice, price14d = currentPrice, price30d = currentPrice;
        if (stock.interval) {
          if (stock.interval.d7) price7d = parseFloat(stock.interval.d7.price);
          if (stock.interval.d14) price14d = parseFloat(stock.interval.d14.price);
          if (stock.interval.d30) price30d = parseFloat(stock.interval.d30.price);
        }
        results[sym] = {
          symbol: sym,
          price: currentPrice,
          dipFrom7d: price7d > 0 ? (currentPrice - price7d) / price7d * 100 : 0,
          dipFrom14d: price14d > 0 ? (currentPrice - price14d) / price14d * 100 : 0,
          dipFrom30d: price30d > 0 ? (currentPrice - price30d) / price30d * 100 : 0,
          investors: stock.investors || 0
        };
      }
      return results;
    }
    function loadVaultConfig() {
      try {
        const saved = JSON.parse(localStorage.getItem("alfa_vault_config"));
        if (saved) vaultConfig = { ...DEFAULT_VAULT_CONFIG, ...saved };
        else vaultConfig = { ...DEFAULT_VAULT_CONFIG };
      } catch (e) {
        vaultConfig = { ...DEFAULT_VAULT_CONFIG };
      }
      return vaultConfig;
    }
    function saveVaultConfig() {
      try {
        localStorage.setItem("alfa_vault_config", JSON.stringify(vaultConfig));
      } catch (e) {
        console.error("Failed to save vault config:", e);
      }
    }
    function openSettingsModal() {
      const savedKey = localStorage.getItem("alfa_vault_apikey") || "";
      const lockBlocksChecked = localStorage.getItem("alfa_vault_lock") === "true";
      const allStocks = Object.keys(STOCK_DATA).sort();
      const stockCheckboxes = allStocks.map((sym) => {
        const isSelected = vaultConfig.stocks.includes(sym);
        const stockData = STOCK_DATA[sym];
        const typeLabel = stockData.type === "P" ? "P" : "A";
        return `<label class="vault-stock-option ${isSelected ? "selected" : ""}" data-sym="${sym}">
            <input type="checkbox" class="vault-stock-check" value="${sym}" ${isSelected ? "checked" : ""}>
            <span class="vault-stock-sym">${sym}</span>
            <span class="vault-stock-type">${typeLabel}</span>
        </label>`;
      }).join("");
      const bankInputs = ["1w", "2w", "1m", "2m", "3m"].map(
        (t) => `<div style="display:flex; justify-content:space-between; align-items:center;">
            <span style="font-size:11px; color:#aaa; font-weight:bold; width:35px;">${t}</span>
            <input id="bank-${t}" class="alfa-tbl-input" style="width:70px;" value="${bankSettings["roi_" + t] || 0}">
         </div>`
      ).join("");
      const mod = vaultConfig.modules || { ...DEFAULT_VAULT_CONFIG.modules };
      const html = `
    <div class="settings-tabs">
        <button class="settings-tab active" data-tab="api">API</button>
        <button class="settings-tab" data-tab="vault">Vault</button>
        <button class="settings-tab" data-tab="protection">Protection</button>
        <button class="settings-tab" data-tab="modules">Modules</button>
        <button class="settings-tab" data-tab="advanced">Advanced</button>
    </div>
    
    <div class="settings-content">
        <!-- API Tab -->
        <div class="settings-tab-content" id="settings-tab-api">
            <div class="settings-section">
                <div class="settings-section-title">Torn API Key</div>
                <input type="password" id="settings-apikey" class="alfa-input" style="width:100%; text-align:center; letter-spacing:2px; margin-bottom:10px;" placeholder="Paste your API key here" value="${savedKey}">
                <div id="settings-api-status" class="settings-api-status">
                    ${savedKey ? '<span style="color:#8bc34a;">\u2713 API key configured</span>' : '<span style="color:#888;">No API key set</span>'}
                </div>
                <div class="settings-warning">
                    <strong>Note:</strong> Full Access API key required for P&L tracking and transaction history features.
                </div>
                <button id="settings-validate-key" class="alfa-mini-btn" style="width:100%; margin-top:10px; border-color:#caa14a; color:#caa14a;">Validate API Key</button>
            </div>
        </div>
        
        <!-- Vault Tab -->
        <div class="settings-tab-content" id="settings-tab-vault" style="display:none;">
            <div class="settings-section">
                <div class="settings-section-title">
                    Vault Stocks
                    <span class="settings-stock-count">Selected: <strong id="vault-selected-count">${vaultConfig.stocks.length}</strong></span>
                </div>
                <div class="vault-stock-grid" id="vault-stock-grid">
                    ${stockCheckboxes}
                </div>
                <div class="vault-quick-actions" style="margin-top:10px;">
                    <button id="vault-select-none" class="alfa-mini-btn" style="border-color:#888; color:#888;">Clear All</button>
                    <button id="vault-select-active" class="alfa-mini-btn" style="border-color:#caa14a; color:#caa14a;">Select Active (A)</button>
                    <button id="vault-select-swing" class="alfa-mini-btn" style="border-color:#caa14a; color:#caa14a;" title="Select vault stocks from swing list">Swing List</button>
                    <button id="vault-select-all" class="alfa-mini-btn" style="border-color:#caa14a; color:#caa14a;">Select All</button>
                </div>
            </div>
            
            <div class="settings-row">
                <div class="settings-section" style="flex:1;">
                    <div class="settings-section-title">Deposit Strategy</div>
                    <select id="vault-deposit-strategy" class="alfa-select" style="width:100%;">
                        <option value="swing_strategy" ${vaultConfig.depositStrategy === "swing_strategy" ? "selected" : ""}>Swing Strategy</option>
                        <option value="rsi_weighted" ${vaultConfig.depositStrategy === "rsi_weighted" ? "selected" : ""}>Weighted by RSI</option>
                        <option value="equal" ${vaultConfig.depositStrategy === "equal" ? "selected" : ""}>Equal Split</option>
                        <option value="dip_weighted" ${vaultConfig.depositStrategy === "dip_weighted" ? "selected" : ""}>Weighted by Dip</option>
                        <option value="roi_priority" ${vaultConfig.depositStrategy === "roi_priority" ? "selected" : ""}>ROI Priority</option>
                    </select>
                </div>
                <div class="settings-section" style="flex:1;">
                    <div class="settings-section-title">Withdrawal Strategy</div>
                    <select id="vault-withdraw-strategy" class="alfa-select" style="width:100%;">
                        <option value="proportional" ${vaultConfig.withdrawStrategy === "proportional" ? "selected" : ""}>Proportional</option>
                        <option value="loose_first" ${vaultConfig.withdrawStrategy === "loose_first" ? "selected" : ""}>Loose Shares First</option>
                        <option value="swing_strategy" ${vaultConfig.withdrawStrategy === "swing_strategy" ? "selected" : ""}>Swing Strategy</option>
                        <option value="worst_roi" ${vaultConfig.withdrawStrategy === "worst_roi" ? "selected" : ""}>Worst ROI First</option>
                        <option value="best_performers" ${vaultConfig.withdrawStrategy === "best_performers" ? "selected" : ""}>Best Performers</option>
                    </select>
                </div>
            </div>
            
            <div class="settings-section">
                <div class="settings-section-title">Default Keep Amount</div>
                <input type="text" id="vault-keep-amount" class="alfa-input" style="width:150px;" 
                    value="${vaultConfig.keepAmount > 0 ? vaultConfig.keepAmount.toLocaleString("en-US") : ""}" 
                    placeholder="e.g. 1m, 500k">
                <span style="font-size:11px; color:#666; margin-left:10px;">Cash to keep when using Vault Spread</span>
            </div>
        </div>
        
        <!-- Protection Tab -->
        <div class="settings-tab-content" id="settings-tab-protection" style="display:none;">
            <div class="settings-section">
                <div class="settings-section-title">Block Protection</div>
                <label class="settings-checkbox-label">
                    <input type="checkbox" id="settings-lock-blocks" ${lockBlocksChecked ? "checked" : ""}>
                    <span><strong>Lock Blocks</strong> - Protect benefit tier shares from being sold during withdrawals and rebalancing</span>
                </label>
                <label class="settings-checkbox-label">
                    <input type="checkbox" id="settings-skip-overvalued" ${vaultConfig.skipOvervalued ? "checked" : ""}>
                    <span><strong>Smart Mode</strong> - Skip stocks with SELL or TAKE PROFIT signals when spreading/rebalancing</span>
                </label>
                <label class="settings-checkbox-label">
                    <input type="checkbox" id="settings-exclude-locked-from-value" ${vaultConfig.excludeLockedBlocksFromValue ? "checked" : ""}>
                    <span><strong>Exclude locked blocks from Total Vault Value</strong> - Show only loose + swing-protected value (locked block shares not counted)</span>
                </label>
            </div>
        </div>
        
        <!-- Modules Tab -->
        <div class="settings-tab-content" id="settings-tab-modules" style="display:none;">
            <div class="settings-section">
                <div class="settings-section-title">Visible modules</div>
                <div class="settings-note">Uncheck to hide sections on the main screen. Hidden modules are not loaded until shown (saves work on load).</div>
                <label class="settings-checkbox-label"><input type="checkbox" id="settings-mod-portfolio" ${mod.portfolio ? "checked" : ""}> <span>Portfolio P&L</span></label>
                <label class="settings-checkbox-label"><input type="checkbox" id="settings-mod-gamble" ${mod.gamble ? "checked" : ""}> <span>Gamble Module</span></label>
                <label class="settings-checkbox-label"><input type="checkbox" id="settings-mod-swing" ${mod.swing ? "checked" : ""}> <span>Swing Trading</span></label>
                <label class="settings-checkbox-label"><input type="checkbox" id="settings-mod-stocks" ${mod.stocks ? "checked" : ""}> <span>Vault Stocks (vault breakdown)</span></label>
            </div>
            <div class="settings-section">
                <div class="settings-section-title">Display</div>
                <label class="settings-checkbox-label">
                    <input type="checkbox" id="settings-minimal-mode" ${vaultConfig.minimalMode ? "checked" : ""}>
                    <span><strong>Minimal Mode</strong> - Show only Vault Spread, Rebalance, Withdraw, and options (Lock Blocks, Smart Mode). Use Torn's default stock display.</span>
                </label>
            </div>
        </div>
        
        <!-- Advanced Tab -->
        <div class="settings-tab-content" id="settings-tab-advanced" style="display:none;">
            <div class="settings-section">
                <div class="settings-section-title">Bank ROI Rates (% APR)</div>
                <div class="settings-note">Used for daily income calculations</div>
                <div class="settings-bank-inputs">
                    ${bankInputs}
                </div>
                <button id="settings-fetch-bank" class="alfa-mini-btn" style="width:100%; margin-top:10px; border-color:#caa14a; color:#caa14a;">Fetch Rates (API)</button>
            </div>
            
            <div class="settings-section">
                <div class="settings-section-title">Cache Management</div>
                <button id="settings-clear-cache" class="alfa-mini-btn" style="width:100%; border-color:#ef5350; color:#ef5350;">Clear Analysis Cache</button>
            </div>
        </div>
    </div>
    
    <div class="settings-actions">
        <button id="settings-save" class="alfa-main-btn" style="flex:1; border-color:#8bc34a; color:#8bc34a;">Save & Close</button>
    </div>`;
      createModal("Settings", html);
      $(".settings-tab").on("click", function() {
        const tab = $(this).data("tab");
        $(".settings-tab").removeClass("active");
        $(this).addClass("active");
        $(".settings-tab-content").hide();
        $(`#settings-tab-${tab}`).show();
      });
      $("#settings-apikey").on("keyup change", function() {
        localStorage.setItem("alfa_vault_apikey", $(this).val().trim());
        if ($(this).val().trim()) {
          $("#settings-api-status").html('<span style="color:#ffd54f;">Key updated - click Validate to test</span>');
        } else {
          $("#settings-api-status").html('<span style="color:#888;">No API key set</span>');
        }
      });
      $("#settings-validate-key").on("click", async function() {
        const key = localStorage.getItem("alfa_vault_apikey");
        if (!key) {
          $("#settings-api-status").html('<span style="color:#ef5350;">No API key to validate</span>');
          return;
        }
        $(this).prop("disabled", true).text("Validating...");
        try {
          const res = await fetch(`https://api.torn.com/user/?selections=basic&key=${key}`);
          const data = await res.json();
          if (data.error) {
            $("#settings-api-status").html(`<span style="color:#ef5350;">\u2717 Invalid: ${data.error.error}</span>`);
          } else {
            $("#settings-api-status").html(`<span style="color:#8bc34a;">\u2713 Valid key for ${data.name} [${data.player_id}]</span>`);
          }
        } catch (e) {
          $("#settings-api-status").html('<span style="color:#ef5350;">\u2717 Connection error</span>');
        }
        $(this).prop("disabled", false).text("Validate API Key");
      });
      $(".vault-stock-check").on("change", function() {
        const sym = $(this).val();
        const isChecked = $(this).is(":checked");
        $(this).closest(".vault-stock-option").toggleClass("selected", isChecked);
        if (isChecked && !vaultConfig.stocks.includes(sym)) {
          vaultConfig.stocks.push(sym);
        } else if (!isChecked) {
          vaultConfig.stocks = vaultConfig.stocks.filter((s) => s !== sym);
        }
        $("#vault-selected-count").text(vaultConfig.stocks.length);
      });
      $("#vault-select-none").on("click", function() {
        $(".vault-stock-check").prop("checked", false);
        $(".vault-stock-option").removeClass("selected");
        vaultConfig.stocks = [];
        $("#vault-selected-count").text(0);
      });
      $("#vault-select-active").on("click", function() {
        $(".vault-stock-check").prop("checked", false);
        $(".vault-stock-option").removeClass("selected");
        vaultConfig.stocks = [];
        Object.keys(STOCK_DATA).forEach((sym) => {
          if (STOCK_DATA[sym].type === "A") {
            $(`.vault-stock-check[value="${sym}"]`).prop("checked", true);
            $(`.vault-stock-option[data-sym="${sym}"]`).addClass("selected");
            vaultConfig.stocks.push(sym);
          }
        });
        $("#vault-selected-count").text(vaultConfig.stocks.length);
      });
      $("#vault-select-swing").on("click", function() {
        $(".vault-stock-check").prop("checked", false);
        $(".vault-stock-option").removeClass("selected");
        vaultConfig.stocks = [];
        VAULT_SWING_PRESET_SYMBOLS.forEach((sym) => {
          if ($(`.vault-stock-check[value="${sym}"]`).length) {
            $(`.vault-stock-check[value="${sym}"]`).prop("checked", true);
            $(`.vault-stock-option[data-sym="${sym}"]`).addClass("selected");
            vaultConfig.stocks.push(sym);
          }
        });
        $("#vault-selected-count").text(vaultConfig.stocks.length);
      });
      $("#vault-select-all").on("click", function() {
        $(".vault-stock-check").prop("checked", false);
        $(".vault-stock-option").removeClass("selected");
        vaultConfig.stocks = [];
        Object.keys(STOCK_DATA).forEach((sym) => {
          $(`.vault-stock-check[value="${sym}"]`).prop("checked", true);
          $(`.vault-stock-option[data-sym="${sym}"]`).addClass("selected");
          vaultConfig.stocks.push(sym);
        });
        $("#vault-selected-count").text(vaultConfig.stocks.length);
      });
      $("#vault-deposit-strategy").on("change", function() {
        vaultConfig.depositStrategy = $(this).val();
      });
      $("#vault-withdraw-strategy").on("change", function() {
        vaultConfig.withdrawStrategy = $(this).val();
      });
      $("#vault-keep-amount").on("change keyup", function() {
        vaultConfig.keepAmount = parseTornNumber($(this).val()) || 0;
      });
      $("#settings-lock-blocks").on("change", function() {
        localStorage.setItem("alfa_vault_lock", $(this).is(":checked"));
      });
      $("#settings-skip-overvalued").on("change", function() {
        vaultConfig.skipOvervalued = $(this).is(":checked");
      });
      $("#settings-minimal-mode").on("change", function() {
        vaultConfig.minimalMode = $(this).is(":checked");
      });
      $(".alfa-tbl-input").on("change keyup", function() {
        ["1w", "2w", "1m", "2m", "3m"].forEach((t) => {
          bankSettings["roi_" + t] = parseFloat($(`#bank-${t}`).val()) || 0;
        });
      });
      $("#settings-fetch-bank").on("click", fetchBankRates);
      $("#settings-clear-cache").on("click", function() {
        if (confirm("Clear all cached analysis data?")) {
          clearVaultCache();
          vaultAnalysisCache = {};
          $(this).text("Cleared!").css("color", "#8bc34a");
          setTimeout(() => $(this).text("Clear Analysis Cache").css("color", "#ef5350"), 1500);
        }
      });
      $("#settings-save").on("click", function() {
        if ($("#settings-exclude-locked-from-value").length) vaultConfig.excludeLockedBlocksFromValue = $("#settings-exclude-locked-from-value").is(":checked");
        if ($("#settings-mod-portfolio").length) {
          vaultConfig.modules = {
            portfolio: $("#settings-mod-portfolio").is(":checked"),
            gamble: $("#settings-mod-gamble").is(":checked"),
            swing: $("#settings-mod-swing").is(":checked"),
            stocks: $("#settings-mod-stocks").is(":checked")
          };
        }
        saveVaultConfig();
        localStorage.setItem("alfa_advisor_networth", JSON.stringify(networthSettings));
        localStorage.setItem("alfa_advisor_bank", JSON.stringify(bankSettings));
        $(this).text("Saved!").css("background", "#8bc34a").css("color", "#111");
        setTimeout(() => {
          $("#alfa-modal-overlay").remove();
          updateVaultDisplay();
        }, 500);
      });
    }
    function openVaultConfigModal() {
      const allStocks = Object.keys(STOCK_DATA).sort();
      const stockCheckboxes = allStocks.map((sym) => {
        const isSelected = vaultConfig.stocks.includes(sym);
        const stockData = STOCK_DATA[sym];
        const typeLabel = stockData.type === "P" ? "P" : "A";
        return `<label class="vault-stock-option ${isSelected ? "selected" : ""}" data-sym="${sym}">
            <input type="checkbox" class="vault-stock-check" value="${sym}" ${isSelected ? "checked" : ""}>
            <span class="vault-stock-sym">${sym}</span>
            <span class="vault-stock-type">${typeLabel}</span>
        </label>`;
      }).join("");
      const html = `
    <div class="vault-config-container">
        <div class="vault-config-section">
            <div class="vault-config-header">
                <span class="vault-config-title">Select Vault Stocks</span>
                <span class="vault-stock-count">Selected: <strong id="vault-selected-count">${vaultConfig.stocks.length}</strong></span>
            </div>
            <div class="vault-stock-grid" id="vault-stock-grid">
                ${stockCheckboxes}
            </div>
            <div class="vault-quick-actions">
                <button id="vault-select-none" class="alfa-mini-btn" style="border-color:#888; color:#888;">Clear All</button>
                <button id="vault-select-active" class="alfa-mini-btn" style="border-color:#caa14a; color:#caa14a;">Select Active (A)</button>
                <button id="vault-select-swing" class="alfa-mini-btn" style="border-color:#caa14a; color:#caa14a;" title="Select vault stocks from swing list">Swing List</button>
                <button id="vault-select-all" class="alfa-mini-btn" style="border-color:#caa14a; color:#caa14a;">Select All</button>
            </div>
        </div>
        
        <div class="vault-config-row">
            <div class="vault-config-section" style="flex:1;">
                <div class="vault-config-header">
                    <span class="vault-config-title">Deposit Strategy</span>
                </div>
                <select id="vault-deposit-strategy" class="alfa-select" style="width:100%;">
                    <option value="swing_strategy" ${vaultConfig.depositStrategy === "swing_strategy" ? "selected" : ""}>Swing Strategy (matches swing scan)</option>
                    <option value="rsi_weighted" ${vaultConfig.depositStrategy === "rsi_weighted" ? "selected" : ""}>Weighted by RSI (oversold priority)</option>
                    <option value="equal" ${vaultConfig.depositStrategy === "equal" ? "selected" : ""}>Equal Split</option>
                    <option value="dip_weighted" ${vaultConfig.depositStrategy === "dip_weighted" ? "selected" : ""}>Weighted by Dip (% below avg)</option>
                    <option value="roi_priority" ${vaultConfig.depositStrategy === "roi_priority" ? "selected" : ""}>ROI Priority (benefit earners)</option>
                </select>
                <div class="vault-strategy-desc" id="deposit-strategy-desc">Prioritize oversold stocks (RSI < 30 gets more allocation)</div>
            </div>
            
            <div class="vault-config-section" style="flex:1;">
                <div class="vault-config-header">
                    <span class="vault-config-title">Withdrawal Strategy</span>
                </div>
                <select id="vault-withdraw-strategy" class="alfa-select" style="width:100%;">
                    <option value="proportional" ${vaultConfig.withdrawStrategy === "proportional" ? "selected" : ""}>Proportional (equal % from each)</option>
                    <option value="loose_first" ${vaultConfig.withdrawStrategy === "loose_first" ? "selected" : ""}>Loose Shares First (Recommended)</option>
                    <option value="swing_strategy" ${vaultConfig.withdrawStrategy === "swing_strategy" ? "selected" : ""}>Swing Strategy</option>
                    <option value="worst_roi" ${vaultConfig.withdrawStrategy === "worst_roi" ? "selected" : ""}>Worst ROI First</option>
                    <option value="best_performers" ${vaultConfig.withdrawStrategy === "best_performers" ? "selected" : ""}>Best Performers (take profits)</option>
                </select>
                <div class="vault-strategy-desc" id="withdraw-strategy-desc">Sell equal percentage from each stock</div>
            </div>
        </div>
        
        <div class="vault-config-section">
            <div class="vault-config-header">
                <span class="vault-config-title">Default Keep Amount</span>
            </div>
            <div style="display:flex; gap:10px; align-items:center;">
                <input type="text" id="vault-keep-amount" class="alfa-input" style="width:150px;" 
                    value="${vaultConfig.keepAmount > 0 ? vaultConfig.keepAmount.toLocaleString("en-US") : ""}" 
                    placeholder="e.g. 1m, 500k">
                <span style="font-size:11px; color:#666;">Cash to keep when using Vault Spread</span>
            </div>
        </div>
        
        <div class="vault-config-actions">
            <button id="vault-config-clear-cache" class="alfa-main-btn" style="border-color:#ef5350; color:#ef5350;">Clear Cache</button>
            <button id="vault-config-save" class="alfa-main-btn" style="border-color:#8bc34a; color:#8bc34a; flex:1;">Save Configuration</button>
        </div>
    </div>`;
      createModal("Vault Configuration", html);
      const depositDescs = {
        "swing_strategy": "Same multi-indicator logic as swing scan (RSI, Bollinger, S/R, ADX, volume)",
        "equal": "Distribute cash evenly across all vault stocks",
        "dip_weighted": "Put more cash into stocks trading below their 7-day average",
        "rsi_weighted": "Prioritize oversold stocks (RSI < 30 gets more allocation)",
        "roi_priority": "Allocate based on benefit ROI (higher ROI = more cash)"
      };
      const withdrawDescs = {
        "loose_first": "Sell shares above benefit thresholds first (protects tiers)",
        "proportional": "Sell equal percentage from each vault stock",
        "swing_strategy": "Sell first from stocks with SELL/TAKE PROFIT signals (same logic as swing scan)",
        "worst_roi": "Sell from lowest ROI stocks first (keep the good ones)",
        "best_performers": "Sell from stocks that are up (take profits)"
      };
      $("#deposit-strategy-desc").text(depositDescs[vaultConfig.depositStrategy] || depositDescs["swing_strategy"]);
      $("#withdraw-strategy-desc").text(withdrawDescs[vaultConfig.withdrawStrategy] || withdrawDescs["proportional"]);
      $(".vault-stock-check").on("change", function() {
        const sym = $(this).val();
        const isChecked = $(this).is(":checked");
        $(this).closest(".vault-stock-option").toggleClass("selected", isChecked);
        if (isChecked && !vaultConfig.stocks.includes(sym)) {
          vaultConfig.stocks.push(sym);
        } else if (!isChecked) {
          vaultConfig.stocks = vaultConfig.stocks.filter((s) => s !== sym);
        }
        $("#vault-selected-count").text(vaultConfig.stocks.length);
      });
      $("#vault-select-none").on("click", function() {
        $(".vault-stock-check").prop("checked", false);
        $(".vault-stock-option").removeClass("selected");
        vaultConfig.stocks = [];
        $("#vault-selected-count").text(0);
      });
      $("#vault-select-active").on("click", function() {
        $(".vault-stock-check").prop("checked", false);
        $(".vault-stock-option").removeClass("selected");
        vaultConfig.stocks = [];
        Object.keys(STOCK_DATA).forEach((sym) => {
          if (STOCK_DATA[sym].type === "A") {
            $(`.vault-stock-check[value="${sym}"]`).prop("checked", true);
            $(`.vault-stock-option[data-sym="${sym}"]`).addClass("selected");
            vaultConfig.stocks.push(sym);
          }
        });
        $("#vault-selected-count").text(vaultConfig.stocks.length);
      });
      $("#vault-select-all").on("click", function() {
        $(".vault-stock-check").prop("checked", false);
        $(".vault-stock-option").removeClass("selected");
        vaultConfig.stocks = [];
        Object.keys(STOCK_DATA).forEach((sym) => {
          $(`.vault-stock-check[value="${sym}"]`).prop("checked", true);
          $(`.vault-stock-option[data-sym="${sym}"]`).addClass("selected");
          vaultConfig.stocks.push(sym);
        });
        $("#vault-selected-count").text(vaultConfig.stocks.length);
      });
      $("#vault-deposit-strategy").on("change", function() {
        vaultConfig.depositStrategy = $(this).val();
        $("#deposit-strategy-desc").text(depositDescs[vaultConfig.depositStrategy]);
      });
      $("#vault-withdraw-strategy").on("change", function() {
        vaultConfig.withdrawStrategy = $(this).val();
        $("#withdraw-strategy-desc").text(withdrawDescs[vaultConfig.withdrawStrategy]);
      });
      $("#vault-keep-amount").on("change keyup", function() {
        vaultConfig.keepAmount = parseTornNumber($(this).val()) || 0;
      });
      $("#vault-config-clear-cache").on("click", function() {
        if (confirm("Clear all cached Tornsy data?")) {
          clearVaultCache();
          vaultAnalysisCache = {};
          $(this).text("Cleared!").css("color", "#8bc34a");
          setTimeout(() => $(this).text("Clear Cache").css("color", "#ef5350"), 1500);
        }
      });
      $("#vault-config-save").on("click", function() {
        saveVaultConfig();
        $(this).text("Saved!").css("background", "#8bc34a").css("color", "#111");
        setTimeout(() => {
          $(this).text("Save Configuration").css("background", "transparent").css("color", "#8bc34a");
          $("#alfa-modal-overlay").remove();
          updateVaultDisplay();
        }, 800);
      });
    }
    async function calculateDailyIncome() {
      let stockIncome = 0;
      let ownedBlocks = [];
      const symbols = Object.keys(STOCK_DATA);
      const IGNORED = ["BAG", "EVL", "CBD", "MCS"];
      for (let sym of symbols) {
        if (IGNORED.includes(sym)) continue;
        let stockData = STOCK_DATA[sym];
        let sharePrice = getPrice(sym);
        if (sharePrice === 0) continue;
        let owned = getOwnedShares(sym);
        let increment = stockData.base;
        let dailyYield = getDailyYield(sym);
        if (sym === "PTS") {
          let ptsPrice = itemPrices["points"] || 0;
          if (ptsPrice > 0) dailyYield = ptsPrice * 100 / 7;
        }
        let currentLevel = 0;
        if (stockData.type === "P") {
          currentLevel = owned >= increment ? 1 : 0;
        } else if (owned >= increment) {
          currentLevel = Math.floor(Math.log2(owned / increment + 1));
        }
        if (currentLevel > 0 && dailyYield > 0) {
          if (stockData.type === "P") {
            let blockCost = increment * sharePrice;
            let blockRoi = blockCost > 0 ? dailyYield * 365 / blockCost * 100 : 0;
            stockIncome += dailyYield;
            ownedBlocks.push({ name: sym, tier: 1, income: dailyYield, value: blockCost, roi: blockRoi });
          } else {
            for (let i = 1; i <= currentLevel; i++) {
              let tierShares = increment * Math.pow(2, i - 1);
              let tierCost = tierShares * sharePrice;
              let tierRoi = tierCost > 0 ? dailyYield * 365 / tierCost * 100 : 0;
              stockIncome += dailyYield;
              ownedBlocks.push({ name: sym, tier: i, income: dailyYield, value: tierCost, roi: tierRoi });
            }
          }
        }
      }
      let bankIncome = 0;
      let bankPrincipal = 0;
      let bankRate = 0;
      const nwData = lastNwCache && lastNwCache.nwFromApi ? await getLiquidNetworth(true) : await getLiquidNetworth(false);
      if (nwData && nwData.bankActive) {
        bankIncome = nwData.dailyBank;
        bankPrincipal = nwData.bankPrincipal;
        if (bankPrincipal > 0) {
          bankRate = bankIncome * 365 / bankPrincipal * 100;
        }
      }
      return {
        stockIncome,
        bankIncome,
        totalIncome: stockIncome + bankIncome,
        bankPrincipal,
        bankRate,
        ownedBlocks
      };
    }
    function getBlockStatus(sym) {
      const stockData = STOCK_DATA[sym];
      if (!stockData) return null;
      const owned = getOwnedShares(sym);
      const price = getPrice(sym);
      const base = stockData.base;
      const isPassive = stockData.type === "P";
      let currentTier = 0;
      if (isPassive) {
        currentTier = owned >= base ? 1 : 0;
      } else if (owned >= base) {
        currentTier = Math.floor(Math.log2(owned / base + 1));
      }
      const maxTier = isPassive ? 1 : null;
      const isMaxed = isPassive && currentTier >= 1;
      let sharesForNextTier, nextTierTotal;
      if (isPassive) {
        sharesForNextTier = base;
        nextTierTotal = base;
      } else {
        const nextTier = currentTier + 1;
        nextTierTotal = base * (Math.pow(2, nextTier) - 1);
        sharesForNextTier = base * Math.pow(2, nextTier - 1);
      }
      const sharesNeeded = Math.max(0, nextTierTotal - owned);
      const progress = nextTierTotal > 0 ? Math.min(100, owned / nextTierTotal * 100) : 0;
      const costToComplete = sharesNeeded * price;
      const dailyYield = getDailyYield(sym);
      const tierCost = sharesForNextTier * price;
      const roi = tierCost > 0 ? dailyYield * 365 / tierCost * 100 : 0;
      const userCash = getMoneyFast();
      const canAfford = costToComplete <= userCash;
      const isClose = progress >= 80 && progress < 100;
      const hasOpportunity = !isMaxed && (isClose || canAfford) && sharesNeeded > 0;
      return {
        symbol: sym,
        currentTier,
        maxTier,
        isMaxed,
        isPassive,
        sharesOwned: owned,
        sharesForNextTier: nextTierTotal,
        sharesNeeded,
        progress,
        costToComplete,
        roi,
        canAfford,
        isClose,
        hasOpportunity,
        price,
        dailyYield
      };
    }
    function calculateTotalVaultValue() {
      const excludeLocked = vaultConfig.excludeLockedBlocksFromValue === true;
      const lockBlocks = $("#alfa-lock-toggle").length > 0 ? $("#alfa-lock-toggle").is(":checked") : localStorage.getItem("alfa_vault_lock") === "true";
      let total = 0;
      for (const sym of vaultConfig.stocks) {
        const owned = getOwnedShares(sym);
        const price = getPrice(sym);
        if (!excludeLocked || !lockBlocks) {
          total += owned * price;
          continue;
        }
        const stockData = STOCK_DATA[sym];
        let benefitLockedShares = 0;
        if (stockData) {
          const tierInfo = getBenefitTier(sym, owned);
          if (stockData.type === "P") {
            benefitLockedShares = owned >= stockData.base ? stockData.base : 0;
          } else {
            const currentTier = tierInfo.tier;
            if (currentTier > 0) {
              const minSharesForCurrentTier = stockData.base * (Math.pow(2, currentTier) - 1);
              benefitLockedShares = Math.min(minSharesForCurrentTier, owned);
            }
          }
        }
        total += Math.max(0, owned - benefitLockedShares) * price;
      }
      return total;
    }
    function calculateVaultBreakdown() {
      const breakdown = [];
      const totalValue = calculateTotalVaultValue();
      const excludeLocked = vaultConfig.excludeLockedBlocksFromValue === true;
      const checkbox = $("#alfa-lock-toggle");
      const lockBlocks = checkbox.length > 0 ? checkbox.is(":checked") : localStorage.getItem("alfa_vault_lock") === "true";
      for (const sym of vaultConfig.stocks) {
        const owned = getOwnedShares(sym);
        const price = getPrice(sym);
        const tierInfo = getBenefitTier(sym, owned);
        const stockData = STOCK_DATA[sym];
        const swingProtectedShares = swingTradeData.activePositions.filter((p) => (p.symbol || "").toUpperCase() === (sym || "").toUpperCase()).reduce((sum, p) => sum + (p.shares || 0), 0);
        let benefitLockedShares = 0;
        if (lockBlocks && stockData) {
          if (stockData.type === "P") {
            benefitLockedShares = owned >= stockData.base ? stockData.base : 0;
          } else {
            const currentTier = tierInfo.tier;
            if (currentTier > 0) {
              const minSharesForCurrentTier = stockData.base * (Math.pow(2, currentTier) - 1);
              benefitLockedShares = Math.min(minSharesForCurrentTier, owned);
            }
          }
        }
        const totalProtected = benefitLockedShares + swingProtectedShares;
        const looseShares = Math.max(0, owned - totalProtected);
        const looseValue = looseShares * price;
        const isLocked = vaultConfig.lockedStocks && vaultConfig.lockedStocks.includes(sym);
        let value = owned * price;
        if (excludeLocked && lockBlocks) {
          value = Math.max(0, owned - benefitLockedShares) * price;
        }
        const percentage = totalValue > 0 ? value / totalValue * 100 : 0;
        breakdown.push({
          symbol: sym,
          owned,
          price,
          value,
          percentage,
          tier: tierInfo.tier,
          lockedShares: totalProtected,
          benefitLockedShares,
          swingProtectedShares,
          looseShares,
          looseValue,
          isLocked,
          hasSwingTrade: swingProtectedShares > 0,
          analysis: vaultAnalysisCache[sym] || null
        });
      }
      return breakdown.sort((a, b) => b.value - a.value);
    }
    function formatCompactNumber(num) {
      const n = Number(num);
      if (!isFinite(n)) return "0";
      const abs = Math.abs(n);
      if (abs >= 1e9) return (n / 1e9).toFixed(2) + "B";
      if (abs >= 1e6) return (n / 1e6).toFixed(2) + "M";
      if (abs >= 1e3) return (n / 1e3).toFixed(2) + "K";
      return n.toLocaleString("en-US", { minimumFractionDigits: 0, maximumFractionDigits: 2 });
    }
    function getSignalBadge(analysis) {
      if (!analysis) return "";
      const signals = {
        "buy": { color: "#66bb6a", text: "BUY", icon: "\u25B2" },
        "good": { color: "#81c784", text: "GOOD", icon: "\u25B2" },
        "hold": { color: "#ffd54f", text: "HOLD", icon: "\u25CF" },
        "take_profit": { color: "#ffb74d", text: "TAKE PROFIT", icon: "\u25BC" },
        "sell": { color: "#ef5350", text: "SELL", icon: "\u25BC" }
      };
      const sig = signals[analysis.signal] || signals.hold;
      return `<span class="vault-signal-badge" style="background:${sig.color}20; color:${sig.color}; border-color:${sig.color};">${sig.icon} ${sig.text}</span>`;
    }
    function persistVaultModuleExpandedState() {
      if ($("#vault-trade-view").is(":visible") || $("#vault-main-content").length && !$("#vault-main-content").is(":visible")) {
        return;
      }
      if ($("#vault-pl-content").length) localStorage.setItem("alfa_pl_expanded", $("#vault-pl-content").is(":visible") ? "true" : "false");
      if ($("#gamble-content").length) localStorage.setItem("alfa_gamble_expanded", $("#gamble-content").is(":visible") ? "true" : "false");
      if ($("#swing-content").length) localStorage.setItem("alfa_swing_expanded", $("#swing-content").is(":visible") ? "true" : "false");
      if ($("#stocks-content").length) localStorage.setItem("alfa_stocks_expanded", $("#stocks-content").is(":visible") ? "true" : "false");
    }
    function renderVaultSection() {
      persistVaultModuleExpandedState();
      $("#alfa-vault-section").remove();
      const savedKey = localStorage.getItem("alfa_vault_apikey") || "";
      const lockBlocks = localStorage.getItem("alfa_vault_lock") === "true";
      if (vaultConfig.stocks.length === 0) {
        const html2 = `
        <div id="alfa-vault-section" class="alfa-vault-section">
            <div class="alfa-vault-header">
                <span class="alfa-vault-title">Smart Stock Vault</span>
                <div class="alfa-vault-actions">
                    <button id="vault-config-btn" class="alfa-mini-btn" style="border-color:#caa14a; color:#caa14a;">Settings</button>
                </div>
            </div>
            <div class="alfa-vault-empty">
                <div style="font-size:14px; margin-bottom:8px;">No vault stocks configured</div>
                <div style="font-size:11px; color:#666;">Click Settings to configure your vault</div>
            </div>
        </div>`;
        mountVaultHtml(html2);
        return;
      }
      const minimalMode = vaultConfig.minimalMode === true;
      const mod = vaultConfig.modules || { ...DEFAULT_VAULT_CONFIG.modules };
      const totalValue = calculateTotalVaultValue();
      const breakdown = calculateVaultBreakdown();
      const lockBlocksChecked = localStorage.getItem("alfa_vault_lock") === "true";
      let plData, hasPortfolioData, lastSyncDate, currentPeriod, periodPL;
      if (!minimalMode && (mod.portfolio || mod.stocks)) {
        plData = calculatePortfolioPL();
        hasPortfolioData = portfolioData.transactions.length > 0;
        lastSyncDate = portfolioData.lastFullSync > 0 ? new Date(portfolioData.lastFullSync).toLocaleDateString() : "Never";
        currentPeriod = localStorage.getItem("alfa_pl_period") || "all";
        periodPL = calculatePeriodPL(currentPeriod);
      }
      const stockBars = !minimalMode && mod.stocks ? breakdown.map((stock) => {
        const barWidth = Math.max(5, stock.percentage);
        const dipDisplay = stock.analysis ? `<span class="${stock.analysis.dipFrom7d < 0 ? "vault-dip-neg" : "vault-dip-pos"}">${stock.analysis.dipFrom7d >= 0 ? "+" : ""}${stock.analysis.dipFrom7d.toFixed(1)}%</span>` : "";
        const rsiDisplay = stock.analysis ? `<span class="vault-rsi ${stock.analysis.rsi < 30 ? "oversold" : stock.analysis.rsi > 70 ? "overbought" : ""}">RSI:${stock.analysis.rsi.toFixed(0)}</span>` : "";
        const lockedClass = stock.isLocked ? "vault-row-locked" : "";
        const stockPL = plData.perStock[stock.symbol];
        let plDisplay = "";
        if (stockPL && stockPL.totalCost > 0) {
          const plColor = stockPL.unrealizedPL >= 0 ? "#8bc34a" : "#ef5350";
          const plSign = stockPL.unrealizedPL >= 0 ? "+" : "";
          plDisplay = `<span class="vault-stock-pl" style="color:${plColor};">${plSign}${stockPL.unrealizedPLPercent.toFixed(1)}%</span>`;
        }
        const swingIndicator = stock.hasSwingTrade ? `<span class="vault-swing-badge" title="Protected by swing trade (${stock.swingProtectedShares.toLocaleString()} shares)">\u{1F4C8}</span>` : "";
        const blockStatus = getBlockStatus(stock.symbol);
        let blockIcon = "";
        let blockProgressBar = "";
        if (blockStatus) {
          const tierLabel = blockStatus.currentTier > 0 ? blockStatus.isPassive ? "1" : blockStatus.currentTier : "";
          let iconClass = "no-block";
          let iconTitle = "No block - click to complete";
          if (blockStatus.isMaxed) {
            iconClass = "maxed";
            iconTitle = "Maximum tier reached \u2713";
          } else if (blockStatus.canAfford) {
            iconClass = "can-afford";
            iconTitle = `Can afford next tier! Cost: ${formatMoney(blockStatus.costToComplete)}`;
          } else if (blockStatus.currentTier > 0) {
            iconClass = blockStatus.isClose ? "has-tier close" : "has-tier";
            iconTitle = `Tier ${blockStatus.currentTier} - ${blockStatus.progress.toFixed(0)}% to next (${formatMoney(blockStatus.costToComplete)})`;
          } else if (blockStatus.isClose) {
            iconClass = "close";
            iconTitle = `${blockStatus.progress.toFixed(0)}% to first tier (${formatMoney(blockStatus.costToComplete)})`;
          }
          blockIcon = `
                <span class="vault-block-icon ${iconClass}" 
                      data-sym="${stock.symbol}" 
                      title="${iconTitle}">
                    <span class="block-icon-shape">\u25A3</span>
                    ${tierLabel ? `<span class="block-tier-num">${tierLabel}</span>` : ""}
                </span>`;
          if (!blockStatus.isMaxed && blockStatus.progress > 0 && blockStatus.progress < 100) {
            blockProgressBar = `
                    <div class="vault-block-progress" title="${blockStatus.sharesOwned.toLocaleString()} / ${blockStatus.sharesForNextTier.toLocaleString()} shares">
                        <div class="vault-block-progress-fill ${blockStatus.isClose ? "close" : ""}" style="width:${blockStatus.progress}%;"></div>
                    </div>`;
          }
        }
        return `
        <div class="vault-stock-row ${lockedClass}" data-sym="${stock.symbol}">
            <div class="vault-stock-info">
                <span class="vault-stock-name">${stock.symbol}</span>
                ${swingIndicator}
                <span class="vault-stock-value">$${formatCompactNumber(stock.value)}</span>
                <span class="vault-stock-pct">(${stock.percentage.toFixed(1)}%)</span>
                ${plDisplay}
                ${getSignalBadge(stock.analysis)}
                <div class="vault-stock-actions">
                    <button class="vault-action-btn vault-quick-buy" data-sym="${stock.symbol}" title="Quick buy">Buy</button>
                    <button class="vault-action-btn vault-quick-sell" data-sym="${stock.symbol}" title="Sell loose shares" ${stock.looseShares <= 0 ? "disabled" : ""}>Sell</button>
                    <button class="vault-action-btn vault-toggle-lock ${stock.isLocked ? "locked" : ""}" data-sym="${stock.symbol}" title="${stock.isLocked ? "Unlock stock" : "Lock stock"}">${stock.isLocked ? "\u{1F512}" : "\u{1F513}"}</button>
                    ${blockIcon}
                </div>
            </div>
            <div class="vault-stock-bar-container">
                <div class="vault-stock-bar" style="width:${barWidth}%;">
                    <div class="vault-stock-bar-loose" style="width:${stock.looseShares > 0 ? stock.looseValue / stock.value * 100 : 0}%;"></div>
                </div>
            </div>
            ${blockProgressBar}
            <div class="vault-stock-details">
                ${dipDisplay} ${rsiDisplay}
                ${stock.looseShares > 0 ? `<span class="vault-loose">Loose: $${formatCompactNumber(stock.looseValue)}</span>` : ""}
            </div>
        </div>`;
      }).join("") : "";
      const html = `
    <div id="alfa-vault-section" class="alfa-vault-section">
        <div class="alfa-vault-header">
            <span class="alfa-vault-title">Smart Stock Vault</span>
            <div class="alfa-vault-actions">
                <button id="vault-config-btn" class="alfa-mini-btn" style="border-color:#888; color:#888;">Settings</button>
            </div>
        </div>
        
        <div id="vault-main-content">
        ${!minimalMode ? `
        <div class="alfa-vault-summary">
            <div class="alfa-vault-total">
                <span class="alfa-vault-total-label">Total Vault Value</span>
                <span class="alfa-vault-total-value">${formatMoneyWhole(totalValue)}</span>
            </div>
            <div class="alfa-vault-meta">
                <span>${vaultConfig.stocks.length} stocks</span>
            </div>
        </div>
        
        <div class="alfa-vault-income-row" id="vault-income-row">
            <span class="vault-income-icon">\u{1F4B0}</span>
            <span class="vault-income-label">Daily Income:</span>
            <span class="vault-income-value" id="vault-daily-income">--</span>
            <span class="vault-income-sep">|</span>
            <span class="vault-income-bank" id="vault-bank-info">\u{1F3E6} Loading...</span>
        </div>
        
        ${mod.portfolio ? `
        <div class="alfa-vault-pl-section" id="vault-pl-section">
            <div class="vault-pl-header" id="vault-pl-toggle">
                <span class="vault-pl-title">\u{1F4CA} Portfolio P&L</span>
                <span class="alfa-caret ${localStorage.getItem("alfa_pl_expanded") !== "false" ? "rotated" : ""}">\u25BC</span>
            </div>
            <div class="vault-pl-content" id="vault-pl-content" style="display:${localStorage.getItem("alfa_pl_expanded") !== "false" ? "block" : "none"};">
                ${hasPortfolioData ? `
                <div class="vault-pl-period-filters">
                    <button class="pl-period-btn ${currentPeriod === "day" ? "active" : ""}" data-period="day">Day</button>
                    <button class="pl-period-btn ${currentPeriod === "week" ? "active" : ""}" data-period="week">Week</button>
                    <button class="pl-period-btn ${currentPeriod === "month" ? "active" : ""}" data-period="month">Month</button>
                    <button class="pl-period-btn ${currentPeriod === "ytd" ? "active" : ""}" data-period="ytd">YTD</button>
                    <button class="pl-period-btn ${currentPeriod === "year" ? "active" : ""}" data-period="year">Year</button>
                    <button class="pl-period-btn ${currentPeriod === "all" ? "active" : ""}" data-period="all">All</button>
                </div>
                <div class="vault-pl-period-summary">
                    <div class="pl-period-title">${getPeriodLabel(currentPeriod)} Activity</div>
                    <div class="pl-period-stats">
                        <div class="pl-period-stat">
                            <span class="pl-stat-label">Buys</span>
                            <span class="pl-stat-value">${periodPL.buys.length} (${formatMoneyWhole(periodPL.totalBuyValue)})</span>
                        </div>
                        <div class="pl-period-stat">
                            <span class="pl-stat-label">Sells</span>
                            <span class="pl-stat-value">${periodPL.sells.length} (${formatMoneyWhole(periodPL.totalSellValue)})</span>
                        </div>
                        <div class="pl-period-stat ${periodPL.realizedPL >= 0 ? "pl-positive" : "pl-negative"}">
                            <span class="pl-stat-label">Realized P&L</span>
                            <span class="pl-stat-value">${periodPL.realizedPL >= 0 ? "+" : ""}${formatMoneyWhole(periodPL.realizedPL)}</span>
                        </div>
                        <div class="pl-period-stat">
                            <span class="pl-stat-label">Net Flow</span>
                            <span class="pl-stat-value" style="color:${periodPL.netInvested > 0 ? "#66bb6a" : periodPL.netInvested < 0 ? "#ef5350" : "#888"};">${periodPL.netInvested > 0 ? "+" : ""}${formatMoneyWhole(periodPL.netInvested)}</span>
                        </div>
                    </div>
                </div>
                
                <div class="vault-pl-divider"></div>
                
                <div class="vault-pl-section-title">Portfolio Totals</div>
                <div class="vault-pl-grid">
                    <div class="vault-pl-item">
                        <span class="vault-pl-label">Cost Basis</span>
                        <span class="vault-pl-value">${plData.totalCostBasis > 0 ? formatMoneyWhole(plData.totalCostBasis) : "Unknown"}</span>
                    </div>
                    <div class="vault-pl-item">
                        <span class="vault-pl-label">Current Value</span>
                        <span class="vault-pl-value">${formatMoneyWhole(plData.totalCurrentValue)}</span>
                    </div>
                    <div class="vault-pl-item ${plData.unrealizedPL >= 0 ? "pl-positive" : "pl-negative"}">
                        <span class="vault-pl-label" ${plData.hasIncompleteData ? 'title="Cost basis may be incomplete when older buys are outside Torn log history"' : ""}>Unrealized P&L</span>
                        <span class="vault-pl-value">${plData.totalCostBasis > 0 ? (plData.unrealizedPL >= 0 ? "+" : "") + formatMoneyWhole(plData.unrealizedPL) + " (" + (plData.unrealizedPLPercent >= 0 ? "+" : "") + plData.unrealizedPLPercent.toFixed(2) + "%)" : "N/A"}</span>
                    </div>
                    <div class="vault-pl-item ${plData.realizedPL >= 0 ? "pl-positive" : "pl-negative"}">
                        <span class="vault-pl-label">Realized P&L \u2713</span>
                        <span class="vault-pl-value">${plData.realizedPL >= 0 ? "+" : ""}${formatMoneyWhole(plData.realizedPL)}</span>
                    </div>
                    <div class="vault-pl-item">
                        <span class="vault-pl-label">Total Fees</span>
                        <span class="vault-pl-value" style="color:#ff9800;">${formatMoneyWhole(plData.totalFees)}</span>
                    </div>
                    <div class="vault-pl-item">
                        <span class="vault-pl-label">Transactions</span>
                        <span class="vault-pl-value">${portfolioData.transactions.length}</span>
                    </div>
                </div>
                <div class="vault-pl-actions">
                    <button id="vault-pl-sync" class="alfa-mini-btn" style="border-color:#caa14a; color:#caa14a;">Sync New</button>
                    <button id="vault-pl-full-sync" class="alfa-mini-btn" style="border-color:#ff9800; color:#ff9800;">Full Sync</button>
                    <button id="vault-pl-history" class="alfa-mini-btn" style="border-color:#888; color:#888;">History</button>
                    <span class="vault-pl-sync-info">Last: ${lastSyncDate}</span>
                </div>
                ` : `
                <div class="vault-pl-empty">
                    <div style="margin-bottom:10px;">No portfolio data yet</div>
                    <button id="vault-pl-initial-sync" class="alfa-main-btn" style="border-color:#caa14a; color:#caa14a;">
                        Sync Transaction History
                    </button>
                    <div style="font-size:10px; color:#666; margin-top:8px;">
                        This will fetch your stock buy/sell history from Torn's logs
                    </div>
                </div>
                `}
            </div>
        </div>
        ` : ""}
        
        ${!minimalMode && mod.gamble ? `
        <div class="alfa-gamble-section" id="gamble-section">
            <div class="gamble-header" id="gamble-toggle">
                <span class="gamble-title">\u{1F3B0} Gamble Module</span>
                <span class="alfa-caret ${localStorage.getItem("alfa_gamble_expanded") !== "false" ? "rotated" : ""}">\u25BC</span>
            </div>
            <div class="gamble-content" id="gamble-content" style="display:${localStorage.getItem("alfa_gamble_expanded") !== "false" ? "block" : "none"};">
                <div class="gamble-target-row">
                    <label class="gamble-target-label">Target Stock</label>
                    <select id="gamble-target-stock" class="alfa-select gamble-target-select">
                        ${vaultConfig.stocks.map((s) => `<option value="${s}" ${getGambleTargetStock() === s ? "selected" : ""}>${s}</option>`).join("")}
                    </select>
                    <button id="gamble-config-btn" class="alfa-mini-btn" style="border-color:#888; color:#888;" title="Configure preset amounts">Configure</button>
                </div>
                <div class="gamble-presets-row">
                    <div class="gamble-presets-group">
                        <div class="gamble-cash-row">
                            <span class="gamble-cash-label">Cash:</span>
                            <span id="gamble-cash-value" class="gamble-cash-value">--</span>
                        </div>
                        <div class="gamble-presets-label">Deposit</div>
                        <div class="gamble-presets-btns">
                            ${(gambleConfig.depositPresets || DEFAULT_GAMBLE_CONFIG.depositPresets).map(
        (p, i) => `<button class="gamble-preset-btn gamble-deposit" data-amount="${p.amount}" data-label="${p.label}">${p.label}</button>`
      ).join("")}
                        </div>
                    </div>
                    <div class="gamble-presets-group">
                        <div class="gamble-cash-row">
                            <span class="gamble-cash-label">Available:</span>
                            <span id="gamble-available-value" class="gamble-available-value">--</span>
                        </div>
                        <div class="gamble-presets-label">Withdraw</div>
                        <div class="gamble-presets-btns">
                            ${(gambleConfig.withdrawPresets || DEFAULT_GAMBLE_CONFIG.withdrawPresets).map(
        (p, i) => `<button class="gamble-preset-btn gamble-withdraw" data-amount="${p.amount}" data-label="${p.label}">${p.label}</button>`
      ).join("")}
                        </div>
                    </div>
                </div>
                <div class="gamble-panic-row">
                    <button id="gamble-panic-btn" class="gamble-panic-btn">\u{1F6A8} Panic: Deposit All</button>
                </div>
                <div id="gamble-status" class="gamble-status"></div>
            </div>
        </div>
        ` : ""}
        
        ${!minimalMode && mod.swing ? `
        <div class="alfa-swing-section" id="swing-section">
            <div class="swing-header" id="swing-toggle">
                <span class="swing-title">\u{1F4C8} Swing Trading</span>
                <span class="alfa-caret ${localStorage.getItem("alfa_swing_expanded") !== "false" ? "rotated" : ""}">\u25BC</span>
            </div>
            <div class="swing-content" id="swing-content" style="display:${localStorage.getItem("alfa_swing_expanded") !== "false" ? "block" : "none"};">
                <div class="swing-tabs">
                    <button class="swing-tab active" data-tab="positions">Positions</button>
                    <button class="swing-tab" data-tab="signals">Signals</button>
                    <button class="swing-tab" data-tab="journal">Journal</button>
                </div>
                
                <div class="swing-tab-content" id="swing-tab-positions">
                    <div class="swing-positions-list" id="swing-positions-list">
                        ${renderSwingPositions()}
                    </div>
                    <div class="swing-actions">
                        <button id="swing-add-position" class="alfa-mini-btn" style="border-color:#66bb6a; color:#66bb6a;">+ Add Position</button>
                        <button id="swing-refresh-signals" class="alfa-mini-btn" style="border-color:#ffd54f; color:#ffd54f;" title="Update prices and signals for your positions">Refresh signals</button>
                    </div>
                </div>
                
                <div class="swing-tab-content" id="swing-tab-signals" style="display:none;">
                    <div class="swing-signals-list" id="swing-signals-list">
                        <div class="swing-loading">Click "Scan" to find opportunities...</div>
                    </div>
                    <div class="swing-actions">
                        <button id="swing-scan-signals" class="alfa-mini-btn" style="border-color:#ffd54f; color:#ffd54f;">\u{1F50D} Scan All Stocks</button>
                    </div>
                </div>
                
                <div class="swing-tab-content" id="swing-tab-journal" style="display:none;">
                    <div class="swing-stats" id="swing-stats">
                        ${renderSwingStats()}
                    </div>
                    <div class="swing-journal-list" id="swing-journal-list">
                        ${renderSwingJournal()}
                    </div>
                </div>
            </div>
        </div>
        ` : ""}
        
        ${!minimalMode && mod.stocks ? `
        <div class="alfa-stocks-section" id="stocks-section">
            <div class="stocks-header" id="stocks-toggle">
                <span class="stocks-title">\u{1F4CB} Vault Stocks</span>
                <span class="alfa-caret ${localStorage.getItem("alfa_stocks_expanded") !== "false" ? "rotated" : ""}" id="stocks-caret">\u25BC</span>
            </div>
            <div class="stocks-content" id="stocks-content" style="display:${localStorage.getItem("alfa_stocks_expanded") !== "false" ? "block" : "none"};">
                <div class="alfa-vault-breakdown" id="vault-breakdown">
                    ${stockBars}
                </div>
            </div>
        </div>
        ` : ""}
        
        ` : ""}
        
        <div class="alfa-vault-controls">
            <div class="alfa-vault-control-group">
                <button id="vault-spread-btn" class="alfa-main-btn vault-spread-btn">Vault Spread</button>
                <input type="text" id="vault-spread-keep" class="alfa-input" style="width:80px;" placeholder="Keep" 
                    value="${vaultConfig.keepAmount > 0 ? vaultConfig.keepAmount : ""}">
            </div>
            <div class="alfa-vault-control-group">
                <button id="vault-rebalance-btn" class="alfa-main-btn vault-rebalance-btn" title="Rebalance portfolio based on current strategy">Rebalance</button>
                ${!minimalMode && mod.stocks ? `<button id="vault-analyze-btn" class="alfa-main-btn vault-analyze-btn" title="Refresh analysis and update signals on vault stocks">Analyze</button>` : ""}
            </div>
            <div class="alfa-vault-control-group">
                <input type="text" id="vault-withdraw-amt" class="alfa-input" style="width:100px;" placeholder="Amount">
                <button id="vault-withdraw-btn" class="alfa-main-btn vault-withdraw-btn">Withdraw</button>
            </div>
        </div>
        
        <div class="alfa-vault-options">
            <label class="vault-option-label" title="Protect benefit tier shares from being sold">
                <input type="checkbox" id="alfa-lock-toggle" ${lockBlocksChecked ? "checked" : ""}>
                <span>Lock Blocks</span>
            </label>
            <label class="vault-option-label" title="Skip stocks with SELL or TAKE PROFIT signals">
                <input type="checkbox" id="vault-skip-overvalued" ${vaultConfig.skipOvervalued ? "checked" : ""}>
                <span>Smart Mode</span>
            </label>
        </div>
        
        <div id="vault-status" class="alfa-vault-status"></div>
        </div>
        
        <div id="vault-trade-view" class="vault-trade-view" style="display:none;"></div>
    </div>`;
      mountVaultHtml(html);
      $("#vault-spread-btn").on("click", vaultSpread);
      $("#vault-rebalance-btn").on("click", vaultRebalance);
      $("#vault-withdraw-btn").on("click", () => smartWithdraw(parseTornNumber($("#vault-withdraw-amt").val())));
      $("#vault-analyze-btn").on("click", vaultAnalyze);
      $("#vault-pl-toggle").on("click", function() {
        $("#vault-pl-content").slideToggle(150, function() {
          localStorage.setItem("alfa_pl_expanded", $("#vault-pl-content").is(":visible") ? "true" : "false");
        });
        $(this).find(".alfa-caret").toggleClass("rotated");
      });
      $(".pl-period-btn").on("click", function() {
        const period = $(this).data("period");
        localStorage.setItem("alfa_pl_period", period);
        $(".pl-period-btn").removeClass("active");
        $(this).addClass("active");
        const periodPL2 = calculatePeriodPL(period);
        $(".pl-period-title").text(getPeriodLabel(period) + " Activity");
        $(".pl-period-stats").html(`
            <div class="pl-period-stat">
                <span class="pl-stat-label">Buys</span>
                <span class="pl-stat-value">${periodPL2.buys.length} (${formatMoneyWhole(periodPL2.totalBuyValue)})</span>
            </div>
            <div class="pl-period-stat">
                <span class="pl-stat-label">Sells</span>
                <span class="pl-stat-value">${periodPL2.sells.length} (${formatMoneyWhole(periodPL2.totalSellValue)})</span>
            </div>
            <div class="pl-period-stat ${periodPL2.realizedPL >= 0 ? "pl-positive" : "pl-negative"}">
                <span class="pl-stat-label">Realized P&L</span>
                <span class="pl-stat-value">${periodPL2.realizedPL >= 0 ? "+" : ""}${formatMoneyWhole(periodPL2.realizedPL)}</span>
            </div>
            <div class="pl-period-stat">
                <span class="pl-stat-label">Net Flow</span>
                <span class="pl-stat-value" style="color:${periodPL2.netInvested > 0 ? "#66bb6a" : periodPL2.netInvested < 0 ? "#ef5350" : "#888"};">${periodPL2.netInvested > 0 ? "+" : ""}${formatMoneyWhole(periodPL2.netInvested)}</span>
            </div>
        `);
      });
      $("#vault-pl-sync, #vault-pl-initial-sync").on("click", async function() {
        const btn = $(this);
        const status = $("#vault-status");
        btn.prop("disabled", true).text("Syncing...");
        const syncResult = await incrementalPortfolioSync((msg) => {
          status.html(`<span style="color:#ffd54f;">${msg}</span>`);
        });
        updateVaultDisplay();
        const statusAfter = $("#vault-status");
        if (syncResult && !syncResult.success) {
          statusAfter.html(`<span style="color:#ef5350;">${syncResult.error || "Sync failed"}</span>`);
        }
        setTimeout(() => statusAfter.html(""), 4e3);
      });
      $("#vault-pl-full-sync").on("click", async function() {
        if (!confirm("Full sync will fetch ALL your stock transaction history. This may take a while. Continue?")) {
          return;
        }
        const btn = $(this);
        const status = $("#vault-status");
        btn.prop("disabled", true).text("Syncing...");
        const syncResult = await fullPortfolioSync((msg) => {
          status.html(`<span style="color:#ffd54f;">${msg}</span>`);
        });
        updateVaultDisplay();
        const statusAfter = $("#vault-status");
        if (!syncResult.success) {
          statusAfter.html(`<span style="color:#ef5350;">${syncResult.error || "Sync failed"}</span>`);
        }
        setTimeout(() => statusAfter.html(""), 4e3);
      });
      $("#vault-pl-history").on("click", openTransactionHistoryModal);
      $("#gamble-toggle").on("click", function() {
        $("#gamble-content").slideToggle(150, function() {
          localStorage.setItem("alfa_gamble_expanded", $("#gamble-content").is(":visible") ? "true" : "false");
          if ($("#gamble-content").is(":visible")) updateGambleCashDisplay();
        });
        $(this).find(".alfa-caret").toggleClass("rotated");
      });
      $("#gamble-target-stock").on("change", function() {
        gambleConfig.targetStock = $(this).val();
        saveGambleConfig();
        updateGambleCashDisplay();
      });
      $("#gamble-config-btn").on("click", openGambleConfigModal);
      $(document).off("click.gambleDeposit", ".gamble-preset-btn.gamble-deposit").on("click.gambleDeposit", ".gamble-preset-btn.gamble-deposit", async function() {
        const amount = parseInt($(this).data("amount"), 10);
        const statusEl = $("#gamble-status");
        await executeGambleDeposit(amount, $(this), statusEl);
      });
      $(document).off("click.gambleWithdraw", ".gamble-preset-btn.gamble-withdraw").on("click.gambleWithdraw", ".gamble-preset-btn.gamble-withdraw", async function() {
        const amount = parseInt($(this).data("amount"), 10);
        const statusEl = $("#gamble-status");
        await executeGambleWithdraw(amount, $(this), statusEl);
      });
      $("#gamble-panic-btn").on("click", async function() {
        const statusEl = $("#gamble-status");
        await executeGamblePanic($(this), statusEl);
      });
      if (localStorage.getItem("alfa_gamble_expanded") !== "false") {
        updateGambleCashDisplay();
      }
      $("#swing-toggle").on("click", function() {
        $("#swing-content").slideToggle(150, function() {
          localStorage.setItem("alfa_swing_expanded", $("#swing-content").is(":visible") ? "true" : "false");
        });
        $(this).find(".alfa-caret").toggleClass("rotated");
      });
      $("#stocks-toggle").on("click", function() {
        $("#stocks-content").slideToggle(150, function() {
          localStorage.setItem("alfa_stocks_expanded", $("#stocks-content").is(":visible") ? "true" : "false");
        });
        $(this).find(".alfa-caret").toggleClass("rotated");
      });
      $(".swing-tab").on("click", function() {
        const tab = $(this).data("tab");
        $(".swing-tab").removeClass("active");
        $(this).addClass("active");
        $(".swing-tab-content").hide();
        $(`#swing-tab-${tab}`).show();
        if (tab === "journal") bindSwingJournalActions();
      });
      $("#swing-add-position").on("click", openAddPositionModal);
      $("#swing-refresh-signals").on("click", async function() {
        const btn = $(this);
        const positions = swingTradeData.activePositions;
        if (positions.length === 0) return;
        btn.prop("disabled", true).text("Updating...");
        try {
          updateAllPositionPrices();
          await refreshPositionSignals();
          $("#swing-positions-list").html(renderSwingPositions());
          bindSwingPositionActions();
        } catch (e) {
          console.error("Refresh signals error:", e);
        }
        btn.prop("disabled", false).text("Refresh signals");
      });
      $("#swing-scan-signals").on("click", async function() {
        const btn = $(this);
        btn.prop("disabled", true).text("Scanning...");
        try {
          const { all } = await getTopSwingSignals(10);
          $("#swing-signals-list").html(renderSwingSignals(all));
          $("#swing-positions-list").html(renderSwingPositions());
          bindSwingSignalActions();
          bindSwingPositionActions();
        } catch (e) {
          console.error("Signal scan error:", e);
          $("#swing-signals-list").html(`<div class="swing-error">Error scanning: ${e.message}</div>`);
        }
        btn.prop("disabled", false).text("\u{1F50D} Scan All Stocks");
      });
      bindSwingPositionActions();
      $("#vault-spread-keep").on("change", function() {
        vaultConfig.keepAmount = parseTornNumber($(this).val()) || 0;
        saveVaultConfig();
      });
      $("#alfa-lock-toggle").on("change", function() {
        localStorage.setItem("alfa_vault_lock", $(this).is(":checked"));
        updateVaultDisplay();
      });
      $("#vault-skip-overvalued").on("change", function() {
        vaultConfig.skipOvervalued = $(this).is(":checked");
        saveVaultConfig();
      });
      $(".vault-quick-buy").on("click", function(e) {
        e.stopPropagation();
        const sym = $(this).data("sym");
        vaultQuickBuy(sym);
      });
      $(".vault-quick-sell").on("click", function(e) {
        e.stopPropagation();
        const sym = $(this).data("sym");
        vaultQuickSell(sym);
      });
      $(".vault-toggle-lock").on("click", function(e) {
        e.stopPropagation();
        const sym = $(this).data("sym");
        toggleStockLock(sym);
      });
      $(".vault-block-icon").on("click", function(e) {
        e.stopPropagation();
        const sym = $(this).data("sym");
        openQuickBlockModal(sym);
      });
      loadDailyIncomeDisplay();
    }
    function openQuickBlockModal(sym) {
      const blockStatus = getBlockStatus(sym);
      if (!blockStatus) {
        alert("Unable to get block status for " + sym);
        return;
      }
      const stockData = STOCK_DATA[sym];
      const tierType = stockData.type === "P" ? "Passive" : "Active";
      const currentTierDisplay = blockStatus.currentTier > 0 ? blockStatus.isPassive ? "Passive Block" : `Tier ${blockStatus.currentTier}` : "No Block";
      const nextTierDisplay = blockStatus.isPassive ? "Passive Block" : `Tier ${blockStatus.currentTier + 1}`;
      const progressWidth = Math.min(100, blockStatus.progress);
      const progressClass = blockStatus.isClose ? "close" : "";
      const breakdown = calculateVaultBreakdown();
      let totalLooseValue = 0;
      const looseStocks = [];
      for (const stock of breakdown) {
        if (stock.symbol !== sym && stock.looseShares > 0) {
          totalLooseValue += stock.looseValue;
          looseStocks.push({
            symbol: stock.symbol,
            shares: stock.looseShares,
            value: stock.looseValue,
            price: stock.price
          });
        }
      }
      const currentCash = getMoneyFast();
      const combinedFunds = currentCash + totalLooseValue;
      let affordStatus = "";
      let buyBtnDisabled = "";
      let rebalanceBtnDisabled = "";
      let canRebalance = false;
      if (blockStatus.isMaxed) {
        affordStatus = '<span style="color:#8bc34a;">\u2713 Maximum tier reached</span>';
        buyBtnDisabled = "disabled";
        rebalanceBtnDisabled = "disabled";
      } else if (blockStatus.canAfford) {
        affordStatus = '<span style="color:#8bc34a;">\u2713 You can afford this!</span>';
        rebalanceBtnDisabled = "disabled";
      } else {
        const missing = blockStatus.costToComplete - currentCash;
        if (combinedFunds >= blockStatus.costToComplete) {
          canRebalance = true;
          affordStatus = `<span style="color:#ffd54f;">Need ${formatMoney(missing)} more - Rebalance available!</span>`;
          buyBtnDisabled = "disabled";
        } else {
          affordStatus = `<span style="color:#ef5350;">Missing ${formatMoney(missing)} (${formatMoney(blockStatus.costToComplete - combinedFunds)} after rebalance)</span>`;
          buyBtnDisabled = "disabled";
          rebalanceBtnDisabled = "disabled";
        }
      }
      let rebalanceInfo = "";
      if (!blockStatus.isMaxed && totalLooseValue > 0) {
        rebalanceInfo = `
        <div class="quick-block-rebalance-info">
            <div class="qb-rebalance-header">
                <span>\u{1F504} Rebalance Option</span>
                <span class="qb-rebalance-total">${formatMoney(totalLooseValue)} available</span>
            </div>
            <div class="qb-rebalance-detail">
                Sell loose shares from ${looseStocks.length} stock${looseStocks.length > 1 ? "s" : ""} to fund this block
            </div>
            <div class="qb-rebalance-funds">
                <span>Cash: ${formatMoney(currentCash)}</span>
                <span>+</span>
                <span>Loose: ${formatMoney(totalLooseValue)}</span>
                <span>=</span>
                <span style="color:${combinedFunds >= blockStatus.costToComplete ? "#8bc34a" : "#ef5350"};">${formatMoney(combinedFunds)}</span>
            </div>
        </div>`;
      }
      const html = `
    <div class="quick-block-container">
        <div class="quick-block-header">
            <span class="quick-block-symbol">${sym}</span>
            <span class="quick-block-type">${tierType}</span>
        </div>
        
        <div class="quick-block-status">
            <div class="quick-block-current">
                <span class="quick-block-label">Current</span>
                <span class="quick-block-value">${currentTierDisplay}</span>
            </div>
            <span class="quick-block-arrow">\u2192</span>
            <div class="quick-block-next">
                <span class="quick-block-label">Next</span>
                <span class="quick-block-value ${blockStatus.isMaxed ? "maxed" : ""}">${blockStatus.isMaxed ? "Maxed" : nextTierDisplay}</span>
            </div>
        </div>
        
        ${!blockStatus.isMaxed ? `
        <div class="quick-block-progress-section">
            <div class="quick-block-progress-header">
                <span>Progress to ${nextTierDisplay}</span>
                <span>${blockStatus.progress.toFixed(1)}%</span>
            </div>
            <div class="quick-block-progress-bar">
                <div class="quick-block-progress-fill ${progressClass}" style="width:${progressWidth}%;"></div>
            </div>
            <div class="quick-block-shares">
                ${blockStatus.sharesOwned.toLocaleString()} / ${blockStatus.sharesForNextTier.toLocaleString()} shares
            </div>
        </div>
        
        <div class="quick-block-details">
            <div class="quick-block-detail-row">
                <span>Shares Needed</span>
                <span>${blockStatus.sharesNeeded.toLocaleString()}</span>
            </div>
            <div class="quick-block-detail-row">
                <span>Current Price</span>
                <span>${formatMoney(blockStatus.price)}</span>
            </div>
            <div class="quick-block-detail-row highlight">
                <span>Cost to Complete</span>
                <span>${formatMoney(blockStatus.costToComplete)}</span>
            </div>
            <div class="quick-block-detail-row">
                <span>Tier ROI</span>
                <span style="color:#caa14a;">${blockStatus.roi.toFixed(2)}% APR</span>
            </div>
            ${blockStatus.dailyYield > 0 ? `
            <div class="quick-block-detail-row">
                <span>Daily Income</span>
                <span style="color:#8bc34a;">${formatMoney(Math.floor(blockStatus.dailyYield))}/day</span>
            </div>
            ` : ""}
        </div>
        
        ${rebalanceInfo}
        
        <div class="quick-block-afford">
            ${affordStatus}
        </div>
        ` : ""}
        
        <div class="quick-block-actions">
            ${!blockStatus.isMaxed ? `
            <button id="quick-block-buy" class="alfa-main-btn qb-btn-buy" ${buyBtnDisabled} data-sym="${sym}" data-shares="${blockStatus.sharesNeeded}">
                \u{1F4B5} Buy with Cash
            </button>
            <button id="quick-block-rebalance" class="alfa-main-btn qb-btn-rebalance" ${rebalanceBtnDisabled} data-sym="${sym}" data-shares="${blockStatus.sharesNeeded}" data-cost="${blockStatus.costToComplete}">
                \u{1F504} Rebalance
            </button>
            ` : ""}
            <button id="quick-block-close" class="alfa-main-btn qb-btn-close">Close</button>
        </div>
    </div>`;
      createModal(`Complete Block: ${sym}`, html);
      $("#quick-block-chart-btn").on("click", function() {
        const expandEl = document.getElementById("quick-block-chart-expand");
        const container = document.getElementById("quick-block-chart-container");
        const btn = this;
        if (!expandEl || !container) return;
        if (expandEl.style.display === "none") {
          expandEl.style.display = "block";
          btn.textContent = "Hide Chart";
          container.innerHTML = "";
          const interval = document.getElementById("quick-block-chart-interval")?.value || "d1";
          renderQuickBlockChart(sym, container, interval);
        } else {
          expandEl.style.display = "none";
          btn.textContent = "Chart";
        }
      });
      $("#quick-block-chart-interval").on("change", function() {
        const container = document.getElementById("quick-block-chart-container");
        const expandEl = document.getElementById("quick-block-chart-expand");
        if (container && expandEl && expandEl.style.display !== "none") {
          const interval = $(this).val();
          container.innerHTML = "";
          renderQuickBlockChart(sym, container, interval);
        }
      });
      $("#alfa-modal-close").off("click").on("click", function() {
        $("#alfa-modal-overlay").remove();
      });
      $("#alfa-modal-overlay").off("click").on("click", function(e) {
        if (e.target.id === "alfa-modal-overlay") {
          $("#alfa-modal-overlay").remove();
        }
      });
      $("#quick-block-buy").on("click", async function() {
        const sym2 = $(this).data("sym");
        const shares = parseInt($(this).data("shares"));
        const $btn = $(this);
        $btn.prop("disabled", true).text("Buying...");
        $("#quick-block-rebalance").prop("disabled", true);
        try {
          await postTradeAsync(sym2, shares, "buyShares");
          $btn.text("Success!").css("background", "#8bc34a").css("color", "#111");
          setTimeout(() => {
            $("#alfa-modal-overlay").remove();
            updateVaultDisplay();
          }, 1e3);
        } catch (e) {
          $btn.text("Error!").css("color", "#ef5350");
          console.error("Quick block buy error:", e);
        }
      });
      $("#quick-block-rebalance").on("click", function() {
        const targetSym = $(this).data("sym");
        const sharesNeeded = parseInt($(this).data("shares"));
        const totalCost = parseFloat($(this).data("cost"));
        $("#alfa-modal-overlay").remove();
        const currentCash2 = getMoneyFast();
        let amountNeeded = totalCost - currentCash2;
        if (amountNeeded <= 0) {
          alert("You already have enough cash! Use 'Buy with Cash' instead.");
          return;
        }
        const sellFeeMultiplier = 1.001;
        const safetyBuffer = 1.01;
        const adjustedAmountNeeded = amountNeeded * sellFeeMultiplier * safetyBuffer;
        const breakdown2 = calculateVaultBreakdown();
        const sellOrders = [];
        let remainingToSell = adjustedAmountNeeded;
        for (const stock of breakdown2) {
          if (remainingToSell <= 0) break;
          if (stock.symbol === targetSym || stock.looseShares <= 0) continue;
          const maxSellValue = Math.min(stock.looseValue, remainingToSell);
          const sharesToSell = Math.ceil(maxSellValue / stock.price);
          const actualSell = Math.min(sharesToSell, stock.looseShares);
          if (actualSell > 0) {
            const grossValue = actualSell * stock.price;
            sellOrders.push({
              symbol: stock.symbol,
              shares: actualSell,
              value: grossValue
            });
            remainingToSell -= grossValue;
          }
        }
        if (sellOrders.length === 0) {
          alert("No loose shares available to sell for rebalancing.");
          return;
        }
        const tradeStates = {
          sells: sellOrders.map(() => "pending"),
          buy: "pending"
        };
        let completedCount = 0;
        const totalTrades = sellOrders.length + 1;
        const tradeQueue = [];
        sellOrders.forEach((order, idx) => {
          tradeQueue.push({ type: "sell", symbol: order.symbol, shares: order.shares, index: idx });
        });
        tradeQueue.push({ type: "buy", symbol: targetSym, shares: sharesNeeded, index: -1 });
        let currentTradeIndex = 0;
        function updateProgress() {
          const sellsCompleted = tradeStates.sells.filter((s) => s === "completed").length;
          const buyCompleted = tradeStates.buy === "completed" ? 1 : 0;
          completedCount = sellsCompleted + buyCompleted;
          const progressText = `Progress: ${completedCount}/${totalTrades} trades completed`;
          $("#qb-rebal-progress").text(progressText);
        }
        function updateRowState(index, type, state) {
          if (type === "sell") {
            tradeStates.sells[index] = state;
            const $row = $(`.rebal-row[data-qb-sell-index="${index}"]`);
            if (state === "pending") {
              $row.removeClass("rebal-row-executing rebal-row-completed rebal-row-failed");
            } else if (state === "executing") {
              $row.addClass("rebal-row-executing");
            } else if (state === "completed") {
              $row.removeClass("rebal-row-executing").addClass("rebal-row-completed");
            } else if (state === "failed") {
              $row.removeClass("rebal-row-executing").addClass("rebal-row-failed");
            }
          } else if (type === "buy") {
            tradeStates.buy = state;
            const $row = $(".rebal-section:has(.rebal-section-header:contains('Buy Order')) .rebal-row");
            if (state === "pending") {
              $row.removeClass("rebal-row-executing rebal-row-completed rebal-row-failed");
            } else if (state === "executing") {
              $row.addClass("rebal-row-executing");
            } else if (state === "completed") {
              $row.removeClass("rebal-row-executing").addClass("rebal-row-completed");
            } else if (state === "failed") {
              $row.removeClass("rebal-row-executing").addClass("rebal-row-failed");
            }
          }
          updateProgress();
        }
        const sellsHtml = `
            <div class="rebal-section">
                <div class="rebal-section-header" style="color:#ef5350;">\u{1F4C9} Sell Orders (${sellOrders.length}) - Freeing up funds</div>
                <div class="rebal-table">
                    ${sellOrders.map((order, idx) => {
          return `<div class="rebal-row" data-qb-sell-index="${idx}">
                            <span class="rebal-sym">${order.symbol}</span>
                            <span class="rebal-signal" style="color:#ef5350;">SELL</span>
                            <span class="rebal-reason"></span>
                            <span class="rebal-shares">-${order.shares.toLocaleString()}</span>
                            <span class="rebal-value" style="color:#ef5350;">$${formatCompactNumber(order.value)}</span>
                        </div>`;
        }).join("")}
                </div>
            </div>`;
        const html2 = `
        <div class="rebal-container">
            <div class="rebal-summary">
                <div class="rebal-summary-item">
                    <span class="rebal-summary-label">Target</span>
                    <span class="rebal-summary-value">${targetSym}</span>
                </div>
                <div class="rebal-summary-item">
                    <span class="rebal-summary-label">Shares Needed</span>
                    <span class="rebal-summary-value">${sharesNeeded.toLocaleString()}</span>
                </div>
                <div class="rebal-summary-item">
                    <span class="rebal-summary-label">Total Cost</span>
                    <span class="rebal-summary-value" style="color:#caa14a;">$${formatCompactNumber(totalCost)}</span>
                </div>
            </div>
            ${sellsHtml}
            <div class="rebal-section">
                <div class="rebal-section-header" style="color:#66bb6a;">\u{1F4C8} Buy Order</div>
                <div class="rebal-table">
                    <div class="rebal-row">
                        <span class="rebal-sym">${targetSym}</span>
                        <span class="rebal-signal" style="color:#66bb6a;">BUY</span>
                        <span class="rebal-reason"></span>
                        <span class="rebal-shares">+${sharesNeeded.toLocaleString()}</span>
                        <span class="rebal-value" style="color:#66bb6a;">$${formatCompactNumber(totalCost)}</span>
                    </div>
                </div>
            </div>
            <div class="rebal-warning">
                \u26A0\uFE0F Click the button to execute trades sequentially. Market prices may vary slightly.
            </div>
            <div id="qb-rebal-progress" style="text-align:center; padding:10px; color:#ffd54f; font-weight:bold;">Progress: 0/${totalTrades} trades completed</div>
            <div class="rebal-actions">
                <button id="qb-rebal-cycle-btn" class="alfa-main-btn" style="margin-right:10px;">${tradeQueue.length > 0 ? (tradeQueue[0].type === "sell" ? "Selling" : "Buying") + " " + tradeQueue[0].symbol + " (1/" + tradeQueue.length + ")" : "No trades"}</button>
                <button id="qb-rebal-cancel" class="alfa-main-btn" style="border-color:#888; color:#888;">Close</button>
            </div>
        </div>`;
        createModal(`Rebalance to Buy ${targetSym}`, html2);
        $("#qb-rebal-cancel").on("click", function() {
          $("#alfa-modal-overlay").remove();
        });
        $("#qb-rebal-cycle-btn").on("click", async function() {
          if (currentTradeIndex >= tradeQueue.length) return;
          const trade = tradeQueue[currentTradeIndex];
          const $btn = $(this);
          $btn.prop("disabled", true).text("Executing...");
          updateRowState(trade.index, trade.type, "executing");
          try {
            await postTradeAsync(
              trade.symbol,
              trade.shares,
              trade.type === "sell" ? "sellShares" : "buyShares"
            );
            updateRowState(trade.index, trade.type, "completed");
            updateVaultDisplay();
            currentTradeIndex++;
            if (currentTradeIndex >= tradeQueue.length) {
              $btn.prop("disabled", true).text("Completed!").css("background", "#8bc34a").css("color", "#111");
              setTimeout(() => {
                $("#alfa-modal-overlay").remove();
              }, 1500);
            } else {
              const nextTrade = tradeQueue[currentTradeIndex];
              $btn.prop("disabled", false).text(`${nextTrade.type === "sell" ? "Selling" : "Buying"} ${nextTrade.symbol} (${currentTradeIndex + 1}/${tradeQueue.length})`);
            }
          } catch (e) {
            console.error(`Trade failed for ${trade.symbol}:`, e);
            updateRowState(trade.index, trade.type, "failed");
            $btn.prop("disabled", false).text(`${trade.type === "sell" ? "Selling" : "Buying"} ${trade.symbol} (${currentTradeIndex + 1}/${tradeQueue.length}) - Retry`);
          }
        });
      });
      $("#quick-block-close").on("click", function() {
        $("#alfa-modal-overlay").remove();
      });
    }
    async function ensureItemPrices(force = false) {
      const key = localStorage.getItem("alfa_vault_apikey");
      if (!key) return;
      const last = Number(localStorage.getItem("alfa_advisor_prices_ts") || 0);
      if (!force && Date.now() - last < 6 * 60 * 60 * 1e3 && Object.keys(itemPrices).length > 3) {
        return;
      }
      const idSet = /* @__PURE__ */ new Set();
      for (const b of Object.values(ADVISOR_DATA)) {
        if (b.type === "item" && b.id) idSet.add(b.id);
        if (b.type === "average" && Array.isArray(b.ids)) b.ids.forEach((id) => idSet.add(id));
      }
      if (idSet.size === 0) return;
      try {
        const res = await fetch(`https://api.torn.com/v2/torn/items?ids=${[...idSet].join(",")}&key=${key}`);
        const data = await res.json();
        const items = data.items || data.item || data;
        const list = Array.isArray(items) ? items : Object.values(items || {});
        let updated = 0;
        for (const item of list) {
          if (!item || item.id == null) continue;
          const value = item.averageprice || item.market_value || item.value || item.sell_price || 0;
          if (value > 0) {
            itemPrices[item.id] = value;
            updated++;
          }
        }
        if (updated > 0) {
          localStorage.setItem("alfa_advisor_prices", JSON.stringify(itemPrices));
          localStorage.setItem("alfa_advisor_prices_ts", String(Date.now()));
        }
      } catch (e) {
        console.error("Item price fetch error:", e);
      }
    }
    async function loadDailyIncomeDisplay() {
      if ($("#vault-daily-income").length === 0) return;
      const savedKey = localStorage.getItem("alfa_vault_apikey");
      if (!savedKey) {
        $("#vault-daily-income").text("--");
        $("#vault-bank-info").text("\u{1F3E6} Set API key in Settings");
        return;
      }
      try {
        await ensureItemPrices(false);
        const incomeData = await calculateDailyIncome();
        $("#vault-daily-income").text(formatMoney(Math.floor(incomeData.totalIncome)) + "/day");
        if (incomeData.bankPrincipal > 0) {
          $("#vault-bank-info").html(
            `\u{1F3E6} ${formatCompactNumber(incomeData.bankPrincipal)} invested @ ${incomeData.bankRate.toFixed(1)}%`
          );
        } else {
          $("#vault-bank-info").text("\u{1F3E6} No bank investment");
        }
      } catch (e) {
        console.error("Income calculation error:", e);
        $("#vault-daily-income").text("Error");
        $("#vault-bank-info").text("");
      }
    }
    let vaultDisplayTimer = null;
    function updateVaultDisplay(immediate = false) {
      const run = () => {
        vaultDisplayTimer = null;
        renderVaultSection();
      };
      if (immediate) {
        if (vaultDisplayTimer) {
          clearTimeout(vaultDisplayTimer);
          vaultDisplayTimer = null;
        }
        run();
        return;
      }
      if (vaultDisplayTimer) clearTimeout(vaultDisplayTimer);
      vaultDisplayTimer = setTimeout(run, 150);
    }
    async function vaultSpread() {
      const status = $("#vault-status");
      if (vaultConfig.stocks.length === 0) {
        status.html('<span style="color:#ef5350;">No vault stocks configured! Click Settings to set up your vault.</span>');
        setTimeout(() => status.html(""), 3e3);
        return;
      }
      let money = $("#alfa-use-api").is(":checked") ? await syncWallet(false) : getMoneyFast();
      if (money === 0 && !$("#alfa-use-api").is(":checked")) money = await syncWallet(false);
      if (money <= 0) {
        status.html('<span style="color:#ef5350;">No cash available!</span>');
        setTimeout(() => status.html(""), 3e3);
        return;
      }
      let keepAmount = parseTornNumber($("#vault-spread-keep").val()) || vaultConfig.keepAmount || 0;
      let availableCash = money - keepAmount;
      if (availableCash <= 0) {
        status.html('<span style="color:#ef5350;">Not enough cash after keeping amount!</span>');
        setTimeout(() => status.html(""), 3e3);
        return;
      }
      status.html('<span style="color:#ffd54f;">Calculating allocations...</span>');
      if (!isGmXhrAvailable()) {
        warnTornsyUnavailable();
      }
      const allocations = await calculateSpreadAllocations(availableCash, vaultConfig.depositStrategy);
      if (allocations.length === 0) {
        status.html('<span style="color:#ef5350;">Could not calculate allocations!</span>');
        setTimeout(() => status.html(""), 3e3);
        return;
      }
      status.html("");
      showSpreadModal(allocations, availableCash, keepAmount);
    }
    async function calculateSpreadAllocations(totalCash, strategy) {
      let stocks2 = vaultConfig.stocks;
      if (stocks2.length === 0) return [];
      if (vaultConfig.lockedStocks && vaultConfig.lockedStocks.length > 0) {
        stocks2 = stocks2.filter((sym) => !vaultConfig.lockedStocks.includes(sym));
      }
      if (vaultConfig.skipOvervalued || $("#vault-skip-overvalued").is(":checked")) {
        const overvaluedSignals = strategy === "swing_strategy" ? ["strong_sell", "sell", "take_profit"] : ["sell", "take_profit"];
        stocks2 = stocks2.filter((sym) => {
          const analysis = vaultAnalysisCache[sym];
          if (!analysis) return true;
          if (strategy === "swing_strategy") {
            const opportunity = analyzeSwingOpportunity(analysis);
            return opportunity ? !overvaluedSignals.includes(opportunity.signal) : true;
          }
          return !overvaluedSignals.includes(analysis.signal);
        });
      }
      if (stocks2.length === 0) {
        $("#vault-status").html('<span style="color:#ffb74d;">No eligible stocks! Check locks or run analysis.</span>');
        return [];
      }
      let allocations = [];
      switch (strategy) {
        case "swing_strategy":
          allocations = await swingStrategyAllocation(totalCash, stocks2);
          break;
        case "dip_weighted":
          allocations = await dipWeightedAllocation(totalCash, stocks2);
          break;
        case "rsi_weighted":
          allocations = await rsiWeightedAllocation(totalCash, stocks2);
          break;
        case "roi_priority":
          allocations = await roiPriorityAllocation(totalCash, stocks2);
          break;
        case "equal":
        default:
          allocations = await equalSplitAllocation(totalCash, stocks2);
          break;
      }
      allocations = adjustForBenefitTiers(allocations);
      return allocations.filter((a) => a.shares > 0);
    }
    async function swingStrategyAllocation(totalCash, stocks2) {
      const allocations = [];
      let weights = {};
      let totalWeight = 0;
      for (const sym of stocks2) {
        let analysis = vaultAnalysisCache[sym];
        if (!analysis) {
          analysis = await analyzeStockForVault(sym);
        }
        const opportunity = analysis ? analyzeSwingOpportunity(analysis) : null;
        let weight = 1;
        if (opportunity) {
          weight = Math.max(0.5, 1 + opportunity.score / 100);
        }
        weights[sym] = weight;
        totalWeight += weight;
      }
      for (const sym of stocks2) {
        const price = getPrice(sym);
        if (price <= 0 || totalWeight <= 0) continue;
        const allocation = weights[sym] / totalWeight * totalCash;
        const shares = Math.floor(allocation / price);
        if (shares > 0) {
          allocations.push({
            symbol: sym,
            shares,
            cost: shares * price,
            price,
            weight: weights[sym]
          });
        }
      }
      return redistributeRemainder(allocations, totalCash, weights, totalWeight);
    }
    async function equalSplitAllocation(totalCash, stocks2) {
      const allocations = [];
      let weights = {};
      let totalWeight = 0;
      for (const sym of stocks2) {
        let weight = 1;
        let analysis = vaultAnalysisCache[sym];
        if (!analysis) analysis = await analyzeStockForVault(sym);
        if (analysis) {
          const signal = analysis.signal;
          if (signal === "buy") weight *= 1.3;
          else if (signal === "good") weight *= 1.15;
          const volMult = analysis.volumeMultiplier ?? 1;
          const volMin = swingTradeData?.settings?.volumeMinMultiplier ?? 1;
          const volPartial = swingTradeData?.settings?.volumePartialMultiplier ?? 0.8;
          const volFactor = volMult >= volMin ? 1 : volMult >= volPartial ? 0.5 : 0;
          weight = weight * volFactor + (1 - volFactor);
        }
        weights[sym] = weight;
        totalWeight += weight;
      }
      for (const sym of stocks2) {
        const price = getPrice(sym);
        if (price <= 0 || totalWeight <= 0) continue;
        const allocation = weights[sym] / totalWeight * totalCash;
        const shares = Math.floor(allocation / price);
        if (shares > 0) {
          allocations.push({
            symbol: sym,
            shares,
            cost: shares * price,
            price,
            weight: weights[sym]
          });
        }
      }
      return redistributeRemainder(allocations, totalCash, weights, totalWeight);
    }
    function redistributeRemainder(allocations, totalCash, weights, totalWeight) {
      if (allocations.length === 0) return allocations;
      let totalSpent = allocations.reduce((sum, a) => sum + a.cost, 0);
      let remainder = totalCash - totalSpent;
      const sorted = [...allocations].sort((a, b) => (b.weight || 1) - (a.weight || 1));
      let iterations = 0;
      const maxIterations = allocations.length * 3;
      while (remainder > 0 && iterations < maxIterations) {
        let addedAny = false;
        for (const alloc of sorted) {
          if (alloc.price <= remainder) {
            alloc.shares += 1;
            alloc.cost = alloc.shares * alloc.price;
            remainder -= alloc.price;
            addedAny = true;
            if (remainder <= 0) break;
          }
        }
        if (!addedAny) break;
        iterations++;
      }
      return allocations;
    }
    async function dipWeightedAllocation(totalCash, stocks2) {
      const allocations = [];
      let weights = {};
      let totalWeight = 0;
      for (const sym of stocks2) {
        let analysis = vaultAnalysisCache[sym];
        if (!analysis) {
          analysis = await analyzeStockForVault(sym);
        }
        let weight = 1;
        if (analysis && analysis.dipFrom7d < 0) {
          weight = Math.abs(analysis.dipFrom7d) + 1;
        } else if (analysis && analysis.dipFrom7d > 3) {
          weight = 0.5;
        }
        if (analysis) {
          const volMult = analysis.volumeMultiplier ?? 1;
          const volMin = swingTradeData?.settings?.volumeMinMultiplier ?? 1;
          const volPartial = swingTradeData?.settings?.volumePartialMultiplier ?? 0.8;
          const volFactor = volMult >= volMin ? 1 : volMult >= volPartial ? 0.5 : 0;
          weight = weight * volFactor + (1 - volFactor);
        }
        if (analysis) {
          const signal = analysis.signal;
          if (signal === "buy") weight *= 1.3;
          else if (signal === "good") weight *= 1.15;
        }
        weights[sym] = weight;
        totalWeight += weight;
      }
      for (const sym of stocks2) {
        const price = getPrice(sym);
        if (price <= 0 || totalWeight <= 0) continue;
        const allocation = weights[sym] / totalWeight * totalCash;
        const shares = Math.floor(allocation / price);
        if (shares > 0) {
          allocations.push({
            symbol: sym,
            shares,
            cost: shares * price,
            price,
            weight: weights[sym]
          });
        }
      }
      return redistributeRemainder(allocations, totalCash, weights, totalWeight);
    }
    async function rsiWeightedAllocation(totalCash, stocks2) {
      const allocations = [];
      let weights = {};
      let totalWeight = 0;
      for (const sym of stocks2) {
        let analysis = vaultAnalysisCache[sym];
        if (!analysis) {
          analysis = await analyzeStockForVault(sym);
        }
        let weight = 1;
        if (analysis && analysis.rsi) {
          weight = Math.max(0.5, (100 - analysis.rsi) / 30);
          const adx = analysis.adx ?? 25;
          const adxMin = swingTradeData?.settings?.adxMinThreshold ?? 25;
          const adxPartial = swingTradeData?.settings?.adxPartialThreshold ?? 20;
          const rsiAdxFactor = adx >= adxMin ? 1 : adx >= adxPartial ? 0.5 : 0;
          weight = weight * rsiAdxFactor + (1 - rsiAdxFactor);
        }
        if (analysis) {
          const volMult = analysis.volumeMultiplier ?? 1;
          const volMin = swingTradeData?.settings?.volumeMinMultiplier ?? 1;
          const volPartial = swingTradeData?.settings?.volumePartialMultiplier ?? 0.8;
          const volFactor = volMult >= volMin ? 1 : volMult >= volPartial ? 0.5 : 0;
          weight = weight * volFactor + (1 - volFactor);
        }
        if (analysis) {
          const signal = analysis.signal;
          if (signal === "buy") weight *= 1.3;
          else if (signal === "good") weight *= 1.15;
        }
        weights[sym] = weight;
        totalWeight += weight;
      }
      for (const sym of stocks2) {
        const price = getPrice(sym);
        if (price <= 0 || totalWeight <= 0) continue;
        const allocation = weights[sym] / totalWeight * totalCash;
        const shares = Math.floor(allocation / price);
        if (shares > 0) {
          allocations.push({
            symbol: sym,
            shares,
            cost: shares * price,
            price,
            weight: weights[sym]
          });
        }
      }
      return redistributeRemainder(allocations, totalCash, weights, totalWeight);
    }
    async function roiPriorityAllocation(totalCash, stocks2) {
      const allocations = [];
      let weights = {};
      let totalWeight = 0;
      for (const sym of stocks2) {
        const price = getPrice(sym);
        const dailyYield = getDailyYield(sym);
        const stockData = STOCK_DATA[sym];
        if (!stockData || price <= 0) continue;
        const tierCost = stockData.base * price;
        const roi = tierCost > 0 ? dailyYield * 365 / tierCost * 100 : 0;
        let weight = Math.max(0.5, roi / 10);
        if (isGmXhrAvailable()) {
          let analysis = vaultAnalysisCache[sym];
          if (!analysis) analysis = await analyzeStockForVault(sym);
          if (analysis) {
            const volMult = analysis.volumeMultiplier ?? 1;
            const volMin = swingTradeData?.settings?.volumeMinMultiplier ?? 1;
            const volPartial = swingTradeData?.settings?.volumePartialMultiplier ?? 0.8;
            const volFactor = volMult >= volMin ? 1 : volMult >= volPartial ? 0.5 : 0;
            weight = weight * volFactor + (1 - volFactor);
            const signal = analysis.signal;
            if (signal === "buy") weight *= 1.3;
            else if (signal === "good") weight *= 1.15;
          }
        }
        weights[sym] = weight;
        totalWeight += weight;
      }
      for (const sym of stocks2) {
        const price = getPrice(sym);
        if (price <= 0 || totalWeight <= 0) continue;
        const allocation = weights[sym] / totalWeight * totalCash;
        const shares = Math.floor(allocation / price);
        if (shares > 0) {
          allocations.push({
            symbol: sym,
            shares,
            cost: shares * price,
            price,
            weight: weights[sym]
          });
        }
      }
      return redistributeRemainder(allocations, totalCash, weights, totalWeight);
    }
    function adjustForBenefitTiers(allocations) {
      return allocations.map((alloc) => {
        const stockData = STOCK_DATA[alloc.symbol];
        if (!stockData) return alloc;
        const currentOwned = getOwnedShares(alloc.symbol);
        const totalAfter = currentOwned + alloc.shares;
        if (stockData.type === "P") {
          const tierTarget = stockData.base;
          if (currentOwned < tierTarget && totalAfter >= tierTarget * 0.9 && totalAfter < tierTarget) {
            const sharesToTier = tierTarget - currentOwned;
            const costToTier = sharesToTier * alloc.price;
            if (costToTier <= alloc.cost * 1.2) {
              alloc.shares = sharesToTier;
              alloc.cost = costToTier;
            }
          }
        } else {
          const base = stockData.base;
          const currentTier = getBenefitTier(alloc.symbol, currentOwned);
          const nextTierShares = currentTier.next || base;
          if (totalAfter >= nextTierShares * 0.9 && totalAfter < nextTierShares) {
            const sharesToTier = nextTierShares - currentOwned;
            const costToTier = sharesToTier * alloc.price;
            if (costToTier <= alloc.cost * 1.2) {
              alloc.shares = sharesToTier;
              alloc.cost = costToTier;
            }
          }
        }
        return alloc;
      });
    }
    function showSpreadModal(allocations, availableCash, keepAmount) {
      const totalAllocated = allocations.reduce((sum, a) => sum + a.cost, 0);
      const strategyLabel = vaultConfig.depositStrategy.replace("_", " ").replace(/\b\w/g, (l) => l.toUpperCase());
      const tradeStates = allocations.map(() => "pending");
      let completedCount = 0;
      const tradeQueue = allocations.map((alloc, idx) => ({
        type: "buy",
        symbol: alloc.symbol,
        shares: alloc.shares,
        index: idx
      }));
      let currentTradeIndex = 0;
      function updateProgress() {
        completedCount = tradeStates.filter((s) => s === "completed").length;
        const progressText = `Progress: ${completedCount}/${allocations.length} trades completed`;
        $("#spread-progress").text(progressText);
      }
      function updateRowState(index, state) {
        const $row = $(`.rebal-row[data-spread-index="${index}"]`);
        tradeStates[index] = state;
        if (state === "pending") {
          $row.removeClass("rebal-row-executing rebal-row-completed rebal-row-failed");
        } else if (state === "executing") {
          $row.addClass("rebal-row-executing");
        } else if (state === "completed") {
          $row.removeClass("rebal-row-executing").addClass("rebal-row-completed");
        } else if (state === "failed") {
          $row.removeClass("rebal-row-executing").addClass("rebal-row-failed");
        }
        updateProgress();
      }
      const allocationsHtml = `
        <div class="rebal-section">
            <div class="rebal-section-header" style="color:#66bb6a;">\u{1F4C8} Buy Orders (${allocations.length})</div>
            <div class="rebal-table">
                ${allocations.map((alloc, idx) => {
        return `<div class="rebal-row" data-spread-index="${idx}">
                        <span class="rebal-sym">${alloc.symbol}</span>
                        <span class="rebal-signal" style="color:#66bb6a;">BUY</span>
                        <span class="rebal-reason"></span>
                        <span class="rebal-shares">+${alloc.shares.toLocaleString()}</span>
                        <span class="rebal-value" style="color:#66bb6a;">$${formatCompactNumber(alloc.cost)}</span>
                    </div>`;
      }).join("")}
            </div>
            <div class="rebal-subtotal" style="color:#66bb6a;">Total: $${formatCompactNumber(totalAllocated)}</div>
        </div>`;
      const html = `
    <div class="rebal-container">
        <div class="rebal-summary">
            <div class="rebal-summary-item">
                <span class="rebal-summary-label">Strategy</span>
                <span class="rebal-summary-value">${strategyLabel}</span>
            </div>
            <div class="rebal-summary-item">
                <span class="rebal-summary-label">Amount to Spread</span>
                <span class="rebal-summary-value" style="color:#66bb6a;">$${formatCompactNumber(availableCash)}</span>
            </div>
            <div class="rebal-summary-item">
                <span class="rebal-summary-label">Keeping</span>
                <span class="rebal-summary-value">$${formatCompactNumber(keepAmount)}</span>
            </div>
        </div>
        ${allocationsHtml}
        <div class="rebal-warning">
            \u26A0\uFE0F Click the button to execute trades sequentially. Market prices may vary slightly.
        </div>
        <div id="spread-progress" style="text-align:center; padding:10px; color:#66bb6a; font-weight:bold;">Progress: 0/${allocations.length} trades completed</div>
        <div class="rebal-actions">
            <button id="spread-cycle-btn" class="alfa-main-btn" style="margin-right:10px;">${tradeQueue.length > 0 ? `Buying ${tradeQueue[0].symbol} (1/${tradeQueue.length})` : "No trades"}</button>
            <button id="spread-cancel" class="alfa-main-btn" style="border-color:#888; color:#888;">Close</button>
        </div>
    </div>`;
      createModal("Vault Spread", html);
      $("#spread-cancel").on("click", function() {
        $("#alfa-modal-overlay").remove();
      });
      $("#spread-cycle-btn").on("click", async function() {
        if (currentTradeIndex >= tradeQueue.length) return;
        const trade = tradeQueue[currentTradeIndex];
        const $btn = $(this);
        $btn.prop("disabled", true).text("Executing...");
        updateRowState(trade.index, "executing");
        try {
          await postTradeAsync(trade.symbol, trade.shares, "buyShares");
          updateRowState(trade.index, "completed");
          updateVaultDisplay();
          currentTradeIndex++;
          if (currentTradeIndex >= tradeQueue.length) {
            $btn.prop("disabled", true).text("Completed!").css("background", "#8bc34a").css("color", "#111");
          } else {
            const nextTrade = tradeQueue[currentTradeIndex];
            $btn.prop("disabled", false).text(`Buying ${nextTrade.symbol} (${currentTradeIndex + 1}/${tradeQueue.length})`);
          }
        } catch (e) {
          console.error(`Buy failed for ${trade.symbol}:`, e);
          updateRowState(trade.index, "failed");
          $btn.prop("disabled", false).text(`Buying ${trade.symbol} (${currentTradeIndex + 1}/${tradeQueue.length}) - Retry`);
        }
      });
    }
    async function executeSpreadTrades(allocations) {
      const status = $("#vault-status");
      let successCount = 0;
      let failCount = 0;
      for (let i = 0; i < allocations.length; i++) {
        const alloc = allocations[i];
        status.html(`<span style="color:#ffd54f;">Buying ${alloc.symbol}... (${i + 1}/${allocations.length})</span>`);
        try {
          await new Promise((resolve, reject) => {
            $.post(
              `https://www.torn.com/page.php?sid=StockMarket&step=buyShares&rfcv=${getRFC()}`,
              { stockId: stockId[alloc.symbol], amount: alloc.shares }
            ).done(function(r) {
              try {
                if (typeof r === "string") r = JSON.parse(r);
                if (r.success) {
                  updateLocalCache(alloc.symbol, alloc.shares);
                  successCount++;
                  resolve();
                } else {
                  failCount++;
                  console.error(`Trade failed for ${alloc.symbol}:`, r.text);
                  resolve();
                }
              } catch (e) {
                successCount++;
                resolve();
              }
            }).fail(function() {
              failCount++;
              resolve();
            });
          });
          if (i < allocations.length - 1) {
            await new Promise((r) => setTimeout(r, 300));
          }
        } catch (e) {
          failCount++;
          console.error(`Error trading ${alloc.symbol}:`, e);
        }
      }
      updateVaultDisplay();
      if (failCount === 0) {
        status.html(`<span style="color:#8bc34a;">Vault spread complete! ${successCount} trades executed.</span>`);
      } else {
        status.html(`<span style="color:#ffb74d;">Completed with ${failCount} failed trades. ${successCount} succeeded.</span>`);
      }
      setTimeout(() => status.html(""), 5e3);
    }
    async function vaultAnalyze() {
      const status = $("#vault-status");
      if (vaultConfig.stocks.length === 0) {
        status.html('<span style="color:#ef5350;">No vault stocks configured!</span>');
        setTimeout(() => status.html(""), 3e3);
        return;
      }
      status.html('<span style="color:#ffd54f;">Analyzing stocks...</span>');
      await analyzeAllVaultStocks((msg) => status.html(`<span style="color:#ffd54f;">${msg}</span>`));
      renderVaultSection();
      $("#vault-status").html('<span style="color:#8bc34a;">Analysis complete! Signals updated.</span>');
      setTimeout(() => $("#vault-status").html(""), 3e3);
    }
    async function vaultRebalance() {
      const status = $("#vault-status");
      if (vaultConfig.stocks.length === 0) {
        alert("No vault stocks configured! Click Config to set up your vault.");
        return;
      }
      if (Object.keys(vaultAnalysisCache).length === 0) {
        if (isGmXhrAvailable()) {
          status.html('<span style="color:#ffd54f;">Running analysis first...</span>');
          await analyzeAllVaultStocks((msg) => status.html(`<span style="color:#ffd54f;">${msg}</span>`));
        } else {
          warnTornsyUnavailable();
        }
      }
      status.html('<span style="color:#ffd54f;">Calculating rebalance plan...</span>');
      const breakdown = calculateVaultBreakdown();
      const totalVaultValue = breakdown.reduce((sum, s) => sum + s.value, 0);
      if (totalVaultValue <= 0) {
        status.html('<span style="color:#ef5350;">No vault holdings to rebalance!</span>');
        return;
      }
      let eligibleStocks = vaultConfig.stocks.filter(
        (sym) => !vaultConfig.lockedStocks || !vaultConfig.lockedStocks.includes(sym)
      );
      if (eligibleStocks.length === 0) {
        status.html('<span style="color:#ef5350;">All stocks are locked!</span>');
        return;
      }
      const rebalancePlan = await calculateSmartRebalancePlan(breakdown, eligibleStocks, totalVaultValue);
      if (rebalancePlan.sells.length === 0 && rebalancePlan.buys.length === 0) {
        status.html('<span style="color:#8bc34a;">Portfolio is already balanced!</span>');
        setTimeout(() => status.html(""), 3e3);
        return;
      }
      showRebalanceModal(rebalancePlan, breakdown, null);
    }
    async function calculateSmartRebalancePlan(currentBreakdown, eligibleStocks, totalVaultValue) {
      const sells = [];
      const buys = [];
      const lockBlocks = $("#alfa-lock-toggle").is(":checked");
      const smartMode = vaultConfig.skipOvervalued || $("#vault-skip-overvalued").is(":checked");
      const overvaluedSignals = ["sell", "take_profit"];
      const buySignals = ["buy", "good", "hold"];
      const currentMap = {};
      for (const stock of currentBreakdown) {
        if (eligibleStocks.includes(stock.symbol)) {
          currentMap[stock.symbol] = stock;
        }
      }
      const idealAllocations = await calculateIdealRebalanceAllocations(totalVaultValue, eligibleStocks, smartMode);
      const idealMap = {};
      for (const alloc of idealAllocations) {
        idealMap[alloc.symbol] = alloc;
      }
      let totalSellValue = 0;
      let totalBuyValue = 0;
      for (const sym of eligibleStocks) {
        const current = currentMap[sym];
        const ideal = idealMap[sym];
        const currentValue = current ? current.value : 0;
        const currentShares = current ? current.owned : 0;
        const idealValue = ideal ? ideal.cost : 0;
        const idealShares = ideal ? ideal.shares : 0;
        const price = current ? current.price : ideal ? ideal.price : getPrice(sym);
        if (price <= 0) continue;
        const valueDiff = idealValue - currentValue;
        const analysis = vaultAnalysisCache[sym];
        const signal = analysis ? analysis.signal : "hold";
        const threshold = Math.max(Math.max(currentValue, idealValue) * 0.02, 5e3);
        if (Math.abs(valueDiff) < threshold) continue;
        if (valueDiff < 0) {
          const excessValue = Math.abs(valueDiff);
          let sharesToSell = Math.floor(excessValue / price);
          if (lockBlocks && current) {
            sharesToSell = Math.min(sharesToSell, current.looseShares);
          } else if (current) {
            sharesToSell = Math.min(sharesToSell, current.owned);
          }
          if (sharesToSell > 0) {
            const sellValue = sharesToSell * price;
            let reason = "Over target allocation";
            if (smartMode && overvaluedSignals.includes(signal)) {
              reason = `${signal.toUpperCase()} signal`;
            }
            sells.push({
              symbol: sym,
              shares: sharesToSell,
              value: sellValue,
              price,
              currentShares,
              idealShares,
              analysis,
              reason
            });
            totalSellValue += sellValue;
          }
        } else if (valueDiff > 0) {
          const shortfallValue = valueDiff;
          const sharesToBuy = Math.floor(shortfallValue / price);
          if (sharesToBuy > 0) {
            buys.push({
              symbol: sym,
              shares: sharesToBuy,
              value: sharesToBuy * price,
              price,
              currentShares,
              idealShares,
              analysis
            });
            totalBuyValue += sharesToBuy * price;
          }
        }
      }
      if (totalBuyValue > totalSellValue && totalSellValue > 0) {
        const scale = totalSellValue / totalBuyValue;
        totalBuyValue = 0;
        for (const buy of buys) {
          buy.shares = Math.floor(buy.shares * scale);
          buy.value = buy.shares * buy.price;
          totalBuyValue += buy.value;
        }
        const filteredBuys = buys.filter((b) => b.shares > 0);
        buys.length = 0;
        buys.push(...filteredBuys);
        let remainder = totalSellValue - totalBuyValue;
        if (remainder > 0 && buys.length > 0) {
          buys.sort((a, b) => {
            const signalPriority2 = { "buy": 1, "good": 2, "hold": 3, "take_profit": 4, "sell": 5 };
            const aPri = a.analysis ? signalPriority2[a.analysis.signal] || 3 : 3;
            const bPri = b.analysis ? signalPriority2[b.analysis.signal] || 3 : 3;
            return aPri - bPri;
          });
          for (const buy of buys) {
            if (remainder < buy.price) break;
            const extraShares = Math.floor(remainder / buy.price);
            if (extraShares > 0) {
              const addShares = Math.min(extraShares, Math.floor(remainder / buy.price));
              buy.shares += addShares;
              buy.value = buy.shares * buy.price;
              remainder -= addShares * buy.price;
              totalBuyValue += addShares * buy.price;
            }
          }
        }
      }
      const signalPriority = { "sell": 1, "take_profit": 2, "hold": 3, "good": 4, "buy": 5 };
      sells.sort((a, b) => {
        const aPri = a.analysis ? signalPriority[a.analysis.signal] || 3 : 3;
        const bPri = b.analysis ? signalPriority[b.analysis.signal] || 3 : 3;
        return aPri - bPri;
      });
      buys.sort((a, b) => {
        const aPri = a.analysis ? signalPriority[a.analysis.signal] || 3 : 3;
        const bPri = b.analysis ? signalPriority[b.analysis.signal] || 3 : 3;
        return bPri - aPri;
      });
      return {
        sells,
        buys,
        totalSellValue,
        totalBuyValue,
        netCashFlow: totalSellValue - totalBuyValue
      };
    }
    async function calculateIdealRebalanceAllocations(totalValue, stocks2, smartMode) {
      const strategy = vaultConfig.depositStrategy;
      const overvaluedSignals = ["sell", "take_profit"];
      let allocations = [];
      let weights = {};
      let totalWeight = 0;
      for (const sym of stocks2) {
        let analysis = vaultAnalysisCache[sym];
        if (!analysis) {
          analysis = await analyzeStockForVault(sym);
        }
        const signal = analysis ? analysis.signal : "hold";
        const opportunity = analysis ? analyzeSwingOpportunity(analysis) : null;
        const swingOvervalued = ["strong_sell", "sell", "take_profit"];
        const isOvervalued = strategy === "swing_strategy" && opportunity ? swingOvervalued.includes(opportunity.signal) : overvaluedSignals.includes(signal);
        if (smartMode && isOvervalued) {
          weights[sym] = 0;
          continue;
        }
        let weight = 1;
        switch (strategy) {
          case "swing_strategy":
            if (opportunity) {
              weight = Math.max(0.5, 1 + opportunity.score / 100);
            }
            break;
          case "rsi_weighted":
            if (analysis && analysis.rsi) {
              weight = Math.max(0.5, (100 - analysis.rsi) / 30);
              const adx = analysis.adx ?? 25;
              const adxMin = swingTradeData?.settings?.adxMinThreshold ?? 25;
              const adxPartial = swingTradeData?.settings?.adxPartialThreshold ?? 20;
              const rsiAdxFactor = adx >= adxMin ? 1 : adx >= adxPartial ? 0.5 : 0;
              weight = weight * rsiAdxFactor + (1 - rsiAdxFactor);
              const volMult = analysis.volumeMultiplier ?? 1;
              const volMin = swingTradeData?.settings?.volumeMinMultiplier ?? 1;
              const volPartial = swingTradeData?.settings?.volumePartialMultiplier ?? 0.8;
              const volFactor = volMult >= volMin ? 1 : volMult >= volPartial ? 0.5 : 0;
              weight = weight * volFactor + (1 - volFactor);
            }
            break;
          case "dip_weighted":
            if (analysis && analysis.dipFrom7d < 0) {
              weight = Math.abs(analysis.dipFrom7d) + 1;
            } else if (analysis && analysis.dipFrom7d > 3) {
              weight = 0.5;
            }
            if (analysis) {
              const volMult = analysis.volumeMultiplier ?? 1;
              const volMin = swingTradeData?.settings?.volumeMinMultiplier ?? 1;
              const volPartial = swingTradeData?.settings?.volumePartialMultiplier ?? 0.8;
              const volFactor = volMult >= volMin ? 1 : volMult >= volPartial ? 0.5 : 0;
              weight = weight * volFactor + (1 - volFactor);
            }
            break;
          case "roi_priority":
            const price = getPrice(sym);
            const dailyYield = getDailyYield(sym);
            const stockData = STOCK_DATA[sym];
            if (stockData && price > 0) {
              const tierCost = stockData.base * price;
              const roi = tierCost > 0 ? dailyYield * 365 / tierCost * 100 : 0;
              weight = Math.max(0.5, roi / 10);
            }
            if (analysis) {
              const volMult = analysis.volumeMultiplier ?? 1;
              const volMin = swingTradeData?.settings?.volumeMinMultiplier ?? 1;
              const volPartial = swingTradeData?.settings?.volumePartialMultiplier ?? 0.8;
              const volFactor = volMult >= volMin ? 1 : volMult >= volPartial ? 0.5 : 0;
              weight = weight * volFactor + (1 - volFactor);
            }
            break;
          case "equal":
          default:
            weight = 1;
            if (analysis) {
              const volMult = analysis.volumeMultiplier ?? 1;
              const volMin = swingTradeData?.settings?.volumeMinMultiplier ?? 1;
              const volPartial = swingTradeData?.settings?.volumePartialMultiplier ?? 0.8;
              const volFactor = volMult >= volMin ? 1 : volMult >= volPartial ? 0.5 : 0;
              weight = weight * volFactor + (1 - volFactor);
            }
            break;
        }
        if (analysis) {
          if (signal === "buy") weight *= 1.3;
          else if (signal === "good") weight *= 1.15;
        }
        weights[sym] = weight;
        totalWeight += weight;
      }
      for (const sym of stocks2) {
        const price = getPrice(sym);
        if (price <= 0 || totalWeight <= 0) continue;
        const weight = weights[sym] || 0;
        const allocation = weight / totalWeight * totalValue;
        const shares = Math.floor(allocation / price);
        allocations.push({
          symbol: sym,
          shares,
          cost: shares * price,
          price,
          weight
        });
      }
      return allocations;
    }
    function showRebalanceModal(plan, currentBreakdown, idealAllocations) {
      const signalColors = {
        "buy": "#66bb6a",
        "good": "#81c784",
        "hold": "#ffd54f",
        "take_profit": "#ffb74d",
        "sell": "#ef5350"
      };
      const tradeStates = {
        sells: plan.sells.map(() => "pending"),
        buys: plan.buys.map(() => "pending")
      };
      let completedCount = 0;
      const totalTrades = plan.sells.length + plan.buys.length;
      const tradeQueue = [];
      plan.sells.forEach((s, idx) => {
        tradeQueue.push({ type: "sell", symbol: s.symbol, shares: s.shares, index: idx });
      });
      plan.buys.forEach((b, idx) => {
        tradeQueue.push({ type: "buy", symbol: b.symbol, shares: b.shares, index: idx });
      });
      let currentTradeIndex = 0;
      function updateProgress() {
        completedCount = tradeStates.sells.filter((s) => s === "completed").length + tradeStates.buys.filter((b) => b === "completed").length;
        const progressText = totalTrades > 0 ? `Progress: ${completedCount}/${totalTrades} trades completed` : "";
        $("#rebal-progress").text(progressText);
      }
      function updateRowState(index, type, state) {
        const typeKey = type + "s";
        tradeStates[typeKey][index] = state;
        const $row = type === "sell" ? $(`.rebal-row[data-sell-index="${index}"]`) : $(`.rebal-row[data-buy-index="${index}"]`);
        if (state === "pending") {
          $row.removeClass("rebal-row-executing rebal-row-completed rebal-row-failed");
        } else if (state === "executing") {
          $row.addClass("rebal-row-executing");
        } else if (state === "completed") {
          $row.removeClass("rebal-row-executing").addClass("rebal-row-completed");
        } else if (state === "failed") {
          $row.removeClass("rebal-row-executing").addClass("rebal-row-failed");
        }
        updateProgress();
      }
      let sellsHtml = "";
      if (plan.sells.length > 0) {
        sellsHtml = `
        <div class="rebal-section">
            <div class="rebal-section-header" style="color:#ef5350;">\u{1F4C9} Sell Orders (${plan.sells.length}) - Freeing up funds</div>
            <div class="rebal-table">
                ${plan.sells.map((s, idx) => {
          const signal = s.analysis ? s.analysis.signal : "hold";
          const signalColor = signalColors[signal] || "#888";
          const reason = s.reason || "";
          const reasonTip = reason.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;");
          const reasonHtml = reason ? `<span class="rebal-reason rebal-reason-tip" title="${reasonTip}" aria-label="${reasonTip}">\u24D8</span>` : `<span class="rebal-reason"></span>`;
          return `<div class="rebal-row" data-sell-index="${idx}">
                        <span class="rebal-sym">${s.symbol}</span>
                        <span class="rebal-signal" style="color:${signalColor};">${signal.toUpperCase()}</span>
                        ${reasonHtml}
                        <span class="rebal-shares">-${s.shares.toLocaleString()}</span>
                        <span class="rebal-value" style="color:#ef5350;">${formatCompactNumber(s.value)}</span>
                    </div>`;
        }).join("")}
            </div>
            <div class="rebal-subtotal" style="color:#ef5350;">Funds to redistribute: $${formatCompactNumber(plan.totalSellValue)}</div>
        </div>`;
      } else {
        sellsHtml = `
        <div class="rebal-section">
            <div class="rebal-section-header" style="color:#888;">\u{1F4C9} No Sell Orders</div>
            <div style="padding:10px; color:#666; font-size:11px; text-align:center;">
                No overvalued or overweight positions found to sell.
                ${vaultConfig.skipOvervalued ? '<br>Try disabling "Smart" mode to include more stocks.' : ""}
            </div>
        </div>`;
      }
      let buysHtml = "";
      if (plan.buys.length > 0) {
        buysHtml = `
        <div class="rebal-section">
            <div class="rebal-section-header" style="color:#66bb6a;">\u{1F4C8} Buy Orders (${plan.buys.length}) - Redistributing to undervalued</div>
            <div class="rebal-table">
                ${plan.buys.map((b, idx) => {
          const signal = b.analysis ? b.analysis.signal : "hold";
          const signalColor = signalColors[signal] || "#888";
          return `<div class="rebal-row" data-buy-index="${idx}">
                        <span class="rebal-sym">${b.symbol}</span>
                        <span class="rebal-signal" style="color:${signalColor};">${signal.toUpperCase()}</span>
                        <span class="rebal-reason"></span>
                        <span class="rebal-shares">+${b.shares.toLocaleString()}</span>
                        <span class="rebal-value" style="color:#66bb6a;">$${formatCompactNumber(b.value)}</span>
                    </div>`;
        }).join("")}
            </div>
            <div class="rebal-subtotal" style="color:#66bb6a;">Total Buy: $${formatCompactNumber(plan.totalBuyValue)}</div>
        </div>`;
      } else if (plan.sells.length > 0) {
        buysHtml = `
        <div class="rebal-section">
            <div class="rebal-section-header" style="color:#888;">\u{1F4C8} No Buy Orders</div>
            <div style="padding:10px; color:#666; font-size:11px; text-align:center;">
                No eligible stocks to buy into. Freed funds will remain as cash.
            </div>
        </div>`;
      }
      const netFlow = plan.netCashFlow;
      const hasAction = plan.sells.length > 0 || plan.buys.length > 0;
      let summaryHtml = "";
      if (plan.sells.length > 0 && plan.buys.length > 0) {
        summaryHtml = `
        <div class="rebal-summary">
            <div class="rebal-summary-item">
                <span class="rebal-summary-label">Strategy</span>
                <span class="rebal-summary-value">${vaultConfig.depositStrategy.replace("_", " ")}</span>
            </div>
            <div class="rebal-summary-item">
                <span class="rebal-summary-label">Sell \u2192 Buy</span>
                <span class="rebal-summary-value" style="color:#caa14a;">$${formatCompactNumber(plan.totalSellValue)}</span>
            </div>
            <div class="rebal-summary-item">
                <span class="rebal-summary-label">Total Trades</span>
                <span class="rebal-summary-value">${totalTrades}</span>
            </div>
        </div>`;
      } else if (plan.sells.length > 0) {
        summaryHtml = `
        <div class="rebal-summary">
            <div class="rebal-summary-item">
                <span class="rebal-summary-label">Action</span>
                <span class="rebal-summary-value">Sell Only</span>
            </div>
            <div class="rebal-summary-item">
                <span class="rebal-summary-label">Cash Out</span>
                <span class="rebal-summary-value" style="color:#ef5350;">$${formatCompactNumber(plan.totalSellValue)}</span>
            </div>
        </div>`;
      }
      const html = `
    <div class="rebal-container">
        ${summaryHtml}
        ${sellsHtml}
        ${buysHtml}
        
        ${hasAction ? `<div class="rebal-warning">
            \u26A0\uFE0F Click the button to execute trades sequentially. Market prices may vary slightly.
        </div>` : ""}
        
        ${hasAction ? `<div id="rebal-progress" style="text-align:center; padding:10px; color:#caa14a; font-weight:bold;">Progress: 0/${totalTrades} trades completed</div>` : ""}
        
        <div class="rebal-actions">
            ${hasAction && tradeQueue.length > 0 ? `<button id="rebal-cycle-btn" class="alfa-main-btn" style="margin-right:10px;">${tradeQueue[0].type === "sell" ? "Selling" : "Buying"} ${tradeQueue[0].symbol} (1/${tradeQueue.length})</button>` : ""}
            <button id="rebal-cancel" class="alfa-main-btn" style="border-color:#888; color:#888;">Close</button>
        </div>
    </div>`;
      createModal("Rebalance Portfolio", html);
      $("#rebal-cancel").on("click", function() {
        $("#alfa-modal-overlay").remove();
      });
      if (hasAction && tradeQueue.length > 0) {
        $("#rebal-cycle-btn").on("click", async function() {
          if (currentTradeIndex >= tradeQueue.length) return;
          const trade = tradeQueue[currentTradeIndex];
          const $btn = $(this);
          $btn.prop("disabled", true).text("Executing...");
          updateRowState(trade.index, trade.type, "executing");
          try {
            await postTradeAsync(
              trade.symbol,
              trade.shares,
              trade.type === "sell" ? "sellShares" : "buyShares"
            );
            updateRowState(trade.index, trade.type, "completed");
            updateVaultDisplay();
            currentTradeIndex++;
            if (currentTradeIndex >= tradeQueue.length) {
              $btn.prop("disabled", true).text("Completed!").css("background", "#8bc34a").css("color", "#111");
            } else {
              const nextTrade = tradeQueue[currentTradeIndex];
              $btn.prop("disabled", false).text(`${nextTrade.type === "sell" ? "Selling" : "Buying"} ${nextTrade.symbol} (${currentTradeIndex + 1}/${tradeQueue.length})`);
            }
          } catch (e) {
            console.error(`Trade failed for ${trade.symbol}:`, e);
            updateRowState(trade.index, trade.type, "failed");
            $btn.prop("disabled", false).text(`${trade.type === "sell" ? "Selling" : "Buying"} ${trade.symbol} (${currentTradeIndex + 1}/${tradeQueue.length}) - Retry`);
          }
        });
      }
    }
    async function executeRebalance(plan) {
      const status = $("#vault-status");
      let successCount = 0;
      let failCount = 0;
      for (let i = 0; i < plan.sells.length; i++) {
        const sell = plan.sells[i];
        status.html(`<span style="color:#ffd54f;">Selling ${sell.symbol}... (${i + 1}/${plan.sells.length})</span>`);
        try {
          await new Promise((resolve) => {
            $.post(
              `https://www.torn.com/page.php?sid=StockMarket&step=sellShares&rfcv=${getRFC()}`,
              { stockId: stockId[sell.symbol], amount: sell.shares }
            ).done(function(r) {
              try {
                if (typeof r === "string") r = JSON.parse(r);
                if (r.success) {
                  updateLocalCache(sell.symbol, -sell.shares);
                  successCount++;
                } else {
                  failCount++;
                  console.error(`Sell failed for ${sell.symbol}:`, r.text);
                }
              } catch (e) {
                successCount++;
              }
              resolve();
            }).fail(function() {
              failCount++;
              resolve();
            });
          });
          await new Promise((r) => setTimeout(r, 300));
        } catch (e) {
          failCount++;
        }
      }
      if (plan.sells.length > 0 && plan.buys.length > 0) {
        status.html('<span style="color:#ffd54f;">Processing buys...</span>');
        await new Promise((r) => setTimeout(r, 500));
      }
      for (let i = 0; i < plan.buys.length; i++) {
        const buy = plan.buys[i];
        status.html(`<span style="color:#ffd54f;">Buying ${buy.symbol}... (${i + 1}/${plan.buys.length})</span>`);
        try {
          await new Promise((resolve) => {
            $.post(
              `https://www.torn.com/page.php?sid=StockMarket&step=buyShares&rfcv=${getRFC()}`,
              { stockId: stockId[buy.symbol], amount: buy.shares }
            ).done(function(r) {
              try {
                if (typeof r === "string") r = JSON.parse(r);
                if (r.success) {
                  updateLocalCache(buy.symbol, buy.shares);
                  successCount++;
                } else {
                  failCount++;
                  console.error(`Buy failed for ${buy.symbol}:`, r.text);
                }
              } catch (e) {
                successCount++;
              }
              resolve();
            }).fail(function() {
              failCount++;
              resolve();
            });
          });
          await new Promise((r) => setTimeout(r, 300));
        } catch (e) {
          failCount++;
        }
      }
      updateVaultDisplay();
      const totalTrades = plan.sells.length + plan.buys.length;
      if (failCount === 0) {
        status.html(`<span style="color:#8bc34a;">Rebalance complete! ${successCount}/${totalTrades} trades executed.</span>`);
      } else {
        status.html(`<span style="color:#ffb74d;">Rebalance done with ${failCount} failed trades. ${successCount}/${totalTrades} succeeded.</span>`);
      }
      setTimeout(() => status.html(""), 5e3);
    }
    async function smartWithdraw(targetAmount) {
      const status = $("#vault-status");
      if (!targetAmount || targetAmount <= 0) {
        status.html('<span style="color:#ef5350;">Enter a valid withdrawal amount!</span>');
        setTimeout(() => status.html(""), 3e3);
        return;
      }
      if (vaultConfig.stocks.length === 0) {
        status.html('<span style="color:#ef5350;">No vault stocks configured! Click Settings to set up your vault.</span>');
        setTimeout(() => status.html(""), 3e3);
        return;
      }
      if (vaultConfig.withdrawStrategy === "swing_strategy") {
        status.html('<span style="color:#ffd54f;">Analyzing stocks...</span>');
        await analyzeAllVaultStocks((msg) => status.html(`<span style="color:#ffd54f;">${msg}</span>`));
      }
      status.html('<span style="color:#ffd54f;">Calculating withdrawal plan...</span>');
      const plan = calculateWithdrawPlan(targetAmount, vaultConfig.withdrawStrategy);
      if (plan.length === 0) {
        status.html('<span style="color:#ef5350;">Cannot withdraw - no available shares!</span>');
        setTimeout(() => status.html(""), 3e3);
        return;
      }
      status.html("");
      showWithdrawModal(plan, targetAmount);
    }
    function calculateWithdrawPlan(targetAmount, strategy) {
      let stocks2 = vaultConfig.stocks;
      if (stocks2.length === 0) return [];
      if (vaultConfig.lockedStocks && vaultConfig.lockedStocks.length > 0) {
        stocks2 = stocks2.filter((sym) => !vaultConfig.lockedStocks.includes(sym));
      }
      if (stocks2.length === 0) {
        $("#vault-status").html('<span style="color:#ffb74d;">All stocks are locked!</span>');
        return [];
      }
      const stockInfo = [];
      for (const sym of stocks2) {
        const owned = getOwnedShares(sym);
        const price = getPrice(sym);
        if (owned <= 0 || price <= 0) continue;
        const stockData = STOCK_DATA[sym];
        const tierInfo = getBenefitTier(sym, owned);
        const swingProtectedShares = swingTradeData.activePositions.filter((p) => (p.symbol || "").toUpperCase() === (sym || "").toUpperCase()).reduce((sum, p) => sum + (p.shares || 0), 0);
        const checkbox = $("#alfa-lock-toggle");
        const lockBlocks = checkbox.length > 0 ? checkbox.is(":checked") : localStorage.getItem("alfa_vault_lock") === "true";
        let lockedShares = 0;
        if (lockBlocks && stockData) {
          if (stockData.type === "P") {
            lockedShares = owned >= stockData.base ? stockData.base : 0;
          } else {
            const currentTier = tierInfo.tier;
            if (currentTier > 0) {
              const minSharesForCurrentTier = stockData.base * (Math.pow(2, currentTier) - 1);
              lockedShares = Math.min(minSharesForCurrentTier, owned);
            }
          }
        }
        const totalProtected = lockedShares + swingProtectedShares;
        const looseShares = Math.max(0, owned - totalProtected);
        const value = owned * price;
        const looseValue = looseShares * price;
        const dailyYield = getDailyYield(sym);
        const roi = lockedShares > 0 && price > 0 ? dailyYield * 365 / (lockedShares * price) * 100 : 0;
        const analysis = vaultAnalysisCache[sym];
        const dipFrom7d = analysis ? analysis.dipFrom7d : 0;
        stockInfo.push({
          symbol: sym,
          owned,
          price,
          value,
          lockedShares: totalProtected,
          // Includes benefit tier + swing trade protection
          benefitLockedShares: lockedShares,
          swingProtectedShares,
          looseShares,
          looseValue,
          tier: tierInfo.tier,
          roi,
          dipFrom7d,
          hasSwingTrade: swingProtectedShares > 0,
          analysis
        });
      }
      let plan = [];
      switch (strategy) {
        case "proportional":
          plan = proportionalPlan(targetAmount, stockInfo);
          break;
        case "swing_strategy":
          plan = swingStrategyWithdrawPlan(targetAmount, stockInfo);
          break;
        case "worst_roi":
          plan = worstRoiFirstPlan(targetAmount, stockInfo);
          break;
        case "best_performers":
          plan = bestPerformersPlan(targetAmount, stockInfo);
          break;
        case "loose_first":
        default:
          plan = looseSharesFirstPlan(targetAmount, stockInfo);
          break;
      }
      return plan.filter((p) => p.shares > 0);
    }
    function looseSharesFirstPlan(targetAmount, stockInfo) {
      const plan = [];
      let remaining = targetAmount;
      const lockBlocks = $("#alfa-lock-toggle").is(":checked");
      for (const stock of stockInfo) {
        if (remaining <= 0) break;
        if (stock.looseShares <= 0) continue;
        const maxSellValue = Math.min(stock.looseValue, remaining);
        const sharesToSell = Math.min(stock.looseShares, Math.ceil(maxSellValue / stock.price));
        const sellValue = sharesToSell * stock.price;
        if (sharesToSell > 0) {
          plan.push({
            symbol: stock.symbol,
            shares: sharesToSell,
            price: stock.price,
            value: sellValue,
            type: "loose",
            swingProtected: stock.hasSwingTrade
          });
          remaining -= sellValue;
        }
      }
      if (remaining > 0 && !lockBlocks) {
        const stocksWithTiers = stockInfo.filter((s) => s.lockedShares > 0).sort((a, b) => a.roi - b.roi);
        for (const stock of stocksWithTiers) {
          if (remaining <= 0) break;
          const alreadyPlanned = plan.find((p) => p.symbol === stock.symbol);
          const alreadySelling = alreadyPlanned ? alreadyPlanned.shares : 0;
          const availableToSell = stock.owned - alreadySelling;
          if (availableToSell <= 0) continue;
          const maxSellValue = Math.min(availableToSell * stock.price, remaining);
          const sharesToSell = Math.ceil(maxSellValue / stock.price);
          const sellValue = sharesToSell * stock.price;
          if (sharesToSell > 0) {
            if (alreadyPlanned) {
              alreadyPlanned.shares += sharesToSell;
              alreadyPlanned.value += sellValue;
              alreadyPlanned.warning = "Will reduce benefit tier!";
            } else {
              plan.push({
                symbol: stock.symbol,
                shares: sharesToSell,
                price: stock.price,
                value: sellValue,
                type: "tier",
                warning: "Will reduce benefit tier!",
                swingProtected: stock.hasSwingTrade
              });
            }
            remaining -= sellValue;
          }
        }
      }
      return plan;
    }
    function swingStrategyWithdrawPlan(targetAmount, stockInfo) {
      const lockBlocks = $("#alfa-lock-toggle").is(":checked");
      const maxFractionPerStock = 0.5;
      const withScore = stockInfo.filter((s) => s.looseShares > 0).map((s) => {
        const opp = s.analysis ? analyzeSwingOpportunity(s.analysis) : null;
        const score = opp ? opp.score : 0;
        const weight = Math.max(1, 100 - score);
        return { ...s, score, weight };
      });
      const totalWeight = withScore.reduce((sum, s) => sum + s.weight, 0);
      const plan = [];
      const plannedBySym = {};
      let remaining = targetAmount;
      if (totalWeight > 0) {
        withScore.sort((a, b) => a.score - b.score);
        for (const stock of withScore) {
          const targetValue = stock.weight / totalWeight * targetAmount;
          const capValue = maxFractionPerStock * stock.looseValue;
          const maxSellValue = Math.min(stock.looseValue, capValue, targetValue, remaining);
          if (maxSellValue <= 0) continue;
          const sharesToSell = Math.min(stock.looseShares, Math.floor(maxSellValue / stock.price));
          if (sharesToSell <= 0) continue;
          const sellValue = sharesToSell * stock.price;
          plan.push({
            symbol: stock.symbol,
            shares: sharesToSell,
            price: stock.price,
            value: sellValue,
            type: "loose",
            swingProtected: stock.hasSwingTrade
          });
          plannedBySym[stock.symbol] = sharesToSell;
          remaining -= sellValue;
        }
        if (remaining > 0) {
          const sorted = [...withScore].sort((a, b) => a.score - b.score);
          for (const stock of sorted) {
            if (remaining <= 0) break;
            const alreadySelling = plannedBySym[stock.symbol] || 0;
            const looseAfter = stock.looseShares - alreadySelling;
            if (looseAfter <= 0) continue;
            const alreadyValue = alreadySelling * stock.price;
            const capValue = maxFractionPerStock * stock.looseValue;
            const roomValue = Math.max(0, capValue - alreadyValue);
            const takeValue = Math.min(roomValue, remaining, looseAfter * stock.price);
            if (takeValue <= 0) continue;
            const addShares = Math.min(looseAfter, Math.floor(takeValue / stock.price));
            if (addShares <= 0) continue;
            const addValue = addShares * stock.price;
            const entry = plan.find((p) => p.symbol === stock.symbol);
            if (entry) {
              entry.shares += addShares;
              entry.value += addValue;
            } else {
              plan.push({
                symbol: stock.symbol,
                shares: addShares,
                price: stock.price,
                value: addValue,
                type: "loose",
                swingProtected: stock.hasSwingTrade
              });
            }
            plannedBySym[stock.symbol] = (plannedBySym[stock.symbol] || 0) + addShares;
            remaining -= addValue;
          }
        }
      }
      if (remaining > 0 && !lockBlocks) {
        const withTierScore = stockInfo.filter((s) => s.lockedShares > 0).map((s) => {
          const opp = s.analysis ? analyzeSwingOpportunity(s.analysis) : null;
          const score = opp ? opp.score : 0;
          const weight = Math.max(1, 100 - score);
          return { ...s, score, weight };
        });
        const tierTotalWeight = withTierScore.reduce((sum, s) => sum + s.weight, 0);
        if (tierTotalWeight > 0) {
          withTierScore.sort((a, b) => a.score - b.score);
          for (const stock of withTierScore) {
            if (remaining <= 0) break;
            const alreadyPlanned = plan.find((p) => p.symbol === stock.symbol);
            const alreadySelling = alreadyPlanned ? alreadyPlanned.shares : 0;
            const availableToSell = stock.owned - alreadySelling;
            if (availableToSell <= 0) continue;
            const targetValue = stock.weight / tierTotalWeight * remaining;
            const tierCapValue = maxFractionPerStock * (stock.owned * stock.price);
            const alreadyValue = alreadySelling * stock.price;
            const roomValue = Math.max(0, tierCapValue - alreadyValue);
            const maxSellValue = Math.min(availableToSell * stock.price, remaining, roomValue, targetValue);
            if (maxSellValue <= 0) continue;
            const sharesToSell = Math.min(availableToSell, Math.floor(maxSellValue / stock.price));
            if (sharesToSell <= 0) continue;
            const sellValue = sharesToSell * stock.price;
            if (alreadyPlanned) {
              alreadyPlanned.shares += sharesToSell;
              alreadyPlanned.value += sellValue;
              alreadyPlanned.warning = "Will reduce benefit tier!";
            } else {
              plan.push({
                symbol: stock.symbol,
                shares: sharesToSell,
                price: stock.price,
                value: sellValue,
                type: "tier",
                warning: "Will reduce benefit tier!",
                swingProtected: stock.hasSwingTrade
              });
            }
            remaining -= sellValue;
          }
          if (remaining > 0) {
            withTierScore.sort((a, b) => a.score - b.score);
            for (const stock of withTierScore) {
              if (remaining <= 0) break;
              const alreadyPlanned = plan.find((p) => p.symbol === stock.symbol);
              const alreadySelling = alreadyPlanned ? alreadyPlanned.shares : 0;
              const availableToSell = stock.owned - alreadySelling;
              if (availableToSell <= 0) continue;
              const tierCapValue = maxFractionPerStock * (stock.owned * stock.price);
              const alreadyValue = alreadySelling * stock.price;
              const roomValue = Math.max(0, tierCapValue - alreadyValue);
              const takeValue = Math.min(roomValue, remaining, availableToSell * stock.price);
              if (takeValue <= 0) continue;
              const addShares = Math.min(availableToSell, Math.floor(takeValue / stock.price));
              if (addShares <= 0) continue;
              const addValue = addShares * stock.price;
              if (alreadyPlanned) {
                alreadyPlanned.shares += addShares;
                alreadyPlanned.value += addValue;
                alreadyPlanned.warning = "Will reduce benefit tier!";
              } else {
                plan.push({
                  symbol: stock.symbol,
                  shares: addShares,
                  price: stock.price,
                  value: addValue,
                  type: "tier",
                  warning: "Will reduce benefit tier!",
                  swingProtected: stock.hasSwingTrade
                });
              }
              remaining -= addValue;
            }
          }
        }
      }
      return plan;
    }
    function proportionalPlan(targetAmount, stockInfo) {
      const plan = [];
      const lockBlocks = $("#alfa-lock-toggle").is(":checked");
      let totalSellable = 0;
      for (const stock of stockInfo) {
        if (lockBlocks) {
          totalSellable += stock.looseValue;
        } else {
          totalSellable += stock.value;
        }
      }
      if (totalSellable <= 0) return plan;
      const sellPercentage = Math.min(1, targetAmount / totalSellable);
      for (const stock of stockInfo) {
        const maxValue = lockBlocks ? stock.looseValue : stock.value;
        const maxShares = lockBlocks ? stock.looseShares : stock.owned;
        if (maxShares <= 0) continue;
        const targetValue = maxValue * sellPercentage;
        const sharesToSell = Math.min(maxShares, Math.ceil(targetValue / stock.price));
        if (sharesToSell > 0) {
          const item = {
            symbol: stock.symbol,
            shares: sharesToSell,
            price: stock.price,
            value: sharesToSell * stock.price,
            type: sharesToSell > stock.looseShares ? "tier" : "loose",
            swingProtected: stock.hasSwingTrade
          };
          if (!lockBlocks && sharesToSell > stock.looseShares) {
            item.warning = "May reduce benefit tier!";
          }
          plan.push(item);
        }
      }
      return plan;
    }
    function worstRoiFirstPlan(targetAmount, stockInfo) {
      const plan = [];
      let remaining = targetAmount;
      const lockBlocks = $("#alfa-lock-toggle").is(":checked");
      const sortedStocks = [...stockInfo].sort((a, b) => a.roi - b.roi);
      for (const stock of sortedStocks) {
        if (remaining <= 0) break;
        const maxShares = lockBlocks ? stock.looseShares : stock.owned;
        if (maxShares <= 0) continue;
        const maxValue = maxShares * stock.price;
        const targetValue = Math.min(maxValue, remaining);
        const sharesToSell = Math.ceil(targetValue / stock.price);
        if (sharesToSell > 0) {
          const item = {
            symbol: stock.symbol,
            shares: sharesToSell,
            price: stock.price,
            value: sharesToSell * stock.price,
            type: sharesToSell > stock.looseShares ? "tier" : "loose",
            roi: stock.roi,
            swingProtected: stock.hasSwingTrade
          };
          if (!lockBlocks && sharesToSell > stock.looseShares) {
            item.warning = "May reduce benefit tier!";
          }
          plan.push(item);
          remaining -= item.value;
        }
      }
      return plan;
    }
    function bestPerformersPlan(targetAmount, stockInfo) {
      const plan = [];
      let remaining = targetAmount;
      const lockBlocks = $("#alfa-lock-toggle").is(":checked");
      const sortedStocks = [...stockInfo].sort((a, b) => b.dipFrom7d - a.dipFrom7d);
      for (const stock of sortedStocks) {
        if (remaining <= 0) break;
        const maxShares = lockBlocks ? stock.looseShares : stock.owned;
        if (maxShares <= 0) continue;
        const maxValue = maxShares * stock.price;
        const targetValue = Math.min(maxValue, remaining);
        const sharesToSell = Math.ceil(targetValue / stock.price);
        if (sharesToSell > 0) {
          const item = {
            symbol: stock.symbol,
            shares: sharesToSell,
            price: stock.price,
            value: sharesToSell * stock.price,
            type: sharesToSell > stock.looseShares ? "tier" : "loose",
            dipFrom7d: stock.dipFrom7d,
            swingProtected: stock.hasSwingTrade
          };
          if (!lockBlocks && sharesToSell > stock.looseShares) {
            item.warning = "May reduce benefit tier!";
          }
          plan.push(item);
          remaining -= item.value;
        }
      }
      return plan;
    }
    function showWithdrawModal(plan, targetAmount) {
      const validPlan = plan.filter((p) => p.shares > 0);
      const totalAvailable = validPlan.reduce((sum, p) => sum + p.value, 0);
      const expectedCash = totalAvailable * 0.999;
      const strategyLabel = vaultConfig.withdrawStrategy.replace("_", " ").replace(/\b\w/g, (l) => l.toUpperCase());
      const tradeStates = validPlan.map(() => "pending");
      let completedCount = 0;
      let cashReceived = 0;
      const tradeQueue = validPlan.map((item, idx) => ({
        type: "sell",
        symbol: item.symbol,
        shares: item.shares,
        value: item.value,
        index: idx
      }));
      let currentTradeIndex = 0;
      function updateProgress() {
        completedCount = tradeStates.filter((s) => s === "completed").length;
        const progressText = `Progress: ${completedCount}/${validPlan.length} trades completed`;
        $("#withdraw-progress").text(progressText);
        const netCash = cashReceived * 0.999;
        $("#withdraw-cash-received").text(`Cash Received: ~$${formatCompactNumber(netCash)}`);
      }
      function updateRowState(index, state, value = 0) {
        tradeStates[index] = state;
        if (state === "completed" && value > 0) {
          cashReceived += value;
        }
        const $row = $(`.rebal-row[data-withdraw-index="${index}"]`);
        if (state === "pending") {
          $row.removeClass("rebal-row-executing rebal-row-completed rebal-row-failed");
        } else if (state === "executing") {
          $row.addClass("rebal-row-executing");
        } else if (state === "completed") {
          $row.removeClass("rebal-row-executing").addClass("rebal-row-completed");
        } else if (state === "failed") {
          $row.removeClass("rebal-row-executing").addClass("rebal-row-failed");
        }
        updateProgress();
      }
      const swingProtectedStocks = swingTradeData.activePositions.map((p) => p.symbol);
      const protectedInPlan = swingProtectedStocks.filter(
        (sym) => vaultConfig.stocks.includes(sym) && validPlan.some((p) => p.symbol === sym && p.shares > 0)
      );
      let warningHtml = "";
      if (totalAvailable < targetAmount) {
        warningHtml = `<div class="rebal-warning" style="background:#ff980022; border-color:#ff980055; color:#ffb74d;">
            \u26A0\uFE0F Only $${formatCompactNumber(totalAvailable)} available to withdraw (requested $${formatCompactNumber(targetAmount)})
        </div>`;
      }
      if (protectedInPlan.length > 0) {
        warningHtml += `<div class="rebal-warning" style="background:#caa14a22; border-color:#caa14a55; color:#caa14a;">
            \u{1F6E1}\uFE0F Swing Trade Protected: ${protectedInPlan.join(", ")}
        </div>`;
      }
      const sellsHtml = `
        <div class="rebal-section">
            <div class="rebal-section-header" style="color:#ef5350;">\u{1F4C9} Sell Orders (${validPlan.length})</div>
            <div class="rebal-table">
                ${validPlan.map((item, idx) => {
        const tipParts = [];
        if (item.warning) tipParts.push(item.warning);
        if (item.swingProtected) tipParts.push("Swing trade protected");
        const tip = tipParts.join(" \xB7 ");
        const tipEsc = tip.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;");
        const reasonHtml = tip ? `<span class="rebal-reason rebal-reason-tip" title="${tipEsc}" aria-label="${tipEsc}">\u24D8</span>` : `<span class="rebal-reason"></span>`;
        return `<div class="rebal-row" data-withdraw-index="${idx}">
                        <span class="rebal-sym">${item.symbol}</span>
                        <span class="rebal-signal" style="color:#ef5350;">SELL</span>
                        ${reasonHtml}
                        <span class="rebal-shares">-${item.shares.toLocaleString()}</span>
                        <span class="rebal-value" style="color:#ef5350;">${formatCompactNumber(item.value)}</span>
                    </div>`;
      }).join("")}
            </div>
            <div class="rebal-subtotal" style="color:#ef5350;">Gross: $${formatCompactNumber(totalAvailable)}</div>
            <div class="rebal-subtotal" style="color:#8bc34a; margin-top:4px;">Net (after 0.1% fee): ~$${formatCompactNumber(expectedCash)}</div>
        </div>`;
      const html = `
    <div class="rebal-container">
        <div class="rebal-summary">
            <div class="rebal-summary-item">
                <span class="rebal-summary-label">Strategy</span>
                <span class="rebal-summary-value">${strategyLabel}</span>
            </div>
            <div class="rebal-summary-item">
                <span class="rebal-summary-label">Requested</span>
                <span class="rebal-summary-value">$${formatCompactNumber(targetAmount)}</span>
            </div>
            <div class="rebal-summary-item">
                <span class="rebal-summary-label">Expected Cash</span>
                <span class="rebal-summary-value" style="color:#8bc34a;">~$${formatCompactNumber(expectedCash)}</span>
            </div>
        </div>
        ${warningHtml}
        ${sellsHtml}
        <div class="rebal-warning">
            \u26A0\uFE0F Click the button to execute trades sequentially. Market prices may vary slightly.
        </div>
        <div id="withdraw-progress" style="text-align:center; padding:10px; color:#ef5350; font-weight:bold;">Progress: 0/${validPlan.length} trades completed</div>
        <div id="withdraw-cash-received" style="text-align:center; padding:5px; color:#8bc34a; font-weight:bold;">Cash Received: ~$0</div>
        <div class="rebal-actions">
            ${tradeQueue.length > 0 ? `<button id="withdraw-cycle-btn" class="alfa-main-btn" style="margin-right:10px;">Selling ${tradeQueue[0].symbol} (1/${tradeQueue.length})</button>` : ""}
            <button id="withdraw-cancel" class="alfa-main-btn" style="border-color:#888; color:#888;">Close</button>
        </div>
    </div>`;
      createModal("Smart Withdraw", html);
      $("#withdraw-cancel").on("click", function() {
        $("#alfa-modal-overlay").remove();
      });
      if (tradeQueue.length > 0) {
        $("#withdraw-cycle-btn").on("click", async function() {
          if (currentTradeIndex >= tradeQueue.length) return;
          const trade = tradeQueue[currentTradeIndex];
          const $btn = $(this);
          $btn.prop("disabled", true).text("Executing...");
          updateRowState(trade.index, "executing");
          try {
            await postTradeAsync(trade.symbol, trade.shares, "sellShares");
            updateRowState(trade.index, "completed", trade.value);
            updateVaultDisplay();
            currentTradeIndex++;
            if (currentTradeIndex >= tradeQueue.length) {
              $btn.prop("disabled", true).text("Completed!").css("background", "#8bc34a").css("color", "#111");
            } else {
              const nextTrade = tradeQueue[currentTradeIndex];
              $btn.prop("disabled", false).text(`Selling ${nextTrade.symbol} (${currentTradeIndex + 1}/${tradeQueue.length})`);
            }
          } catch (e) {
            console.error(`Sell failed for ${trade.symbol}:`, e);
            updateRowState(trade.index, "failed");
            $btn.prop("disabled", false).text(`Selling ${trade.symbol} (${currentTradeIndex + 1}/${tradeQueue.length}) - Retry`);
          }
        });
      }
    }
    async function executeWithdrawPlan(plan) {
      const status = $("#vault-status");
      let successCount = 0;
      let failCount = 0;
      let totalCash = 0;
      for (let i = 0; i < plan.length; i++) {
        const item = plan[i];
        status.html(`<span style="color:#ffd54f;">Selling ${item.symbol}... (${i + 1}/${plan.length})</span>`);
        try {
          await new Promise((resolve, reject) => {
            $.post(
              `https://www.torn.com/page.php?sid=StockMarket&step=sellShares&rfcv=${getRFC()}`,
              { stockId: stockId[item.symbol], amount: item.shares }
            ).done(function(r) {
              try {
                if (typeof r === "string") r = JSON.parse(r);
                if (r.success) {
                  updateLocalCache(item.symbol, -item.shares);
                  successCount++;
                  totalCash += item.value * 0.999;
                  resolve();
                } else {
                  failCount++;
                  console.error(`Sell failed for ${item.symbol}:`, r.text);
                  resolve();
                }
              } catch (e) {
                successCount++;
                totalCash += item.value * 0.999;
                resolve();
              }
            }).fail(function() {
              failCount++;
              resolve();
            });
          });
          if (i < plan.length - 1) {
            await new Promise((r) => setTimeout(r, 300));
          }
        } catch (e) {
          failCount++;
          console.error(`Error selling ${item.symbol}:`, e);
        }
      }
      updateVaultDisplay();
      if (failCount === 0) {
        status.html(`<span style="color:#8bc34a;">Withdrawal complete! ~${formatMoney(totalCash)} received.</span>`);
      } else {
        status.html(`<span style="color:#ffb74d;">Completed with ${failCount} failed trades. ~${formatMoney(totalCash)} received.</span>`);
      }
      setTimeout(() => status.html(""), 5e3);
    }
    function openQuickBuyModal(sym) {
      const price = getPriceForTrade(sym);
      if (price <= 0) {
        const status = $("#vault-status");
        status.html('<span style="color:#ef5350;">Price error!</span>');
        return;
      }
      const currentCash = getMoneyFast();
      const maxShares = Math.floor(currentCash / price);
      const html = `
    <div class="quick-trade-container">
        <div class="quick-trade-header">
            <span class="quick-trade-symbol">${sym}</span>
            <span class="quick-trade-price">${formatMoney(price)}</span>
        </div>
        
        <div class="quick-trade-info">
            <div class="quick-trade-info-row">
                <span>Available Cash</span>
                <span style="color:#8bc34a;">${formatMoney(currentCash)}</span>
            </div>
            <div class="quick-trade-info-row">
                <span>Max Shares</span>
                <span>${maxShares.toLocaleString()}</span>
            </div>
        </div>
        
        <div class="quick-trade-input-section">
            <label class="quick-trade-label">Amount to Buy</label>
            <input type="text" id="quick-buy-input" class="alfa-input" style="width:100%;" placeholder="e.g. 100k, 1m, or share count" autofocus>
            <div class="quick-trade-hint">Enter dollar amount (100k, 1m) or share count</div>
        </div>
        
        <div class="quick-trade-calc" id="quick-buy-calc" style="display:none;">
            <div class="quick-trade-calc-row">
                <span>Shares</span>
                <span id="quick-buy-shares" style="font-weight:bold; color:#fff;">0</span>
            </div>
            <div class="quick-trade-calc-row highlight">
                <span>Total Cost</span>
                <span id="quick-buy-cost" style="font-weight:bold; color:#8bc34a;">$0</span>
            </div>
            <div id="quick-buy-warning" class="quick-trade-warning" style="display:none;"></div>
        </div>
        
        <div class="quick-trade-actions">
            <button id="quick-buy-execute" class="alfa-main-btn qb-btn-buy" disabled>Buy</button>
            <button id="quick-buy-cancel" class="alfa-main-btn qb-btn-close">Cancel</button>
        </div>
    </div>`;
      createModal(`Buy ${sym}`, html);
      const $input = $("#quick-buy-input");
      const $calc = $("#quick-buy-calc");
      const $shares = $("#quick-buy-shares");
      const $cost = $("#quick-buy-cost");
      const $warning = $("#quick-buy-warning");
      const $execute = $("#quick-buy-execute");
      $input.on("input", function() {
        const input = $(this).val().trim();
        if (!input) {
          $calc.hide();
          $execute.prop("disabled", true);
          return;
        }
        let sharesToBuy = 0;
        const parsed = parseTornNumber(input);
        if (parsed >= 1e3 && !input.match(/^\d+$/)) {
          sharesToBuy = Math.floor(parsed / price);
        } else {
          sharesToBuy = Math.floor(parsed);
        }
        if (sharesToBuy <= 0) {
          $calc.hide();
          $execute.prop("disabled", true);
          return;
        }
        const totalCost = sharesToBuy * price;
        $shares.text(sharesToBuy.toLocaleString());
        $cost.text(formatMoney(totalCost));
        $calc.show();
        if (totalCost > currentCash) {
          const short = totalCost - currentCash;
          $warning.html(`<span style="color:#ef5350;">Insufficient funds! Need ${formatMoney(short)} more.</span>`).show();
          $execute.prop("disabled", true);
        } else {
          $warning.hide();
          $execute.prop("disabled", false);
        }
      });
      $execute.on("click", async function() {
        const input = $input.val().trim();
        if (!input) return;
        let sharesToBuy = 0;
        const parsed = parseTornNumber(input);
        if (parsed >= 1e3 && !input.match(/^\d+$/)) {
          sharesToBuy = Math.floor(parsed / price);
        } else {
          sharesToBuy = Math.floor(parsed);
        }
        if (sharesToBuy <= 0) return;
        const $btn = $(this);
        $btn.prop("disabled", true).text("Buying...");
        $("#quick-buy-cancel").prop("disabled", true);
        try {
          await postTradeAsync(sym, sharesToBuy, "buyShares");
          $btn.text("Success!").css("background", "#8bc34a").css("color", "#111");
          setTimeout(() => {
            $("#alfa-modal-overlay").remove();
            updateVaultDisplay();
          }, 1e3);
        } catch (e) {
          $btn.text("Error!").css("color", "#ef5350");
          console.error("Quick buy error:", e);
          setTimeout(() => {
            $btn.text("Buy").prop("disabled", false);
            $("#quick-buy-cancel").prop("disabled", false);
          }, 2e3);
        }
      });
      $("#quick-buy-cancel").on("click", function() {
        $("#alfa-modal-overlay").remove();
      });
      $input.on("keypress", function(e) {
        if (e.which === 13 && !$execute.prop("disabled")) {
          $execute.click();
        }
      });
    }
    function openQuickSellModal(sym) {
      const breakdown = calculateVaultBreakdown();
      const stock = breakdown.find((s) => s.symbol === sym);
      if (!stock || stock.looseShares <= 0) {
        const status = $("#vault-status");
        if (stock && stock.hasSwingTrade && stock.swingProtectedShares >= stock.owned) {
          status.html('<span style="color:#ef5350;">All shares protected by swing trade!</span>');
        } else if (stock && stock.benefitLockedShares >= stock.owned) {
          status.html('<span style="color:#ef5350;">All shares locked in benefit tier!</span>');
        } else {
          status.html('<span style="color:#ef5350;">No loose shares to sell!</span>');
        }
        return;
      }
      const price = getPriceForTrade(sym);
      const looseValue = stock.looseShares * price;
      let protectionInfo = "";
      if (stock.hasSwingTrade || stock.benefitLockedShares > 0) {
        protectionInfo = '<div class="quick-trade-protection">';
        if (stock.hasSwingTrade) {
          protectionInfo += `<div class="quick-trade-protection-item">\u{1F6E1}\uFE0F Swing trade protected: ${stock.swingProtectedShares.toLocaleString()} shares</div>`;
        }
        if (stock.benefitLockedShares > 0) {
          protectionInfo += `<div class="quick-trade-protection-item">\u{1F512} Benefit tier locked: ${stock.benefitLockedShares.toLocaleString()} shares</div>`;
        }
        protectionInfo += "</div>";
      }
      const html = `
    <div class="quick-trade-container">
        <div class="quick-trade-header">
            <span class="quick-trade-symbol">${sym}</span>
            <span class="quick-trade-price">${formatMoney(price)}</span>
        </div>
        
        <div class="quick-trade-info">
            <div class="quick-trade-info-row">
                <span>Available Loose Shares</span>
                <span style="color:#8bc34a;">${stock.looseShares.toLocaleString()}</span>
            </div>
            <div class="quick-trade-info-row">
                <span>Total Value</span>
                <span>${formatMoney(looseValue)}</span>
            </div>
        </div>
        
        ${protectionInfo}
        
        <div class="quick-trade-input-section">
            <label class="quick-trade-label">Amount to Sell</label>
            <input type="text" id="quick-sell-input" class="alfa-input" style="width:100%;" placeholder='e.g. "all", 50k, 1m, or share count' autofocus>
            <div class="quick-trade-hint">Enter "all", dollar amount (50k, 1m), or share count</div>
        </div>
        
        <div class="quick-trade-calc" id="quick-sell-calc" style="display:none;">
            <div class="quick-trade-calc-row">
                <span>Shares</span>
                <span id="quick-sell-shares" style="font-weight:bold; color:#fff;">0</span>
            </div>
            <div class="quick-trade-calc-row">
                <span>Gross Value</span>
                <span id="quick-sell-gross" style="font-weight:bold; color:#fff;">$0</span>
            </div>
            <div class="quick-trade-calc-row highlight">
                <span>Net Proceeds (after 0.1% fee)</span>
                <span id="quick-sell-net" style="font-weight:bold; color:#8bc34a;">$0</span>
            </div>
        </div>
        
        <div class="quick-trade-actions">
            <button id="quick-sell-execute" class="alfa-main-btn qb-btn-sell" disabled>Sell</button>
            <button id="quick-sell-cancel" class="alfa-main-btn qb-btn-close">Cancel</button>
        </div>
    </div>`;
      createModal(`Sell ${sym}`, html);
      const $input = $("#quick-sell-input");
      const $calc = $("#quick-sell-calc");
      const $shares = $("#quick-sell-shares");
      const $gross = $("#quick-sell-gross");
      const $net = $("#quick-sell-net");
      const $execute = $("#quick-sell-execute");
      $input.on("input", function() {
        const input = $(this).val().trim();
        if (!input) {
          $calc.hide();
          $execute.prop("disabled", true);
          return;
        }
        let sharesToSell = 0;
        if (input.toLowerCase() === "all") {
          sharesToSell = stock.looseShares;
        } else {
          const parsed = parseTornNumber(input);
          if (parsed >= 1e3 && !input.match(/^\d+$/)) {
            sharesToSell = Math.ceil(parsed / price);
          } else {
            sharesToSell = Math.ceil(parsed);
          }
        }
        sharesToSell = Math.min(sharesToSell, stock.looseShares);
        if (sharesToSell <= 0) {
          $calc.hide();
          $execute.prop("disabled", true);
          return;
        }
        const grossValue = sharesToSell * price;
        const netValue = grossValue * 0.999;
        $shares.text(sharesToSell.toLocaleString());
        $gross.text(formatMoney(grossValue));
        $net.text(formatMoney(netValue));
        $calc.show();
        $execute.prop("disabled", false);
      });
      $execute.on("click", async function() {
        const input = $input.val().trim();
        if (!input) return;
        let sharesToSell = 0;
        if (input.toLowerCase() === "all") {
          sharesToSell = stock.looseShares;
        } else {
          const parsed = parseTornNumber(input);
          if (parsed >= 1e3 && !input.match(/^\d+$/)) {
            sharesToSell = Math.ceil(parsed / price);
          } else {
            sharesToSell = Math.ceil(parsed);
          }
        }
        sharesToSell = Math.min(sharesToSell, stock.looseShares);
        if (sharesToSell <= 0) return;
        const $btn = $(this);
        $btn.prop("disabled", true).text("Selling...");
        $("#quick-sell-cancel").prop("disabled", true);
        try {
          await postTradeAsync(sym, sharesToSell, "sellShares");
          $btn.text("Success!").css("background", "#8bc34a").css("color", "#111");
          setTimeout(() => {
            $("#alfa-modal-overlay").remove();
            updateVaultDisplay();
          }, 1e3);
        } catch (e) {
          $btn.text("Error!").css("color", "#ef5350");
          console.error("Quick sell error:", e);
          setTimeout(() => {
            $btn.text("Sell").prop("disabled", false);
            $("#quick-sell-cancel").prop("disabled", false);
          }, 2e3);
        }
      });
      $("#quick-sell-cancel").on("click", function() {
        $("#alfa-modal-overlay").remove();
      });
      $input.on("keypress", function(e) {
        if (e.which === 13 && !$execute.prop("disabled")) {
          $execute.click();
        }
      });
    }
    function ensureVaultTradeViewPlacement() {
      const $main = $("#vault-main-content");
      const $trade = $("#vault-trade-view");
      if (!$trade.length || !$main.length) return;
      if ($.contains($main[0], $trade[0])) {
        $trade.detach().insertAfter($main);
      }
    }
    function closeVaultTradeView() {
      $("#vault-trade-view").empty().hide();
      $("#vault-main-content").show();
    }
    function openVaultTradeView(sym, type) {
      ensureVaultTradeViewPlacement();
      if (type === "buy") {
        openVaultBuyView(sym);
      } else {
        openVaultSellView(sym);
      }
    }
    function openVaultBuyView(sym) {
      const price = getPriceForTrade(sym);
      if (price <= 0) {
        $("#vault-status").html('<span style="color:#ef5350;">Price error!</span>');
        return;
      }
      const currentCash = getMoneyFast();
      const maxShares = Math.floor(currentCash / price);
      const html = `
    <div class="vault-trade-view-inner">
        <button id="vault-trade-back" class="alfa-mini-btn" style="border-color:#888; color:#888; margin-bottom:12px;">\u2190 Back to Vault</button>
        <div class="vault-trade-header">
            <span class="vault-trade-symbol">Buy ${sym}</span>
            <span class="vault-trade-price">${formatMoney(price)}</span>
        </div>
        <div class="quick-trade-container">
            <div class="quick-trade-info">
                <div class="quick-trade-info-row"><span>Available Cash</span><span style="color:#8bc34a;">${formatMoney(currentCash)}</span></div>
                <div class="quick-trade-info-row"><span>Max Shares</span><span>${maxShares.toLocaleString()}</span></div>
            </div>
            <div class="quick-trade-input-section">
                <label class="quick-trade-label">Amount to Buy</label>
                <input type="text" id="vault-trade-buy-input" class="alfa-input" style="width:100%;" placeholder="e.g. 100k, 1m, or share count" autofocus>
                <div class="quick-trade-hint">Enter dollar amount (100k, 1m) or share count</div>
            </div>
            <div class="quick-trade-calc" id="vault-trade-buy-calc" style="display:none;">
                <div class="quick-trade-calc-row"><span>Shares</span><span id="vault-trade-buy-shares" style="font-weight:bold; color:#fff;">0</span></div>
                <div class="quick-trade-calc-row highlight"><span>Total Cost</span><span id="vault-trade-buy-cost" style="font-weight:bold; color:#8bc34a;">$0</span></div>
                <div id="vault-trade-buy-warning" class="quick-trade-warning" style="display:none;"></div>
            </div>
            <div class="quick-trade-actions">
                <button id="vault-trade-buy-execute" class="alfa-main-btn qb-btn-buy" disabled>Buy</button>
            </div>
        </div>
    </div>`;
      $("#vault-main-content").hide();
      $("#vault-trade-view").html(html).show();
      $("#vault-trade-back").on("click", closeVaultTradeView);
      const $input = $("#vault-trade-buy-input");
      const $calc = $("#vault-trade-buy-calc");
      const $shares = $("#vault-trade-buy-shares");
      const $cost = $("#vault-trade-buy-cost");
      const $warning = $("#vault-trade-buy-warning");
      const $execute = $("#vault-trade-buy-execute");
      $input.on("input", function() {
        const input = $(this).val().trim();
        if (!input) {
          $calc.hide();
          $execute.prop("disabled", true);
          return;
        }
        let sharesToBuy = 0;
        const parsed = parseTornNumber(input);
        if (parsed >= 1e3 && !input.match(/^\d+$/)) sharesToBuy = Math.floor(parsed / price);
        else sharesToBuy = Math.floor(parsed);
        if (sharesToBuy <= 0) {
          $calc.hide();
          $execute.prop("disabled", true);
          return;
        }
        const totalCost = sharesToBuy * price;
        $shares.text(sharesToBuy.toLocaleString());
        $cost.text(formatMoney(totalCost));
        $calc.show();
        if (totalCost > currentCash) {
          $warning.html(`<span style="color:#ef5350;">Insufficient funds! Need ${formatMoney(totalCost - currentCash)} more.</span>`).show();
          $execute.prop("disabled", true);
        } else {
          $warning.hide();
          $execute.prop("disabled", false);
        }
      });
      $execute.on("click", async function() {
        const input = $input.val().trim();
        if (!input) return;
        let sharesToBuy = 0;
        const parsed = parseTornNumber(input);
        if (parsed >= 1e3 && !input.match(/^\d+$/)) sharesToBuy = Math.floor(parsed / price);
        else sharesToBuy = Math.floor(parsed);
        if (sharesToBuy <= 0) return;
        const $btn = $(this);
        $btn.prop("disabled", true).text("Buying...");
        try {
          await postTradeAsync(sym, sharesToBuy, "buyShares");
          localStorage.setItem("alfa_stocks_expanded", "true");
          $btn.text("Success!").css("background", "#8bc34a").css("color", "#111");
          closeVaultTradeView();
          updateVaultDisplay();
          setTimeout(async () => {
            await getLiquidNetworth(false);
            updateVaultDisplay();
          }, 600);
        } catch (e) {
          $btn.text("Error!").css("color", "#ef5350");
          console.error("Vault buy error:", e);
          setTimeout(() => {
            $btn.text("Buy").prop("disabled", false);
          }, 2e3);
        }
      });
      $input.on("keypress", function(e) {
        if (e.which === 13 && !$execute.prop("disabled")) $execute.click();
      });
    }
    function openVaultSellView(sym) {
      const breakdown = calculateVaultBreakdown();
      const stock = breakdown.find((s) => s.symbol === sym);
      if (!stock || stock.looseShares <= 0) {
        $("#vault-status").html(stock && stock.hasSwingTrade && stock.swingProtectedShares >= stock.owned ? '<span style="color:#ef5350;">All shares protected by swing trade!</span>' : stock && stock.benefitLockedShares >= stock.owned ? '<span style="color:#ef5350;">All shares locked in benefit tier!</span>' : '<span style="color:#ef5350;">No loose shares to sell!</span>');
        return;
      }
      const price = getPriceForTrade(sym);
      const looseValue = stock.looseShares * price;
      let protectionInfo = "";
      if (stock.hasSwingTrade || stock.benefitLockedShares > 0) {
        protectionInfo = '<div class="quick-trade-protection">';
        if (stock.hasSwingTrade) protectionInfo += `<div class="quick-trade-protection-item">\u{1F6E1}\uFE0F Swing trade protected: ${stock.swingProtectedShares.toLocaleString()} shares</div>`;
        if (stock.benefitLockedShares > 0) protectionInfo += `<div class="quick-trade-protection-item">\u{1F512} Benefit tier locked: ${stock.benefitLockedShares.toLocaleString()} shares</div>`;
        protectionInfo += "</div>";
      }
      const html = `
    <div class="vault-trade-view-inner">
        <button id="vault-trade-back" class="alfa-mini-btn" style="border-color:#888; color:#888; margin-bottom:12px;">\u2190 Back to Vault</button>
        <div class="vault-trade-header">
            <span class="vault-trade-symbol">Sell ${sym}</span>
            <span class="vault-trade-price">${formatMoney(price)}</span>
        </div>
        <div class="quick-trade-container">
            <div class="quick-trade-info">
                <div class="quick-trade-info-row"><span>Available Loose Shares</span><span style="color:#8bc34a;">${stock.looseShares.toLocaleString()}</span></div>
                <div class="quick-trade-info-row"><span>Total Value</span><span>${formatMoney(looseValue)}</span></div>
            </div>
            ${protectionInfo}
            <div class="quick-trade-input-section">
                <label class="quick-trade-label">Amount to Sell</label>
                <input type="text" id="vault-trade-sell-input" class="alfa-input" style="width:100%;" placeholder='e.g. "all", 50k, 1m, or share count' autofocus>
                <div class="quick-trade-hint">Enter "all", dollar amount (50k, 1m), or share count</div>
            </div>
            <div class="quick-trade-calc" id="vault-trade-sell-calc" style="display:none;">
                <div class="quick-trade-calc-row"><span>Shares</span><span id="vault-trade-sell-shares" style="font-weight:bold; color:#fff;">0</span></div>
                <div class="quick-trade-calc-row"><span>Gross Value</span><span id="vault-trade-sell-gross" style="font-weight:bold; color:#fff;">$0</span></div>
                <div class="quick-trade-calc-row highlight"><span>Net Proceeds (after 0.1% fee)</span><span id="vault-trade-sell-net" style="font-weight:bold; color:#8bc34a;">$0</span></div>
            </div>
            <div class="quick-trade-actions">
                <button id="vault-trade-sell-execute" class="alfa-main-btn qb-btn-sell" disabled>Sell</button>
            </div>
        </div>
    </div>`;
      $("#vault-main-content").hide();
      $("#vault-trade-view").html(html).show();
      $("#vault-trade-back").on("click", closeVaultTradeView);
      const $input = $("#vault-trade-sell-input");
      const $calc = $("#vault-trade-sell-calc");
      const $shares = $("#vault-trade-sell-shares");
      const $gross = $("#vault-trade-sell-gross");
      const $net = $("#vault-trade-sell-net");
      const $execute = $("#vault-trade-sell-execute");
      $input.on("input", function() {
        const input = $(this).val().trim();
        if (!input) {
          $calc.hide();
          $execute.prop("disabled", true);
          return;
        }
        let sharesToSell = input.toLowerCase() === "all" ? stock.looseShares : 0;
        if (sharesToSell === 0) {
          const parsed = parseTornNumber(input);
          sharesToSell = parsed >= 1e3 && !input.match(/^\d+$/) ? Math.ceil(parsed / price) : Math.ceil(parsed);
        }
        sharesToSell = Math.min(sharesToSell, stock.looseShares);
        if (sharesToSell <= 0) {
          $calc.hide();
          $execute.prop("disabled", true);
          return;
        }
        const grossValue = sharesToSell * price;
        const netValue = grossValue * 0.999;
        $shares.text(sharesToSell.toLocaleString());
        $gross.text(formatMoney(grossValue));
        $net.text(formatMoney(netValue));
        $calc.show();
        $execute.prop("disabled", false);
      });
      $execute.on("click", async function() {
        const input = $input.val().trim();
        if (!input) return;
        let sharesToSell = input.toLowerCase() === "all" ? stock.looseShares : 0;
        if (sharesToSell === 0) {
          const parsed = parseTornNumber(input);
          sharesToSell = parsed >= 1e3 && !input.match(/^\d+$/) ? Math.ceil(parsed / price) : Math.ceil(parsed);
        }
        sharesToSell = Math.min(sharesToSell, stock.looseShares);
        if (sharesToSell <= 0) return;
        const $btn = $(this);
        $btn.prop("disabled", true).text("Selling...");
        try {
          await postTradeAsync(sym, sharesToSell, "sellShares");
          localStorage.setItem("alfa_stocks_expanded", "true");
          $btn.text("Success!").css("background", "#8bc34a").css("color", "#111");
          closeVaultTradeView();
          updateVaultDisplay();
          setTimeout(async () => {
            await getLiquidNetworth(false);
            updateVaultDisplay();
          }, 600);
        } catch (e) {
          $btn.text("Error!").css("color", "#ef5350");
          console.error("Vault sell error:", e);
          setTimeout(() => {
            $btn.text("Sell").prop("disabled", false);
          }, 2e3);
        }
      });
      $input.on("keypress", function(e) {
        if (e.which === 13 && !$execute.prop("disabled")) $execute.click();
      });
    }
    function vaultQuickBuy(sym) {
      openQuickBuyModal(sym);
    }
    function vaultQuickSell(sym) {
      openQuickSellModal(sym);
    }
    function toggleStockLock(sym) {
      if (!vaultConfig.lockedStocks) {
        vaultConfig.lockedStocks = [];
      }
      const index = vaultConfig.lockedStocks.indexOf(sym);
      if (index > -1) {
        vaultConfig.lockedStocks.splice(index, 1);
        $("#vault-status").html(`<span style="color:#81c784;">${sym} unlocked</span>`);
      } else {
        vaultConfig.lockedStocks.push(sym);
        $("#vault-status").html(`<span style="color:#ffd54f;">${sym} locked - excluded from spread/withdraw</span>`);
      }
      saveVaultConfig();
      setTimeout(() => {
        $("#vault-status").html("");
        updateVaultDisplay();
      }, 1500);
    }
    function openTransactionHistoryModal(initialPeriod = "all") {
      if (portfolioData.transactions.length === 0) {
        alert("No transaction history available. Please sync your portfolio first.");
        return;
      }
      let currentHistoryPeriod = localStorage.getItem("alfa_history_period") || initialPeriod;
      function buildHistoryContent(period) {
        const startTimestamp = getPeriodStartTimestamp(period);
        const sortedTx2 = [...portfolioData.transactions].filter((tx) => tx.timestamp >= startTimestamp).sort((a, b) => b.timestamp - a.timestamp);
        const plData2 = calculatePortfolioPL();
        const periodPL = calculatePeriodPL(period);
        const totalBuys2 = sortedTx2.filter((t) => t.type === "buy").length;
        const totalSells2 = sortedTx2.filter((t) => t.type === "sell").length;
        const totalBuyValue2 = sortedTx2.filter((t) => t.type === "buy").reduce((sum, t) => sum + t.totalValue, 0);
        const totalSellValue2 = sortedTx2.filter((t) => t.type === "sell").reduce((sum, t) => sum + t.totalValue, 0);
        const periodRealizedPL2 = periodPL.realizedPL;
        const periodFees2 = periodPL.fees;
        const stockSummary = {};
        for (const tx of sortedTx2) {
          if (!stockSummary[tx.symbol]) {
            stockSummary[tx.symbol] = { buys: 0, sells: 0, buyValue: 0, sellValue: 0, profit: 0 };
          }
          if (tx.type === "buy") {
            stockSummary[tx.symbol].buys += tx.shares;
            stockSummary[tx.symbol].buyValue += tx.totalValue;
          } else {
            stockSummary[tx.symbol].sells += tx.shares;
            stockSummary[tx.symbol].sellValue += tx.totalValue;
            stockSummary[tx.symbol].profit += tx.profit;
          }
        }
        const stockRows3 = Object.entries(stockSummary).sort((a, b) => b[1].buyValue + b[1].sellValue - (a[1].buyValue + a[1].sellValue)).map(([sym, data]) => {
          const stockPL = plData2.perStock[sym];
          const unrealizedPL = stockPL ? stockPL.unrealizedPL : 0;
          const unrealizedColor = unrealizedPL >= 0 ? "#8bc34a" : "#ef5350";
          const realizedColor = data.profit >= 0 ? "#8bc34a" : "#ef5350";
          return `<tr>
                    <td style="font-weight:bold;">${sym}</td>
                    <td style="text-align:right;">${data.buys.toLocaleString()}</td>
                    <td style="text-align:right;">${data.sells.toLocaleString()}</td>
                    <td style="text-align:right;">${formatMoney(data.buyValue)}</td>
                    <td style="text-align:right;">${formatMoney(data.sellValue)}</td>
                    <td style="text-align:right; color:${realizedColor};">${data.profit >= 0 ? "+" : ""}${formatMoney(data.profit)}</td>
                    <td style="text-align:right; color:${unrealizedColor};">${stockPL ? (unrealizedPL >= 0 ? "+" : "") + formatMoney(unrealizedPL) : "-"}</td>
                </tr>`;
        }).join("");
        const txRows2 = sortedTx2.slice(0, 200).map((tx) => {
          const date = new Date(tx.timestamp * 1e3);
          const dateStr = date.toLocaleDateString() + " " + date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
          const typeColor = tx.type === "buy" ? "#66bb6a" : "#ef5350";
          const typeIcon = tx.type === "buy" ? "\u25B2" : "\u25BC";
          const profitDisplay = tx.type === "sell" ? `<span style="color:${tx.profit >= 0 ? "#8bc34a" : "#ef5350"};">${tx.profit >= 0 ? "+" : ""}${formatMoney(tx.profit)}</span>` : "-";
          return `<tr>
                <td style="font-size:10px; color:#888;">${dateStr}</td>
                <td style="color:${typeColor}; font-weight:bold;">${typeIcon} ${tx.type.toUpperCase()}</td>
                <td style="font-weight:bold;">${tx.symbol}</td>
                <td style="text-align:right;">${tx.shares.toLocaleString()}</td>
                <td style="text-align:right;">${formatMoney(tx.pricePerShare)}</td>
                <td style="text-align:right;">${formatMoney(tx.totalValue)}</td>
                <td style="text-align:right;">${profitDisplay}</td>
            </tr>`;
        }).join("");
        return { sortedTx: sortedTx2, totalBuys: totalBuys2, totalSells: totalSells2, totalBuyValue: totalBuyValue2, totalSellValue: totalSellValue2, periodRealizedPL: periodRealizedPL2, periodFees: periodFees2, stockRows: stockRows3, txRows: txRows2, plData: plData2 };
      }
      let contentData = buildHistoryContent(currentHistoryPeriod);
      const { sortedTx, totalBuys, totalSells, totalBuyValue, totalSellValue, periodRealizedPL, periodFees, stockRows: stockRows2, txRows, plData } = contentData;
      const html = `
    <div class="tx-history-container">
        <div class="tx-period-filter-row">
            <span class="tx-period-label">Period:</span>
            <button class="tx-period-btn ${currentHistoryPeriod === "day" ? "active" : ""}" data-period="day">Day</button>
            <button class="tx-period-btn ${currentHistoryPeriod === "week" ? "active" : ""}" data-period="week">Week</button>
            <button class="tx-period-btn ${currentHistoryPeriod === "month" ? "active" : ""}" data-period="month">Month</button>
            <button class="tx-period-btn ${currentHistoryPeriod === "ytd" ? "active" : ""}" data-period="ytd">YTD</button>
            <button class="tx-period-btn ${currentHistoryPeriod === "year" ? "active" : ""}" data-period="year">Year</button>
            <button class="tx-period-btn ${currentHistoryPeriod === "all" ? "active" : ""}" data-period="all">All</button>
        </div>
        
        <div class="tx-tabs">
            <button class="tx-tab active" data-tab="summary">Summary</button>
            <button class="tx-tab" data-tab="stocks">By Stock</button>
            <button class="tx-tab" data-tab="history">Transactions</button>
        </div>
        
        <div class="tx-tab-content" id="tab-summary">
            <div class="tx-period-badge">${getPeriodLabel(currentHistoryPeriod)}</div>
            <div class="tx-summary-grid">
                <div class="tx-summary-card">
                    <span class="tx-summary-label">Buys (${getPeriodLabel(currentHistoryPeriod)})</span>
                    <span class="tx-summary-value">${totalBuys}</span>
                    <span class="tx-summary-sub">${formatMoney(totalBuyValue)}</span>
                </div>
                <div class="tx-summary-card">
                    <span class="tx-summary-label">Sells (${getPeriodLabel(currentHistoryPeriod)})</span>
                    <span class="tx-summary-value">${totalSells}</span>
                    <span class="tx-summary-sub">${formatMoney(totalSellValue)}</span>
                </div>
                <div class="tx-summary-card ${periodRealizedPL >= 0 ? "pl-positive" : "pl-negative"}">
                    <span class="tx-summary-label">Realized P&L (${getPeriodLabel(currentHistoryPeriod)})</span>
                    <span class="tx-summary-value">${periodRealizedPL >= 0 ? "+" : ""}${formatMoney(periodRealizedPL)}</span>
                    <span class="tx-summary-sub">From ${totalSells} sales</span>
                </div>
                <div class="tx-summary-card">
                    <span class="tx-summary-label">Fees (${getPeriodLabel(currentHistoryPeriod)})</span>
                    <span class="tx-summary-value" style="color:#ff9800;">${formatMoney(periodFees)}</span>
                    <span class="tx-summary-sub">0.1% sell fee</span>
                </div>
                <div class="tx-summary-card ${plData.unrealizedPL >= 0 ? "pl-positive" : "pl-negative"}">
                    <span class="tx-summary-label">Unrealized P&L (Total)</span>
                    <span class="tx-summary-value">${plData.unrealizedPL >= 0 ? "+" : ""}${formatMoney(plData.unrealizedPL)}</span>
                    <span class="tx-summary-sub">${plData.unrealizedPLPercent >= 0 ? "+" : ""}${plData.unrealizedPLPercent.toFixed(2)}%</span>
                </div>
                <div class="tx-summary-card">
                    <span class="tx-summary-label">Cost Basis (Total)</span>
                    <span class="tx-summary-value">${formatMoney(plData.totalCostBasis)}</span>
                    <span class="tx-summary-sub">Current holdings</span>
                </div>
            </div>
        </div>
        
        <div class="tx-tab-content" id="tab-stocks" style="display:none;">
            <div style="max-height:400px; overflow-y:auto;">
                <table class="tx-table">
                    <thead>
                        <tr>
                            <th>Stock</th>
                            <th style="text-align:right;">Bought</th>
                            <th style="text-align:right;">Sold</th>
                            <th style="text-align:right;">Buy Val</th>
                            <th style="text-align:right;">Sell Val</th>
                            <th style="text-align:right;">Realized</th>
                            <th style="text-align:right;">Unrealized</th>
                        </tr>
                    </thead>
                    <tbody>${stockRows2}</tbody>
                </table>
            </div>
        </div>
        
        <div class="tx-tab-content" id="tab-history" style="display:none;">
            <div style="max-height:400px; overflow-y:auto;">
                <table class="tx-table">
                    <thead>
                        <tr>
                            <th>Date</th>
                            <th>Type</th>
                            <th>Stock</th>
                            <th style="text-align:right;">Shares</th>
                            <th style="text-align:right;">Price</th>
                            <th style="text-align:right;">Total</th>
                            <th style="text-align:right;">P&L</th>
                        </tr>
                    </thead>
                    <tbody>${txRows}</tbody>
                </table>
            </div>
            ${sortedTx.length > 200 ? `<div style="text-align:center; padding:10px; color:#888; font-size:11px;">Showing 200 of ${sortedTx.length} transactions</div>` : `<div style="text-align:center; padding:10px; color:#888; font-size:11px;">${sortedTx.length} transactions</div>`}
        </div>
        
        <div class="tx-actions">
            <button id="tx-export-btn" class="alfa-main-btn" style="border-color:#888; color:#888;">Export CSV</button>
            <button id="tx-clear-btn" class="alfa-main-btn" style="border-color:#ef5350; color:#ef5350;">Clear Data</button>
        </div>
    </div>`;
      createModal("Transaction History & P&L", html);
      $(".tx-period-btn").on("click", function() {
        const newPeriod = $(this).data("period");
        localStorage.setItem("alfa_history_period", newPeriod);
        closeModal();
        openTransactionHistoryModal(newPeriod);
      });
      $(".tx-tab").on("click", function() {
        const tab = $(this).data("tab");
        $(".tx-tab").removeClass("active");
        $(this).addClass("active");
        $(".tx-tab-content").hide();
        $(`#tab-${tab}`).show();
      });
      $("#tx-export-btn").on("click", function() {
        exportTransactionCSV();
      });
      $("#tx-clear-btn").on("click", function() {
        if (confirm("This will clear ALL portfolio tracking data. You can re-sync at any time. Continue?")) {
          portfolioData = {
            transactions: [],
            lastSyncTimestamp: 0,
            costBasis: {},
            realizedPL: 0,
            lastFullSync: 0
          };
          savePortfolioData();
          $("#alfa-modal-overlay").remove();
          updateVaultDisplay();
        }
      });
    }
    function exportTransactionCSV() {
      if (portfolioData.transactions.length === 0) {
        alert("No transactions to export");
        return;
      }
      const headers = ["Date", "Type", "Symbol", "Shares", "Price Per Share", "Total Value", "Fees", "Profit/Loss"];
      const rows = portfolioData.transactions.map((tx) => {
        const date = new Date(tx.timestamp * 1e3).toISOString();
        return [
          date,
          tx.type.toUpperCase(),
          tx.symbol,
          tx.shares,
          tx.pricePerShare.toFixed(2),
          tx.totalValue,
          tx.fees || 0,
          tx.profit || 0
        ].join(",");
      });
      const csv = [headers.join(","), ...rows].join("\n");
      const blob = new Blob([csv], { type: "text/csv" });
      const url = URL.createObjectURL(blob);
      const a = document.createElement("a");
      a.href = url;
      a.download = `torn_stock_transactions_${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.csv`;
      a.click();
      URL.revokeObjectURL(url);
    }
    function createModal(title, contentHtml) {
      $("#alfa-modal-overlay").remove();
      let modal = `<div id="alfa-modal-overlay" class="alfa-modal-overlay hf-torn">
        <div class="alfa-modal"><div class="alfa-modal-header"><h3>${title}</h3><span id="alfa-modal-close" class="alfa-modal-close">&times;</span></div>
        <div class="alfa-modal-body">${contentHtml}</div></div></div>`;
      $("body").append(modal);
      $("#alfa-modal-close").on("click", function() {
        $("#alfa-modal-overlay").remove();
      });
      $("#alfa-modal-overlay").on("click", function(e) {
        if (e.target.id === "alfa-modal-overlay") $("#alfa-modal-overlay").remove();
      });
    }
    function closeModal() {
      $("#alfa-modal-overlay").remove();
    }
    function parseTornNumber(val) {
      if (typeof val !== "string") return 0;
      val = val.trim().toLowerCase();
      if (!val) return 0;
      if (val.endsWith("k")) return parseFloat(val.replace("k", "")) * 1e3;
      if (val.endsWith("m")) return parseFloat(val.replace("m", "")) * 1e6;
      if (val.endsWith("b")) return parseFloat(val.replace("b", "")) * 1e9;
      return parseFloat(val.replace(/,/g, ""));
    }
    function formatMoney(amount) {
      return "$" + amount.toLocaleString("en-US", { minimumFractionDigits: 0, maximumFractionDigits: 2 });
    }
    function formatMoneyWhole(amount) {
      return "$" + formatCompactNumber(amount);
    }
    function getRFC() {
      var c = document.cookie.match(/rfc_v=([^;]+)/);
      return c ? c[1] : "";
    }
    function getBenefitTier(sym, shares) {
      const data = STOCK_DATA[sym];
      if (!data) return { tier: 0, next: 0, label: "Unknown" };
      if (data.type === "P") {
        return shares >= data.base ? { tier: 1, next: data.base, label: "Passive" } : { tier: 0, next: data.base, label: "None" };
      }
      if (shares < data.base) {
        return { tier: 0, next: data.base, label: "None" };
      }
      const tier = Math.floor(Math.log2(shares / data.base + 1));
      const next = data.base * (Math.pow(2, tier + 1) - 1);
      return { tier, next, label: "Tier " + tier };
    }
    async function syncWallet(silent = false) {
      let key = localStorage.getItem("alfa_vault_apikey");
      if (!key) return 0;
      if (Date.now() - lastSync < 2e3) return 0;
      lastSync = Date.now();
      if (!silent) $("#responseStock").html("Syncing...").css("color", "orange");
      try {
        const response = await fetch(`https://api.torn.com/user/?selections=money&key=${key}&ts=${Date.now()}`);
        const data = await response.json();
        if (data.money_onhand !== void 0) {
          let money = data.money_onhand;
          if ($("#user-money").length > 0) $("#user-money").attr("data-money", money).text("$" + money.toLocaleString());
          if (!silent) $("#responseStock").html(`Synced: $${money.toLocaleString()}`).css("color", "green");
          return money;
        }
      } catch (e) {
        if (!silent) $("#responseStock").html("Sync Failed").css("color", "red");
      }
      return 0;
    }
    function getMoneyFast() {
      let dataMoney = $("#user-money").attr("data-money");
      if (dataMoney) return parseFloat(dataMoney);
      let textMoney = $("#user-money").text();
      return textMoney ? parseTornNumber(textMoney) : 0;
    }
    function insert() {
      if ($("ul[class^='stock_']").length == 0) {
        setTimeout(insert, 500);
        return;
      }
      $("ul[class^='stock_']").each(function() {
        let sym = $("img", $(this)).attr("src").split("logos/")[1].split(".svg")[0];
        stockId[sym] = $(this).attr("id");
        stockRows[sym] = $(this);
        stocks[sym] = $(this);
      });
      buildStockIdMap();
      loadVaultConfig();
      renderVaultSection();
      if (!window.__smartVaultShareCacheHooked) {
        let applyExternalStockBuy = function(sym, shares) {
          const symbol = String(sym || "").toUpperCase();
          const n = Math.floor(Number(shares) || 0);
          if (!symbol || n <= 0) return;
          updateLocalCache(symbol, n);
          updateVaultDisplay(true);
          try {
            if ($("#gamble-content").is(":visible")) updateGambleCashDisplay();
          } catch (e) {
          }
        };
        window.__smartVaultShareCacheHooked = true;
        document.addEventListener("visibilitychange", () => {
          if (document.visibilityState === "visible") invalidateShareCache();
        });
        window.addEventListener("focus", () => invalidateShareCache());
        window.__smartStockVaultApplyBuy = applyExternalStockBuy;
        try {
          if (typeof unsafeWindow !== "undefined") {
            unsafeWindow.__smartStockVaultApplyBuy = applyExternalStockBuy;
          }
        } catch (e) {
        }
        document.addEventListener("smart-torn:stock-bought", (ev) => {
          const d = ev && ev.detail;
          if (!d) return;
          applyExternalStockBuy(d.symbol, d.shares);
        });
      }
    }
    function getOwnedSharesFromDom(id) {
      let row = stockRows[id];
      if (!row) return 0;
      let mobileEl = row.find("p[class^='count']");
      if (mobileEl.length > 0) return parseFloat(mobileEl.text().replace(/,/g, "")) || 0;
      let cols = row.children("div");
      if (cols.length >= 5) return parseFloat($(cols[4]).text().replace(/,/g, "")) || 0;
      return 0;
    }
    function invalidateShareCache(sym) {
      if (sym) delete localShareCache[sym];
      else localShareCache = {};
    }
    function getOwnedShares(id, opts2 = {}) {
      if (!opts2.forceDom && localShareCache[id] !== void 0) return localShareCache[id];
      const n = getOwnedSharesFromDom(id);
      localShareCache[id] = n;
      return n;
    }
    function updateLocalCache(sym, amt) {
      const current = localShareCache[sym] !== void 0 ? localShareCache[sym] : getOwnedSharesFromDom(sym);
      localShareCache[sym] = Math.max(0, current + amt);
    }
    function getSharePriceFromRow($row) {
      if (!$row || !$row.length) return 0;
      const rawId = String($row.attr("id") || "");
      const numericId = rawId.replace(/^stock_/i, "");
      if (numericId) {
        const byId = $row.find(`div.price_${numericId}`);
        if (byId.length) {
          const price = parseFloat(byId.first().text().replace(/,/g, ""));
          if (!Number.isNaN(price) && price > 0) return price;
        }
      }
      let best = 0;
      $row.find('div[class*="price_"]').each(function() {
        const cls = this.className || "";
        if (/price_change|price_diff|price_pct/i.test(cls)) return;
        const price = parseFloat($(this).text().replace(/,/g, ""));
        if (!Number.isNaN(price) && price > best) best = price;
      });
      return best;
    }
    function getPrice(id) {
      if (stockRows[id]) return getSharePriceFromRow(stockRows[id]);
      return 0;
    }
    function resolveTradeStockId(symb) {
      const raw = stockId[symb];
      if (raw) return raw;
      const numeric = SYMBOL_TO_ID[symb];
      if (numeric) return String(numeric);
      return null;
    }
    function getPriceForTrade(sym) {
      const live = getPrice(sym);
      if (live > 0) return live;
      const analysis = vaultAnalysisCache[sym];
      if (analysis && analysis.price > 0) return analysis.price;
      return 0;
    }
    async function postTradeRequest(symb, amt, step) {
      const tradeId = resolveTradeStockId(symb);
      if (!tradeId) throw new Error("Stock not found on page");
      const rfc = getRFC();
      const url = `https://www.torn.com/page.php?sid=StockMarket&step=${step}&rfcv=${encodeURIComponent(rfc)}`;
      const body = new URLSearchParams();
      body.set("stockId", tradeId);
      body.set("amount", String(amt));
      const res = await fetch(url, {
        method: "POST",
        credentials: "include",
        headers: {
          "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
          "X-Requested-With": "XMLHttpRequest"
        },
        body: body.toString()
      });
      const text = await res.text();
      let data;
      try {
        data = JSON.parse(text);
      } catch (e) {
        const snippet = String(text || "").replace(/\s+/g, " ").trim().slice(0, 140);
        throw new Error(
          "Invalid response from Torn (HTTP " + res.status + (snippet ? ": " + snippet : ", empty body") + ")"
        );
      }
      if (!data.success) {
        throw new Error(data.message || data.text || "Trade failed");
      }
      return data;
    }
    function getDailyYield(sym) {
      let b = ADVISOR_DATA[sym];
      if (!b) return 0;
      let val = 0;
      if (b.type === "cash") val = b.val;
      else if (b.type === "item") val = itemPrices[b.id] || 0;
      else if (b.type === "manual") val = itemPrices["HRG_AVG"] || 0;
      else if (b.type === "average") {
        let t = 0, c = 0;
        for (let cid of b.ids) {
          let p = itemPrices[cid] || 0;
          if (p > 0) {
            t += p;
            c++;
          }
        }
        val = c > 0 ? t / c : 0;
      }
      return val > 0 && b.freq ? val / b.freq : 0;
    }
    async function getLiquidNetworth(fastMode = false) {
      if (fastMode && lastNwCache) return lastNwCache;
      let key = localStorage.getItem("alfa_vault_apikey");
      if (!key) return lastNwCache || { liquid: 0, pureCash: 0, wallet: 0, dailyBank: 0, bankActive: false, bankPrincipal: 0 };
      try {
        const res = await fetch(`https://api.torn.com/v2/user/money?key=${key}`);
        const data = await res.json();
        if (!data.money) return lastNwCache || { liquid: 0, pureCash: 0, wallet: 0, dailyBank: 0, bankActive: false, bankPrincipal: 0 };
        let m = data.money, dailyBank = 0, bankActive = false, bankPrincipal = 0;
        if (m.city_bank && m.city_bank.amount > 0) {
          bankPrincipal = m.city_bank.amount;
          if (m.city_bank.profit > 0 && m.city_bank.duration > 0) dailyBank = m.city_bank.profit / m.city_bank.duration;
          bankActive = true;
        }
        let pureCash = m.wallet || 0;
        if (networthSettings.sources.points && m.points > 0) pureCash += m.points * (itemPrices["points"] || 45e3);
        const wallet = m.wallet || 0;
        lastNwCache = { liquid: 0, pureCash, wallet, dailyBank, bankActive, bankPrincipal, nwFromApi: true };
        return lastNwCache;
      } catch (e) {
        console.error("NW Error", e);
        return lastNwCache || { liquid: 0, pureCash: 0, wallet: 0, dailyBank: 0, bankActive: false, bankPrincipal: 0 };
      }
    }
    async function fetchUserPortfolio() {
      let key = localStorage.getItem("alfa_vault_apikey");
      if (!key) return;
      try {
        const res = await fetch(`https://api.torn.com/user/?selections=stocks&key=${key}&ts=${Date.now()}`);
        const data = await res.json();
        if (data.stocks) {
          let idToSym = {};
          for (let [sym, domId] of Object.entries(stockId)) {
            idToSym[domId.replace("stock_", "")] = sym;
          }
          for (let [sID, sData] of Object.entries(data.stocks)) {
            let sym = idToSym[sID];
            if (sym) localShareCache[sym] = sData.total_shares || 0;
          }
          for (let sym of Object.keys(stockRows)) {
            localShareCache[sym] = getOwnedSharesFromDom(sym);
          }
        }
      } catch (e) {
        console.error("Portfolio Sync Error", e);
      }
    }
    async function sellSmart(sym, shares) {
      let price = getPrice(sym);
      if (price <= 0) {
        alert("Price error.");
        return;
      }
      let confirmMsg = "";
      if ($("#alfa-lock-toggle").is(":checked")) {
        let owned = getOwnedShares(sym);
        let future = getBenefitTier(sym, owned - shares);
        let current = getBenefitTier(sym, owned);
        if (future.tier < current.tier) {
          confirmMsg = `WARNING: Selling this will drop your Block Tier!

`;
        }
      }
      let totalCash = formatMoney(shares * price);
      confirmMsg += `Sell ${shares.toLocaleString()} shares of ${sym} for approx ${totalCash}?`;
      if (confirm(confirmMsg)) {
        await postTrade(sym, shares, "sellShares", `Sold`);
        setTimeout(() => updateVaultDisplay(), 1e3);
      }
    }
    async function buySmart(sym, targetShares) {
      let money = await syncWallet(true);
      let price = getPrice(sym);
      if (price <= 0) {
        alert("Price error.");
        return;
      }
      let maxAffordable = Math.floor(money / price);
      let sharesToBuy = Math.min(maxAffordable, targetShares);
      if (sharesToBuy <= 0) {
        alert("Not enough cash!");
        return;
      }
      let totalCost = formatMoney(sharesToBuy * price);
      if (confirm(`Invest ${totalCost} to buy ${sharesToBuy.toLocaleString()} shares of ${sym}?`)) {
        await postTrade(sym, sharesToBuy, "buyShares", `Invested`);
        setTimeout(() => updateVaultDisplay(), 1e3);
      }
    }
    function postTrade(symb, amt, step, msg) {
      postTradeRequest(symb, amt, step).then(() => {
        updateLocalCache(symb, step === "buyShares" ? amt : -amt);
        updateVaultDisplay();
        setTimeout(async () => {
          await getLiquidNetworth(false);
          updateVaultDisplay();
        }, 600);
      }).catch((e) => console.log("Trade error", e));
    }
    async function postTradeAsync(symb, amt, step) {
      await postTradeRequest(symb, amt, step);
      updateLocalCache(symb, step === "buyShares" ? amt : -amt);
      return { success: true, symbol: symb, amount: amt, step };
    }
    async function executeGambleDeposit(amount, $btn, statusEl) {
      const sym = getGambleTargetStock();
      if (!sym || !vaultConfig.stocks.includes(sym)) {
        if (statusEl) statusEl.html('<span style="color:#ef5350;">No target stock configured. Add vault stocks in Settings.</span>');
        return;
      }
      if (vaultConfig.lockedStocks && vaultConfig.lockedStocks.includes(sym)) {
        if (statusEl) statusEl.html('<span style="color:#ef5350;">Target stock is locked.</span>');
        return;
      }
      if (!vaultAnalysisCache[sym]) await analyzeStockForVault(sym);
      const price = getPriceForTrade(sym);
      if (price <= 0) {
        if (statusEl) statusEl.html('<span style="color:#ef5350;">Price unavailable.</span>');
        return;
      }
      let cash = 0;
      const domCash = getMoneyFast();
      if (domCash !== void 0 && domCash !== null && !isNaN(domCash) && $("#user-money").length > 0) {
        cash = domCash;
      } else {
        const nw = await getLiquidNetworth(true) || await getLiquidNetworth(false);
        cash = (nw.wallet !== void 0 ? nw.wallet : nw.pureCash) || 0;
      }
      let sharesToBuy = 0;
      if (amount < 0) {
        sharesToBuy = Math.floor(cash / price);
      } else {
        const cost = amount;
        sharesToBuy = Math.floor(cost / price);
        if (sharesToBuy * price > cash) {
          if (statusEl) statusEl.html(`<span style="color:#ef5350;">Cannot deposit ${formatMoneyWhole(amount)}: only ${formatMoneyWhole(cash)} available. All or nothing.</span>`);
          setTimeout(() => {
            if (statusEl) statusEl.html("");
          }, 5e3);
          return;
        }
      }
      if (sharesToBuy <= 0) {
        if (statusEl) statusEl.html('<span style="color:#ef5350;">No cash to deposit.</span>');
        return;
      }
      if ($btn) $btn.prop("disabled", true).data("orig-text", $btn.text()).text("...");
      try {
        await postTradeAsync(sym, sharesToBuy, "buyShares");
        if (statusEl) statusEl.html(`<span style="color:#8bc34a;">Deposited ${sharesToBuy.toLocaleString()} shares (${formatMoneyWhole(sharesToBuy * price)})</span>`);
        updateVaultDisplay();
      } catch (e) {
        if (statusEl) statusEl.html(`<span style="color:#ef5350;">${e.message || "Failed"}</span>`);
        console.error("Gamble deposit error:", e);
      }
      if ($btn) {
        $btn.prop("disabled", false).text($btn.data("orig-text") || "Deposit");
        setTimeout(() => {
          if (statusEl) statusEl.html("");
        }, 3e3);
      }
    }
    async function executeGambleWithdraw(amount, $btn, statusEl) {
      const sym = getGambleTargetStock();
      if (!sym || !vaultConfig.stocks.includes(sym)) {
        if (statusEl) statusEl.html('<span style="color:#ef5350;">No target stock configured.</span>');
        return;
      }
      if (vaultConfig.lockedStocks && vaultConfig.lockedStocks.includes(sym)) {
        if (statusEl) statusEl.html('<span style="color:#ef5350;">Target stock is locked. Unlock in vault to withdraw.</span>');
        return;
      }
      const breakdown = calculateVaultBreakdown();
      const stock = breakdown.find((s) => s.symbol === sym);
      if (!stock || stock.looseShares <= 0) {
        if (statusEl) {
          if (stock && stock.hasSwingTrade && stock.swingProtectedShares >= stock.owned) {
            statusEl.html('<span style="color:#ef5350;">All shares protected by swing trade!</span>');
          } else if (stock && stock.benefitLockedShares >= stock.owned) {
            statusEl.html('<span style="color:#ef5350;">All shares locked in benefit tier!</span>');
          } else {
            statusEl.html('<span style="color:#ef5350;">No loose shares to withdraw.</span>');
          }
        }
        return;
      }
      if (!vaultAnalysisCache[sym]) await analyzeStockForVault(sym);
      const price = getPriceForTrade(sym);
      if (price <= 0) {
        if (statusEl) statusEl.html('<span style="color:#ef5350;">Price unavailable.</span>');
        return;
      }
      const maxSellable = Math.max(0, stock.owned - stock.swingProtectedShares - stock.benefitLockedShares);
      let maxByValue = maxSellable;
      if (stock.looseValue > 0 && price > 0) {
        maxByValue = Math.min(maxSellable, Math.floor(stock.looseValue / price));
      }
      const maxSellableShares = Math.min(stock.looseShares, maxByValue);
      let sharesToSell = 0;
      if (amount < 0) {
        sharesToSell = maxSellableShares;
      } else {
        const sharesRequested = Math.ceil(amount / price);
        if (sharesRequested > maxSellableShares) {
          if (statusEl) statusEl.html(`<span style="color:#ef5350;">Cannot withdraw ${formatMoneyWhole(amount)}: only ${formatMoneyWhole(stock.looseValue)} loose available (${maxSellableShares.toLocaleString()} shares). All or nothing.</span>`);
          setTimeout(() => {
            if (statusEl) statusEl.html("");
          }, 5e3);
          return;
        }
        sharesToSell = sharesRequested;
      }
      if (sharesToSell <= 0) {
        if (statusEl) statusEl.html('<span style="color:#ef5350;">No shares to sell.</span>');
        return;
      }
      if ($btn) $btn.prop("disabled", true).data("orig-text", $btn.text()).text("...");
      try {
        await postTradeAsync(sym, sharesToSell, "sellShares");
        if (statusEl) statusEl.html(`<span style="color:#8bc34a;">Withdrew ${sharesToSell.toLocaleString()} shares (${formatMoneyWhole(sharesToSell * price * 0.999)})</span>`);
        updateVaultDisplay();
      } catch (e) {
        if (statusEl) statusEl.html(`<span style="color:#ef5350;">${e.message || "Failed"}</span>`);
        console.error("Gamble withdraw error:", e);
      }
      if ($btn) {
        $btn.prop("disabled", false).text($btn.data("orig-text") || "Withdraw");
        setTimeout(() => {
          if (statusEl) statusEl.html("");
        }, 3e3);
      }
    }
    async function executeGamblePanic($btn, statusEl) {
      await executeGambleDeposit(-1, $btn, statusEl);
    }
    async function updateGambleCashDisplay() {
      const el = $("#gamble-cash-value");
      const availEl = $("#gamble-available-value");
      if (!el.length) return;
      try {
        const domCash = getMoneyFast();
        if (domCash !== void 0 && domCash !== null && !isNaN(domCash) && $("#user-money").length > 0) {
          if (lastNwCache) {
            lastNwCache.wallet = domCash;
            lastNwCache.pureCash = domCash;
          }
          el.text(formatMoneyWhole(domCash));
        } else {
          const nw = await getLiquidNetworth(true) || await getLiquidNetworth(false);
          el.text(formatMoneyWhole((nw.wallet !== void 0 ? nw.wallet : nw.pureCash) || 0));
        }
      } catch (e) {
        el.text("--");
      }
      try {
        if (!availEl.length) return;
        const sym = getGambleTargetStock();
        if (!sym || !vaultConfig.stocks.includes(sym)) {
          availEl.text("--");
          return;
        }
        if (vaultConfig.lockedStocks && vaultConfig.lockedStocks.includes(sym)) {
          availEl.text("$0");
          return;
        }
        const breakdown = calculateVaultBreakdown();
        const stock = breakdown.find((s) => s.symbol === sym);
        if (!stock || stock.looseShares <= 0) {
          availEl.text("$0");
          return;
        }
        availEl.text(formatMoneyWhole(stock.looseValue));
      } catch (e) {
        if (availEl.length) availEl.text("--");
      }
    }
    function openGambleConfigModal() {
      const presets = gambleConfig.depositPresets || DEFAULT_GAMBLE_CONFIG.depositPresets;
      const wPresets = gambleConfig.withdrawPresets || DEFAULT_GAMBLE_CONFIG.withdrawPresets;
      const depositRows = presets.map((p, i) => `
        <div class="gamble-config-row">
            <input type="text" class="gamble-config-label alfa-input" data-type="deposit" data-i="${i}" value="${p.label}" placeholder="Label" style="width:60px;">
            <input type="text" class="gamble-config-amount alfa-input" data-type="deposit" data-i="${i}" value="${p.amount < 0 ? "all" : p.amount.toLocaleString()}" placeholder="Amount or 'all'" style="width:100px;">
        </div>
    `).join("");
      const withdrawRows = wPresets.map((p, i) => `
        <div class="gamble-config-row">
            <input type="text" class="gamble-config-label alfa-input" data-type="withdraw" data-i="${i}" value="${p.label}" placeholder="Label" style="width:60px;">
            <input type="text" class="gamble-config-amount alfa-input" data-type="withdraw" data-i="${i}" value="${p.amount < 0 ? "all" : p.amount.toLocaleString()}" placeholder="Amount or 'all'" style="width:100px;">
        </div>
    `).join("");
      const html = `
    <div class="gamble-config-modal">
        <div class="gamble-config-section">
            <div class="gamble-config-title">Deposit Presets</div>
            ${depositRows}
        </div>
        <div class="gamble-config-section">
            <div class="gamble-config-title">Withdraw Presets</div>
            ${withdrawRows}
        </div>
        <div class="gamble-config-hint">Use "all" for full amount. Amounts support k, m (e.g. 50k, 1m).</div>
        <div class="gamble-config-actions">
            <button id="gamble-config-save" class="alfa-main-btn" style="border-color:#66bb6a; color:#66bb6a;">Save</button>
            <button id="gamble-config-cancel" class="alfa-main-btn" style="border-color:#888; color:#888;">Cancel</button>
        </div>
    </div>`;
      createModal("Gamble Presets", html);
      $("#gamble-config-save").on("click", function() {
        const dep = gambleConfig.depositPresets || [...DEFAULT_GAMBLE_CONFIG.depositPresets];
        const wit = gambleConfig.withdrawPresets || [...DEFAULT_GAMBLE_CONFIG.withdrawPresets];
        $(".gamble-config-label[data-type=deposit]").each(function() {
          const i = parseInt($(this).data("i"), 10);
          dep[i] = dep[i] || {};
          dep[i].label = $(this).val().trim() || "Preset";
        });
        $(".gamble-config-amount[data-type=deposit]").each(function() {
          const i = parseInt($(this).data("i"), 10);
          const val = $(this).val().trim().toLowerCase();
          dep[i] = dep[i] || {};
          dep[i].amount = val === "all" ? -1 : parseTornNumber($(this).val()) || 0;
        });
        $(".gamble-config-label[data-type=withdraw]").each(function() {
          const i = parseInt($(this).data("i"), 10);
          wit[i] = wit[i] || {};
          wit[i].label = $(this).val().trim() || "Preset";
        });
        $(".gamble-config-amount[data-type=withdraw]").each(function() {
          const i = parseInt($(this).data("i"), 10);
          const val = $(this).val().trim().toLowerCase();
          wit[i] = wit[i] || {};
          wit[i].amount = val === "all" ? -1 : parseTornNumber($(this).val()) || 0;
        });
        gambleConfig.depositPresets = dep;
        gambleConfig.withdrawPresets = wit;
        saveGambleConfig();
        $("#alfa-modal-overlay").remove();
        updateVaultDisplay();
      });
      $("#gamble-config-cancel").on("click", function() {
        $("#alfa-modal-overlay").remove();
      });
    }
    async function fetchBankRates() {
      let key = localStorage.getItem("alfa_vault_apikey");
      if (!key) {
        alert("API Key missing");
        return;
      }
      const fetchBtn = $("#settings-fetch-bank, #adv-fetch-bank");
      fetchBtn.text("Fetching...");
      try {
        const resRates = await fetch(`https://api.torn.com/v2/torn?selections=bank&key=${key}`);
        const dataRates = await resRates.json();
        const resPerks = await fetch(`https://api.torn.com/user/?selections=perks&key=${key}`);
        const dataPerks = await resPerks.json();
        if (dataRates.error) throw new Error(dataRates.error.error);
        const parseInterest = (list) => {
          if (!list || !Array.isArray(list)) return 0;
          let bonus = 0;
          list.forEach((str) => {
            if (str.toLowerCase().includes("bank interest")) {
              let match = str.match(/(\d+(?:\.\d+)?)%/);
              if (match) bonus += parseFloat(match[1]);
            }
          });
          return bonus;
        };
        let totalBonus = 0;
        totalBonus += parseInterest(dataPerks.merit_perks);
        totalBonus += parseInterest(dataPerks.faction_perks);
        totalBonus += parseInterest(dataPerks.job_perks);
        totalBonus += parseInterest(dataPerks.property_perks);
        totalBonus += parseInterest(dataPerks.stock_perks);
        totalBonus += parseInterest(dataPerks.education_perks);
        totalBonus += parseInterest(dataPerks.book_perks);
        let multi = 1 + totalBonus / 100;
        let bankData = dataRates.bank;
        if (bankData) {
          ["1w", "2w", "1m", "2m", "3m"].forEach((term) => {
            if (bankData[term]) {
              let baseApr = parseFloat(bankData[term]);
              let finalApr = baseApr * multi;
              $(`#bank-${term}`).val(finalApr.toFixed(2)).trigger("change");
              bankSettings["roi_" + term] = finalApr;
            }
          });
          localStorage.setItem("alfa_advisor_bank", JSON.stringify(bankSettings));
          fetchBtn.text("Updated!");
        } else {
          fetchBtn.text("No Data");
        }
      } catch (e) {
        console.error("Bank Fetch Error:", e);
        fetchBtn.text("Error");
      }
      setTimeout(() => fetchBtn.text("Fetch Rates (API)"), 2e3);
    }
    function startVaultApp() {
      opts.registerSettings?.(openSettingsModal);
      insert();
    }
    function whenReady() {
      const jq = resolveJQuery();
      if (!jq) {
        return false;
      }
      $ = jq;
      return jq(MOUNT_SELECTOR).length > 0;
    }
    const deadline = Date.now() + 3e4;
    const tick = () => {
      if (whenReady()) {
        startVaultApp();
        return;
      }
      if (Date.now() > deadline) {
        console.error("[Smart Stock Vault] jQuery or panel mount not ready after 30s");
        return;
      }
      window.setTimeout(tick, 50);
    };
    tick();
  }

  // src/stocks/legacy-styles.js
  var LEGACY_STYLES = `
.alfa-card-head { cursor: pointer; user-select: none; } .alfa-card-head:hover .alfa-card-title { color: #fff; }
.alfa-card-details { display: none; margin-top: 10px; padding-top: 8px; border-top: 1px dashed #444; font-size: 11px; color: #ccc; }
.alfa-detail-row { display: flex; justify-content: space-between; padding: 2px 0; }
.alfa-detail-sub { color: #888; padding-left: 8px; }
.alfa-detail-total { border-top: 1px solid #333; margin-top: 4px; padding-top: 4px; font-weight: bold; color: #8bc34a; }
.alfa-detail-miss { border-top: 1px solid #333; margin-top: 4px; padding-top: 4px; font-weight: bold; color: #ef5350; }
.alfa-hero { cursor: pointer; transition: background 0.2s; background: linear-gradient(135deg, #2a2a2a 0%, #222 100%); border: 1px solid #333; border-radius: 8px; padding: 20px; text-align: center; position: relative; }
.alfa-hero:hover { border-color: var(--hf-accent, #caa14a); }
.alfa-breakdown { display: none; margin-top: 15px; border-top: 1px solid #444; padding-top: 10px; text-align: left; }
.alfa-break-row { display: flex; flex-direction: column; font-size: 11px; padding: 6px 0; border-bottom: 1px solid #222; color: #ccc; }
.alfa-break-row:last-child { border-bottom: none; }
.alfa-break-val { color: #8bc34a; font-weight: bold; }
.alfa-caret { float: right; transition: transform 0.3s; font-size: 10px; color: #666; }
.alfa-expanded .alfa-caret { transform: rotate(180deg); }
.alfa-header { display: flex; justify-content: space-between; align-items: center; width: 100%; gap: 10px; margin-bottom: 10px; border-bottom: 1px solid #333; padding-bottom: 10px; }
.alfa-toolbar { display: flex; justify-content: space-between; align-items: center; width: 100%; margin-top: 5px; font-size: 11px; color: #888; }
.alfa-small-label { display: flex; align-items: center; gap: 4px; cursor: pointer; user-select: none; } .alfa-small-label:hover { color: #fff; }
.alfa-advisor-btn { background: #2a4040; border: 1px solid var(--hf-accent, #caa14a); color: #fff; padding: 4px 10px; border-radius: 4px; font-size: 11px; font-weight: bold; cursor: pointer; text-decoration: none; }
.alfa-advisor-btn:hover { background: var(--hf-accent, #caa14a); }
.alfa-input, .alfa-select { background: #333; color: #fff; border: 1px solid #555; border-radius: 4px; padding: 4px 8px; height: 28px; box-sizing: border-box; }
.alfa-input { width: 130px; } .alfa-select { width: 150px; line-height: 20px; } .alfa-input:focus, .alfa-select:focus { border-color: var(--hf-accent, #caa14a); outline: none; }
textarea.alfa-input { height: auto; min-height: 60px; }
.journal-notes-textarea { width: 100% !important; min-height: 80px; box-sizing: border-box; }
.alfa-link { cursor: pointer; color: var(--hf-accent, #caa14a); text-decoration: underline; margin-left: auto; } .alfa-link:hover { color: #fff; }
.alfa-modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.8); z-index: 99999; display: flex; justify-content: center; align-items: center; backdrop-filter: blur(4px); }
.alfa-modal { background: #1e1e1e; width: 600px; max-width: 95%; border: 1px solid #333; border-radius: 12px; box-shadow: 0 20px 50px rgba(0,0,0,0.9); display: flex; flex-direction: column; overflow: hidden; animation: fadeIn 0.2s ease-out; }
@keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
.alfa-modal-header { background: #252525; padding: 15px 20px; border-bottom: 1px solid #333; display: flex; justify-content: space-between; align-items: center; }
.alfa-modal-header h3 { margin: 0; font-size: 16px; font-weight: 600; color: #fff; letter-spacing: 0.5px; }
.alfa-modal-close { cursor: pointer; font-size: 24px; color: #666; transition: color 0.2s; } .alfa-modal-close:hover { color: #fff; }
.alfa-modal-body { padding: 20px; color: #ccc; overflow-y: auto; max-height: 80vh; }
.alfa-dashboard { display: flex; flex-direction: column; gap: 20px; }
.alfa-hero-label { font-size: 11px; text-transform: uppercase; color: #888; letter-spacing: 1px; margin-bottom: 5px; }
.alfa-hero-val { font-size: 28px; font-weight: 700; color: #8bc34a; text-shadow: 0 2px 4px rgba(0,0,0,0.3); }
.alfa-hero-sub { font-size: 12px; color: #666; margin-top: 5px; }
.alfa-grid-section { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; }
.alfa-card { background: #252525; border: 1px solid #333; border-radius: 8px; padding: 15px; display: flex; flex-direction: column; transition: transform 0.2s; } .alfa-card:hover { border-color: #444; transform: translateY(-2px); }
.alfa-card-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; border-bottom: 1px solid #333; padding-bottom: 8px; }
.alfa-card-title { font-size: 11px; font-weight: bold; color: #aaa; text-transform: uppercase; }
.alfa-card-roi { font-size: 18px; font-weight: bold; color: var(--hf-accent, #caa14a); }
.alfa-card-body { flex-grow: 1; display: flex; flex-direction: column; gap: 4px; }
.alfa-stock-name { font-size: 14px; font-weight: bold; color: #fff; }
.alfa-stock-cost { font-size: 12px; color: #888; }
.alfa-stock-gain { font-size: 12px; color: #8bc34a; margin-top: auto; padding-top: 10px; display: flex; align-items: center; gap: 5px; } .alfa-stock-gain::before { content: "\u25B2"; font-size: 8px; margin-right: 4px; }
.alfa-actions { display: flex; gap: 10px; border-top: 1px solid #333; padding-top: 20px; margin-top: 10px; }
.alfa-btn-main { flex: 1; background: #333; color: #fff; border: 1px solid #444; padding: 10px; border-radius: 6px; cursor: pointer; font-weight: bold; transition: all 0.2s; text-align: center; } .alfa-btn-main:hover { background: #444; border-color: var(--hf-accent, #caa14a); }
.alfa-settings-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
.alfa-table { width: 100%; border-collapse: separate; border-spacing: 0; font-size: 12px; }
.alfa-table th { text-align: left; padding: 8px; color: #888; border-bottom: 1px solid #444; }
.alfa-table td { padding: 8px; border-bottom: 1px solid #333; }
.alfa-check-list { display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px; max-height: 200px; overflow-y: auto; background: #151515; padding: 10px; border-radius: 6px; border: 1px solid #333; }
.alfa-check-label { display: flex; align-items: center; gap: 8px; color: #ccc; cursor: pointer; font-size: 11px; } .alfa-check-label:hover { color: #fff; }
.alfa-tbl-input { width: 100%; background: #222; border: 1px solid #444; color: #fff; padding: 4px; text-align: right; border-radius: 4px; box-sizing: border-box; }
.alfa-shortage { color: #ef5350; font-weight: bold; }
.alfa-main-btn { background: #333; color: #ddd; border: 1px solid #555; border-radius: 4px; padding: 0 15px; height: 28px; font-size: 12px; font-weight: bold; cursor: pointer; transition: all 0.2s; } .alfa-main-btn:hover { background: #444; border-color: var(--hf-accent, #caa14a); color: #fff; }
.alfa-mini-btn { background: transparent; border: 1px solid #ef5350; color: #ef5350; font-size: 9px; padding: 1px 6px; border-radius: 3px; cursor: pointer; margin-left: 8px; text-transform: uppercase; }
.alfa-mini-btn:hover { background: #ef5350; color: #fff; }
#vault-trade-back.alfa-mini-btn:hover { background: var(--hf-accent, #caa14a); border-color: var(--hf-accent, #caa14a); color: #fff; }
.alfa-invest-btn { width: 100%; background: #2a4040; border: 1px solid var(--hf-accent, #caa14a); color: #fff; padding: 6px; margin-top: 8px; font-size: 11px; font-weight: bold; cursor: pointer; border-radius: 4px; }
.alfa-invest-btn:hover { background: var(--hf-accent, #caa14a); }
/* --- DIAGNOSTIC TOOL CSS --- */
.sim-row { transition: background 0.2s; }
.sim-row:hover { background: rgba(255,255,255,0.05) !important; }
/* Ensure inputs in the modal look right */
.alfa-tbl-input { background: #111; border: 1px solid #444; color: #fff; padding: 4px; border-radius: 4px; }
/* --- MULTI-STOCK VAULT CSS --- */
.alfa-vault-section { background: #111; padding: 12px; border: 1px solid #333; border-radius: 8px; margin-bottom: 15px; color: #ccc; font-family: Arial, sans-serif; font-size: 12px; }
.alfa-vault-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
.alfa-vault-api-row { display: flex; gap: 10px; align-items: center; margin-bottom: 12px; padding-bottom: 10px; border-bottom: 1px solid #333; }
.alfa-vault-title { font-size: 14px; font-weight: bold; color: var(--hf-accent, #caa14a); }
.alfa-vault-actions { display: flex; gap: 5px; }
.alfa-vault-empty { text-align: center; padding: 20px; color: #888; }
.alfa-vault-summary { display: flex; justify-content: space-between; align-items: center; background: linear-gradient(135deg, #1a2a2a 0%, #151515 100%); padding: 12px; border-radius: 6px; margin-bottom: 12px; }
.alfa-vault-total-label { font-size: 10px; text-transform: uppercase; color: #888; display: block; margin-bottom: 2px; }
.alfa-vault-total-value { font-size: 20px; font-weight: bold; color: #8bc34a; }
.alfa-vault-meta { font-size: 11px; color: #666; }
.alfa-vault-breakdown { display: flex; flex-direction: column; gap: 6px; margin-bottom: 12px; max-height: 200px; overflow-y: auto; }
.vault-stock-row { background: #1a1a1a; padding: 8px 10px; border-radius: 4px; border: 1px solid #252525; transition: border-color 0.2s; }
.vault-stock-row:hover { border-color: #444; }
.vault-stock-info { display: flex; align-items: center; gap: 8px; margin-bottom: 4px; }
.vault-stock-name { font-weight: bold; color: #fff; min-width: 35px; }
.vault-swing-badge { font-size: 10px; cursor: help; }
.vault-stock-value { color: #8bc34a; font-weight: bold; }
.vault-stock-pct { color: #666; font-size: 10px; }
.vault-signal-badge { font-size: 9px; padding: 1px 5px; border-radius: 3px; border: 1px solid; font-weight: bold; }
.vault-stock-bar-container { height: 6px; background: #252525; border-radius: 3px; overflow: hidden; margin-bottom: 4px; }
.vault-stock-bar { height: 100%; background: linear-gradient(90deg, var(--hf-accent, #caa14a), #4a7a7a); border-radius: 3px; position: relative; min-width: 2px; transition: width 0.3s; }
.vault-stock-bar-loose { position: absolute; right: 0; top: 0; height: 100%; background: rgba(139, 195, 74, 0.5); }
.vault-stock-details { display: flex; gap: 10px; font-size: 10px; }
.vault-dip-neg { color: #ef5350; }
.vault-dip-pos { color: #8bc34a; }
.vault-rsi { color: #888; }
.vault-rsi.oversold { color: #66bb6a; font-weight: bold; }
.vault-rsi.overbought { color: #ef5350; font-weight: bold; }
.vault-loose { color: #81c784; }
.alfa-vault-controls { display: flex; flex-wrap: wrap; gap: 15px; justify-content: space-between; padding: 10px 0; border-top: 1px solid #333; margin-bottom: 8px; }
.alfa-vault-control-group { display: flex; gap: 5px; align-items: center; }
@media (max-width: 600px) {
    .alfa-vault-controls { flex-direction: column; align-items: stretch; gap: 10px; }
    .alfa-vault-control-group { justify-content: flex-start; flex-wrap: wrap; }
    .alfa-vault-control-group:last-child { flex-wrap: wrap; }
    #vault-withdraw-amt { flex: 1; min-width: 60px; max-width: 120px; }
}
.alfa-vault-options { display: flex; gap: 20px; padding: 8px 0; border-bottom: 1px solid #333; margin-bottom: 10px; }
.vault-option-label { display: flex; align-items: center; gap: 6px; font-size: 11px; color: #888; cursor: pointer; }
.vault-option-label:hover { color: #fff; }
.vault-option-label input:checked + span { color: #8bc34a; }
.vault-spread-btn { border-color: #66bb6a !important; color: #66bb6a !important; }
.vault-spread-btn:hover { background: #66bb6a !important; color: #111 !important; }
.vault-withdraw-btn { border-color: #ef5350 !important; color: #ef5350 !important; }
.vault-withdraw-btn:hover { background: #ef5350 !important; color: #fff !important; }
/* --- SETTINGS MODAL CSS --- */
.settings-tabs { display: flex; gap: 5px; margin-bottom: 15px; border-bottom: 1px solid #333; padding-bottom: 10px; }
.settings-tab { background: transparent; border: 1px solid #444; color: #888; padding: 8px 16px; border-radius: 4px 4px 0 0; cursor: pointer; font-size: 11px; text-transform: uppercase; transition: all 0.2s; }
.settings-tab:hover { color: #fff; border-color: var(--hf-accent, #caa14a); }
.settings-tab.active { background: var(--hf-accent, #caa14a); color: #fff; border-color: var(--hf-accent, #caa14a); }
.settings-content { min-height: 300px; }
.settings-tab-content { animation: fadeIn 0.2s ease-out; }
.settings-section { background: #1a1a1a; border: 1px solid #333; border-radius: 6px; padding: 12px; margin-bottom: 12px; }
.settings-section-title { font-size: 11px; font-weight: bold; color: var(--hf-accent, #caa14a); text-transform: uppercase; margin-bottom: 10px; display: flex; justify-content: space-between; align-items: center; }
.settings-stock-count { font-size: 10px; color: #888; font-weight: normal; }
.settings-row { display: flex; gap: 10px; }
.settings-warning { background: #2a2a20; border: 1px solid #666633; border-radius: 4px; padding: 10px; font-size: 11px; color: #ffd54f; margin-top: 10px; }
.settings-api-status { text-align: center; font-size: 11px; padding: 8px; background: #151515; border-radius: 4px; }
.settings-checkbox-label { display: flex; align-items: flex-start; gap: 10px; padding: 8px; background: #151515; border-radius: 4px; margin-bottom: 8px; cursor: pointer; font-size: 11px; color: #ccc; }
.settings-checkbox-label:hover { background: #1a1a1a; }
.settings-checkbox-label input[type="checkbox"] { margin-top: 2px; }
.settings-checkbox-label strong { color: #fff; }
.settings-note { font-size: 10px; color: #666; margin-bottom: 8px; }
.settings-exclude-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 5px; max-height: 150px; overflow-y: auto; background: #151515; padding: 10px; border-radius: 4px; }
.settings-exclude-item { display: flex; align-items: center; gap: 5px; font-size: 10px; color: #888; cursor: pointer; }
.settings-exclude-item:hover { color: #fff; }
.settings-bank-inputs { display: flex; flex-direction: column; gap: 6px; }
.settings-actions { display: flex; gap: 10px; margin-top: 15px; padding-top: 15px; border-top: 1px solid #333; }
/* --- DAILY INCOME ROW CSS --- */
.alfa-vault-income-row { display: flex; align-items: center; gap: 8px; padding: 8px 12px; background: linear-gradient(135deg, #1a2520 0%, #151515 100%); border-radius: 6px; margin-bottom: 12px; font-size: 12px; }
.vault-income-icon { font-size: 14px; }
.vault-income-label { color: #888; }
.vault-income-value { color: #8bc34a; font-weight: bold; }
.vault-income-sep { color: #444; margin: 0 4px; }
.vault-income-bank { color: var(--hf-accent, #caa14a); font-size: 11px; }
/* --- BLOCK PROGRESS INDICATOR CSS --- */
/* Block Icon Styles */
.vault-block-icon { position: relative; display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; cursor: pointer; transition: all 0.2s; margin: 0 2px; }
.vault-block-icon .block-icon-shape { font-size: 18px; transition: all 0.2s; }
.vault-block-icon .block-tier-num { position: absolute; font-size: 8px; font-weight: bold; color: #fff; text-shadow: 0 0 2px #000; }
.vault-block-icon.no-block .block-icon-shape { color: #444; opacity: 0.5; }
.vault-block-icon.has-tier .block-icon-shape { color: var(--hf-accent, #caa14a); opacity: 1; }
.vault-block-icon.has-tier.close .block-icon-shape { color: #ffd54f; }
.vault-block-icon.close .block-icon-shape { color: #ffd54f; opacity: 0.8; }
.vault-block-icon.can-afford .block-icon-shape { color: #8bc34a; animation: blockIconPulse 1.5s ease-in-out infinite; }
.vault-block-icon.maxed .block-icon-shape { color: #8bc34a; opacity: 1; }
@keyframes blockIconPulse { 0%, 100% { filter: drop-shadow(0 0 4px rgba(139, 195, 74, 0.6)); transform: scale(1); } 50% { filter: drop-shadow(0 0 8px rgba(139, 195, 74, 0.9)); transform: scale(1.1); } }
.vault-block-icon:hover .block-icon-shape { transform: scale(1.15); }
.vault-block-progress { height: 3px; background: #252525; border-radius: 2px; margin: 2px 0 4px 0; overflow: hidden; }
.vault-block-progress-fill { height: 100%; background: linear-gradient(90deg, #444, var(--hf-accent, #caa14a)); border-radius: 2px; transition: width 0.3s; }
.vault-block-progress-fill.close { background: linear-gradient(90deg, #ffd54f, #ffb300); }
/* --- QUICK BLOCK MODAL CSS --- */
.quick-block-container { display: flex; flex-direction: column; gap: 15px; }
.quick-block-header { display: flex; justify-content: space-between; align-items: center; padding-bottom: 10px; border-bottom: 1px solid #333; }
.quick-block-symbol { font-size: 24px; font-weight: bold; color: #fff; }
.quick-block-type { font-size: 11px; color: #888; text-transform: uppercase; background: #252525; padding: 4px 8px; border-radius: 4px; }
.quick-block-status { display: flex; align-items: center; justify-content: center; gap: 20px; padding: 15px; background: #1a1a1a; border-radius: 8px; }
.quick-block-current, .quick-block-next { text-align: center; }
.quick-block-label { display: block; font-size: 10px; color: #666; text-transform: uppercase; margin-bottom: 4px; }
.quick-block-value { font-size: 16px; font-weight: bold; color: var(--hf-accent, #caa14a); }
.quick-block-value.maxed { color: #8bc34a; }
.quick-block-arrow { font-size: 20px; color: #444; }
.quick-block-progress-section { background: #151515; padding: 12px; border-radius: 6px; }
.quick-block-progress-header { display: flex; justify-content: space-between; font-size: 11px; color: #888; margin-bottom: 8px; }
.quick-block-progress-bar { height: 8px; background: #252525; border-radius: 4px; overflow: hidden; }
.quick-block-progress-fill { height: 100%; background: linear-gradient(90deg, var(--hf-accent, #caa14a), #4a7a7a); border-radius: 4px; transition: width 0.3s; }
.quick-block-progress-fill.close { background: linear-gradient(90deg, #ffd54f, #ffb300); }
.quick-block-shares { text-align: center; font-size: 11px; color: #666; margin-top: 8px; }
.quick-block-details { background: #1a1a1a; padding: 12px; border-radius: 6px; }
.quick-block-detail-row { display: flex; justify-content: space-between; padding: 6px 0; font-size: 12px; color: #ccc; border-bottom: 1px solid #252525; }
.quick-block-detail-row:last-child { border-bottom: none; }
.quick-block-detail-row.highlight { background: #1a2a1a; margin: 0 -12px; padding: 8px 12px; font-weight: bold; color: #fff; }
.quick-block-afford { text-align: center; font-size: 12px; padding: 10px; background: #151515; border-radius: 6px; }
.quick-block-actions { display: flex; gap: 10px; margin-top: 5px; }
.qb-btn-buy { flex: 1; border-color: #8bc34a !important; color: #8bc34a !important; }
.qb-btn-buy:disabled { border-color: #444 !important; color: #666 !important; }
.qb-btn-rebalance { flex: 1; border-color: #ffd54f !important; color: #ffd54f !important; }
.qb-btn-rebalance:disabled { border-color: #444 !important; color: #666 !important; }
.qb-btn-close { flex: 0 0 80px; background: #333 !important; border-color: #555 !important; color: #888 !important; }
.quick-block-rebalance-info { background: linear-gradient(135deg, #2a2a1a 0%, #1a1a15 100%); border: 1px solid #554422; border-radius: 6px; padding: 12px; }
.qb-rebalance-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; font-size: 12px; font-weight: bold; color: #ffd54f; }
.qb-rebalance-total { font-size: 11px; color: #fff; background: #332211; padding: 2px 8px; border-radius: 4px; }
.qb-rebalance-detail { font-size: 10px; color: #888; margin-bottom: 8px; }
.qb-rebalance-funds { display: flex; align-items: center; justify-content: center; gap: 8px; font-size: 11px; color: #ccc; background: #1a1a1a; padding: 8px; border-radius: 4px; }
.alfa-vault-status { text-align: center; font-size: 11px; padding: 5px 0; min-height: 18px; }

/* --- VAULT CONFIG MODAL CSS --- */
.vault-config-container { display: flex; flex-direction: column; gap: 15px; }
.vault-config-section { background: #1a1a1a; border-radius: 6px; padding: 12px; }
.vault-config-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
.vault-config-title { font-size: 11px; font-weight: bold; color: #888; text-transform: uppercase; }
.vault-stock-count { font-size: 11px; color: var(--hf-accent, #caa14a); }
.vault-stock-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px; max-height: 200px; overflow-y: auto; padding: 5px; background: #111; border-radius: 4px; }
.vault-stock-option { display: flex; align-items: center; gap: 5px; padding: 5px 8px; background: #1a1a1a; border: 1px solid #333; border-radius: 4px; cursor: pointer; transition: all 0.15s; }
.vault-stock-option:hover { border-color: #555; }
.vault-stock-option.selected { border-color: var(--hf-accent, #caa14a); background: var(--hf-accent, #caa14a)15; }
.vault-stock-option input { display: none; }
.vault-stock-sym { font-weight: bold; color: #fff; font-size: 11px; }
.vault-stock-type { font-size: 9px; color: #666; }
.vault-quick-actions { display: flex; gap: 8px; margin-top: 10px; justify-content: flex-end; }
.vault-config-row { display: flex; gap: 15px; }
.vault-strategy-desc { font-size: 10px; color: #666; margin-top: 6px; line-height: 1.3; }
.vault-config-actions { display: flex; gap: 10px; padding-top: 10px; border-top: 1px solid #333; }

/* --- VAULT ACTION BUTTONS CSS --- */
.vault-stock-actions { display: flex; gap: 4px; margin-left: auto; }
.vault-action-btn { background: transparent; border: 1px solid #444; color: #888; font-size: 9px; padding: 2px 6px; border-radius: 3px; cursor: pointer; transition: all 0.15s; }
.vault-action-btn:hover:not(:disabled) { border-color: var(--hf-accent, #caa14a); color: #fff; }
.vault-action-btn:disabled { opacity: 0.3; cursor: not-allowed; }
.vault-quick-buy:hover:not(:disabled) { border-color: #66bb6a; color: #66bb6a; }
.vault-quick-sell:hover:not(:disabled) { border-color: #ef5350; color: #ef5350; }
.vault-toggle-lock { font-size: 11px; padding: 1px 5px; }
.vault-toggle-lock.locked { border-color: #ffd54f; background: #ffd54f22; }
.vault-row-locked { border-color: #ffd54f44 !important; background: linear-gradient(90deg, #ffd54f08 0%, #1a1a1a 30%) !important; }
.vault-row-locked .vault-stock-name::after { content: ' \u{1F512}'; font-size: 9px; }


/* --- ANALYZE & REBALANCE BUTTON CSS --- */
.vault-analyze-btn { border-color: var(--hf-accent, #caa14a) !important; color: var(--hf-accent, #caa14a) !important; }
.vault-analyze-btn:hover { background: var(--hf-accent, #caa14a) !important; color: #fff !important; }
.vault-rebalance-btn { border-color: var(--hf-accent, #caa14a) !important; color: var(--hf-accent, #caa14a) !important; }
.vault-rebalance-btn:hover { background: var(--hf-accent, #caa14a) !important; color: #fff !important; }
.rebal-container { display: flex; flex-direction: column; gap: 15px; }
.rebal-summary { display: flex; justify-content: space-around; background: #1a1a1a; padding: 12px; border-radius: 6px; }
.rebal-summary-item { text-align: center; }
.rebal-summary-label { display: block; font-size: 10px; color: #888; text-transform: uppercase; margin-bottom: 4px; }
.rebal-summary-value { font-size: 14px; font-weight: bold; color: #fff; text-transform: capitalize; }
.rebal-section { background: #151515; border-radius: 6px; padding: 10px; }
.rebal-section-header { font-size: 12px; font-weight: bold; margin-bottom: 8px; padding-bottom: 6px; border-bottom: 1px solid #333; }
.rebal-table { display: flex; flex-direction: column; gap: 4px; max-height: 150px; overflow-y: auto; }
.rebal-row { display: flex; align-items: center; padding: 4px 8px; background: #1a1a1a; border-radius: 4px; font-size: 11px; gap: 8px; }
.rebal-row-executing { background: #2a2a1a; border: 1px solid #554422; }
.rebal-row-completed { background: #1a2a1a; opacity: 0.7; }
.rebal-row-failed { background: #2a1a1a; border: 1px solid #554422; }
.rebal-action-btn { background: #333; color: #fff; border: 1px solid #555; border-radius: 4px; padding: 2px 10px; font-size: 10px; font-weight: bold; cursor: pointer; transition: all 0.2s; min-width: 50px; }
.rebal-action-btn:hover:not(:disabled) { background: #444; border-color: var(--hf-accent, #caa14a); }
.rebal-action-btn:disabled { opacity: 0.5; cursor: not-allowed; }
.rebal-action-btn[data-type="sell"] { border-color: #ef5350; color: #ef5350; }
.rebal-action-btn[data-type="sell"]:hover:not(:disabled) { background: #ef5350; color: #fff; }
.rebal-action-btn[data-type="buy"] { border-color: #66bb6a; color: #66bb6a; }
.rebal-action-btn[data-type="buy"]:hover:not(:disabled) { background: #66bb6a; color: #fff; }
.rebal-sym { font-weight: bold; color: #fff; min-width: 40px; }
.rebal-signal { font-size: 9px; min-width: 55px; text-transform: uppercase; }
.rebal-reason { font-size: 9px; color: #666; flex: 1; }
.rebal-shares { min-width: 70px; text-align: right; color: #ccc; }
.rebal-value { min-width: 60px; text-align: right; font-weight: bold; }
.rebal-subtotal { text-align: right; font-size: 12px; font-weight: bold; margin-top: 8px; padding-top: 8px; border-top: 1px dashed #333; }
.rebal-warning { font-size: 10px; color: #888; text-align: center; padding: 8px; background: #1a1a1a; border-radius: 4px; }
.rebal-actions { display: flex; gap: 10px; padding-top: 10px; border-top: 1px solid #333; }

/* --- PORTFOLIO P&L CSS --- */
.alfa-vault-pl-section { background: #151515; border: 1px solid #333; border-radius: 6px; margin-bottom: 12px; overflow: hidden; }
.vault-pl-header { display: flex; justify-content: space-between; align-items: center; padding: 10px 12px; cursor: pointer; transition: background 0.2s; }
.vault-pl-header:hover { background: #1a1a1a; }
.vault-pl-title { font-size: 12px; font-weight: bold; color: var(--hf-accent, #caa14a); }
.vault-pl-content { padding: 0 12px 12px; }
.vault-pl-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin-bottom: 10px; }
.vault-pl-item { background: #1a1a1a; padding: 10px; border-radius: 6px; text-align: center; }
.vault-pl-item.pl-positive { border-left: 3px solid #8bc34a; }
.vault-pl-item.pl-negative { border-left: 3px solid #ef5350; }
.vault-pl-label { display: block; font-size: 10px; color: #888; text-transform: uppercase; margin-bottom: 4px; }
.vault-pl-value { display: block; font-size: 13px; font-weight: bold; color: #fff; }
.vault-pl-item.pl-positive .vault-pl-value { color: #8bc34a; }
.vault-pl-item.pl-negative .vault-pl-value { color: #ef5350; }
.vault-pl-actions { display: flex; gap: 8px; align-items: center; padding-top: 10px; border-top: 1px solid #333; }
.vault-pl-sync-info { font-size: 10px; color: #666; margin-left: auto; }
.vault-pl-empty { text-align: center; padding: 20px; color: #888; }
.vault-stock-pl { font-size: 10px; font-weight: bold; margin-left: 4px; }
.vault-pl-warning { background: #ff980022; border: 1px solid #ff980055; border-radius: 6px; padding: 10px; margin-bottom: 12px; font-size: 11px; color: #ffb74d; line-height: 1.4; }

/* Period Filter Buttons */
.vault-pl-period-filters { display: flex; gap: 5px; margin-bottom: 12px; flex-wrap: wrap; align-items: center; }
.pl-period-btn { background: transparent; border: 1px solid #444; color: #888; padding: 5px 12px; border-radius: 4px; cursor: pointer; font-size: 11px; transition: all 0.2s; }
.pl-period-btn:hover { border-color: var(--hf-accent, #caa14a); color: #fff; }
.pl-period-btn.active { background: var(--hf-accent, #caa14a); border-color: var(--hf-accent, #caa14a); color: #fff; }

/* Period Summary */
.vault-pl-period-summary { background: #1a1a1a; border-radius: 6px; padding: 12px; margin-bottom: 12px; }
.pl-period-title { font-size: 12px; font-weight: bold; color: var(--hf-accent, #caa14a); margin-bottom: 10px; }
.pl-period-stats { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; }
.pl-period-stat { display: flex; justify-content: space-between; align-items: center; padding: 6px 8px; background: #252525; border-radius: 4px; }
.pl-period-stat.pl-positive { border-left: 2px solid #8bc34a; }
.pl-period-stat.pl-negative { border-left: 2px solid #ef5350; }
.pl-stat-label { font-size: 10px; color: #888; text-transform: uppercase; }
.pl-stat-value { font-size: 12px; font-weight: bold; color: #fff; }
.pl-period-stat.pl-positive .pl-stat-value { color: #8bc34a; }
.pl-period-stat.pl-negative .pl-stat-value { color: #ef5350; }

.vault-pl-divider { border-top: 1px solid #333; margin: 12px 0; }
.vault-pl-section-title { font-size: 11px; color: #888; text-transform: uppercase; margin-bottom: 8px; font-weight: bold; }

/* --- GAMBLE MODULE CSS --- */
.alfa-gamble-section { background: #151515; border: 1px solid #333; border-radius: 6px; margin: 12px 0; }
.gamble-header { display: flex; justify-content: space-between; align-items: center; padding: 10px 12px; cursor: pointer; transition: background 0.2s; gap: 10px; }
.gamble-header:hover { background: #1a1a1a; }
.gamble-title { font-weight: bold; color: var(--hf-accent, #caa14a); font-size: 12px; }
.gamble-content { padding: 0 12px 12px; }
.gamble-target-row { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; flex-wrap: wrap; }
.gamble-target-label { font-size: 11px; color: #888; }
.gamble-target-select { flex: 1; min-width: 80px; max-width: 120px; }
.gamble-cash-row { font-size: 12px; color: #aaa; margin-bottom: 10px; }
.gamble-cash-label { margin-right: 6px; }
.gamble-cash-value { font-weight: bold; color: #8bc34a; }
.gamble-presets-row { display: flex; gap: 20px; margin-bottom: 12px; flex-wrap: wrap; }
.gamble-presets-group { flex: 1; min-width: 180px; }
.gamble-presets-label { font-size: 10px; color: #888; text-transform: uppercase; margin-bottom: 6px; }
.gamble-presets-btns { display: flex; flex-wrap: wrap; gap: 6px; }
.gamble-preset-btn { background: transparent; border: 1px solid #555; color: #888; padding: 6px 12px; border-radius: 4px; cursor: pointer; font-size: 11px; transition: all 0.2s; }
.gamble-preset-btn:hover { border-color: var(--hf-accent, #caa14a); color: #fff; }
.gamble-preset-btn.gamble-deposit:hover { border-color: #66bb6a; color: #66bb6a; }
.gamble-preset-btn.gamble-withdraw:hover { border-color: #ef5350; color: #ef5350; }
.gamble-panic-row { margin-top: 10px; padding-top: 10px; border-top: 1px solid #333; }
.gamble-panic-btn { width: 100%; background: #ef535022; border: 1px solid #ef5350; color: #ef5350; padding: 10px; border-radius: 6px; cursor: pointer; font-size: 12px; font-weight: bold; transition: all 0.2s; }
.gamble-panic-btn:hover { background: #ef5350; color: #fff; }
.gamble-status { font-size: 11px; margin-top: 8px; min-height: 20px; }
.gamble-config-modal { display: flex; flex-direction: column; gap: 15px; }
.gamble-config-section { display: flex; flex-direction: column; gap: 8px; }
.gamble-config-title { font-size: 12px; font-weight: bold; color: var(--hf-accent, #caa14a); }
.gamble-config-row { display: flex; gap: 10px; align-items: center; }
.gamble-config-hint { font-size: 10px; color: #666; }
.gamble-config-actions { display: flex; gap: 10px; }

/* --- SWING TRADING CSS --- */
.alfa-swing-section { background: #151515; border: 1px solid #333; border-radius: 6px; margin: 12px 0; }
.swing-header { display: flex; justify-content: space-between; align-items: center; padding: 10px 12px; cursor: pointer; transition: background 0.2s; }
.swing-header:hover { background: #1a1a1a; }
.swing-title { font-weight: bold; color: var(--hf-accent, #caa14a); font-size: 12px; }
.swing-content { padding: 0 12px 12px; overflow-x: hidden; }
.stocks-header { display: flex; justify-content: space-between; align-items: center; padding: 10px 12px; cursor: pointer; transition: background 0.2s; }
.stocks-header:hover { background: #1a1a1a; }
.stocks-title { font-weight: bold; color: var(--hf-accent, #caa14a); font-size: 12px; }
.stocks-content { padding: 0 12px 12px; overflow-x: hidden; }
.alfa-stocks-section { background: #151515; border: 1px solid #333; border-radius: 6px; margin: 12px 0; overflow: hidden; }

.swing-tabs { display: flex; gap: 5px; margin-bottom: 12px; border-bottom: 1px solid #333; padding-bottom: 10px; }
.swing-tab { background: transparent; border: 1px solid #444; color: #888; padding: 6px 12px; border-radius: 4px; cursor: pointer; font-size: 11px; transition: all 0.2s; }
.swing-tab:hover { border-color: var(--hf-accent, #caa14a); color: #fff; }
.swing-tab.active { background: var(--hf-accent, #caa14a); border-color: var(--hf-accent, #caa14a); color: #fff; }

.swing-tab-content { min-height: 100px; overflow-x: hidden; }
.swing-empty { text-align: center; padding: 30px 15px; color: #666; }
.swing-loading { text-align: center; padding: 20px; color: #888; }
.swing-error { text-align: center; padding: 20px; color: #ef5350; }

.swing-actions { display: flex; gap: 10px; margin-top: 12px; padding-top: 12px; border-top: 1px solid #333; }

/* Positions Summary */
.swing-positions-summary { display: flex; gap: 12px; margin-bottom: 12px; flex-wrap: wrap; }
.swing-summary-item { background: #252525; padding: 10px 15px; border-radius: 6px; text-align: center; flex: 1; min-width: 100px; }
.swing-summary-item.positive { border-left: 3px solid #8bc34a; }
.swing-summary-item.negative { border-left: 3px solid #ef5350; }
.swing-summary-label { display: block; font-size: 10px; color: #888; text-transform: uppercase; margin-bottom: 4px; }
.swing-summary-value { font-size: 14px; font-weight: bold; color: #fff; }
.swing-summary-item.positive .swing-summary-value { color: #8bc34a; }
.swing-summary-item.negative .swing-summary-value { color: #ef5350; }

/* Position Cards */
.swing-position { background: #1a1a1a; border: 1px solid #333; border-radius: 8px; padding: 12px; margin-bottom: 10px; transition: all 0.2s; }
.swing-position:hover { border-color: #555; }
.swing-position.target-hit { border-color: #8bc34a; background: #8bc34a11; }
.swing-position.stopped { border-color: #ef5350; background: #ef535011; }

.swing-pos-header { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 6px; margin-bottom: 8px; }
.swing-pos-title-group { display: flex; align-items: center; gap: 8px; }
.swing-pos-header-right { display: flex; align-items: center; gap: 8px; }
.swing-pos-symbol { font-size: 16px; font-weight: bold; color: #fff; }
.swing-pos-signal { font-size: 9px; font-weight: bold; padding: 2px 6px; border-radius: 4px; white-space: nowrap; }
.swing-signal-stale { opacity: 0.6; }
.swing-pos-pl { font-size: 14px; font-weight: bold; }
.swing-pos-pl.positive { color: #8bc34a; }
.swing-pos-pl.negative { color: #ef5350; }
.swing-pos-status { font-size: 16px; }

.swing-pos-details { font-size: 11px; color: #aaa; }
.swing-pos-row { display: flex; justify-content: space-between; margin-bottom: 6px; flex-wrap: wrap; gap: 5px; }
.swing-stop { color: #ef5350; }
.swing-target { color: #8bc34a; }

.swing-pos-progress { margin: 10px 0; }
.swing-progress-bar { height: 6px; background: linear-gradient(to right, #ef5350, #ffd54f 50%, #8bc34a); border-radius: 3px; position: relative; }
.swing-progress-fill { position: absolute; left: 0; top: 0; height: 100%; background: rgba(0,0,0,0.5); border-radius: 3px 0 0 3px; }
.swing-progress-marker { position: absolute; top: -3px; width: 4px; height: 12px; background: #fff; border-radius: 2px; transform: translateX(-50%); box-shadow: 0 0 5px rgba(0,0,0,0.5); }
.swing-progress-labels { display: flex; justify-content: space-between; font-size: 9px; color: #666; margin-top: 3px; }

.swing-pos-meta { display: flex; justify-content: space-between; font-size: 10px; color: #666; margin-top: 8px; }

.swing-pos-actions { display: flex; gap: 8px; margin-top: 10px; padding-top: 10px; border-top: 1px solid #333; }
.swing-close-btn, .swing-merge-btn, .swing-edit-btn { background: transparent; border: 1px solid #555; color: #888; padding: 5px 12px; border-radius: 4px; cursor: pointer; font-size: 10px; transition: all 0.2s; }
.swing-close-btn:hover { border-color: #ef5350; color: #ef5350; }
.swing-merge-btn:hover { border-color: var(--hf-accent, #caa14a); color: var(--hf-accent, #caa14a); }
.swing-edit-btn:hover { border-color: var(--hf-accent, #caa14a); color: var(--hf-accent, #caa14a); }

/* Signals */
.swing-signal-group { margin-bottom: 15px; }
.swing-signal-group-title { font-size: 12px; font-weight: bold; margin-bottom: 8px; padding-bottom: 5px; border-bottom: 1px solid #333; }

.swing-signal-card { background: #1a1a1a; border: 1px solid #333; border-radius: 8px; padding: 12px; margin-bottom: 8px; }
.swing-signal-card.buy { border-left: 3px solid #66bb6a; }
.swing-signal-card.sell { border-left: 3px solid #ef5350; }

.swing-signal-header { display: flex; align-items: center; gap: 10px; margin-bottom: 6px; flex-wrap: wrap; }
.swing-signal-symbol { font-size: 14px; font-weight: bold; color: #fff; }
.swing-signal-badge { padding: 3px 8px; border-radius: 12px; font-size: 10px; font-weight: bold; }
.swing-signal-score { font-size: 10px; color: #888; margin-left: auto; }
.swing-signal-price { font-size: 18px; font-weight: bold; color: #fff; margin-bottom: 8px; }

.swing-signal-reasons { display: flex; flex-direction: column; gap: 3px; margin-bottom: 8px; }
.swing-reason { font-size: 10px; padding: 3px 8px; border-radius: 4px; }
.swing-reason.bullish { background: #66bb6a22; color: #66bb6a; }
.swing-reason.bearish { background: #ef535022; color: #ef5350; }

.swing-signal-targets { display: flex; justify-content: space-between; font-size: 10px; color: #888; margin-bottom: 8px; }
.swing-signal-holding { font-size: 10px; color: #ffd54f; background: #ffd54f22; padding: 5px 10px; border-radius: 4px; margin-top: 5px; }

.swing-signal-actions { display: flex; gap: 8px; align-items: center; margin-top: 8px; flex-wrap: wrap; }
.swing-signal-actions .swing-signal-track-btn { flex: 1; min-width: 120px; }
.swing-signal-actions .swing-already-tracking { flex: 1; }
.swing-signal-track-btn { background: #66bb6a; border: none; color: #fff; padding: 8px 16px; border-radius: 4px; cursor: pointer; font-size: 11px; font-weight: bold; }
.swing-signal-track-btn:hover { background: #4caf50; }
.swing-already-tracking { font-size: 10px; color: #888; font-style: italic; }

/* Stats */
.swing-stats { margin-bottom: 15px; }
.swing-stats-empty { text-align: center; padding: 15px; color: #666; font-size: 11px; }
.swing-stats-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
.swing-stat-card { background: #1a1a1a; padding: 12px; border-radius: 6px; text-align: center; }
.swing-stat-card.positive { border-bottom: 2px solid #8bc34a; }
.swing-stat-card.negative { border-bottom: 2px solid #ef5350; }
.swing-stat-label { display: block; font-size: 10px; color: #888; text-transform: uppercase; margin-bottom: 4px; }
.swing-stat-value { font-size: 16px; font-weight: bold; color: #fff; }
.swing-stat-value.positive { color: #8bc34a; }
.swing-stat-value.negative { color: #ef5350; }
.swing-stat-sub { display: block; font-size: 10px; color: #666; margin-top: 3px; }

/* Journal */
.swing-journal-list { max-height: 400px; overflow-y: auto; }
.swing-journal-entry { background: #1a1a1a; border: 1px solid #333; border-radius: 6px; padding: 10px; margin-bottom: 8px; }
.swing-journal-entry.winner { border-left: 3px solid #8bc34a; }
.swing-journal-entry.loser { border-left: 3px solid #ef5350; }

.swing-journal-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 5px; }
.swing-journal-symbol { font-size: 13px; font-weight: bold; color: #fff; }
.swing-journal-pnl { font-size: 12px; font-weight: bold; }
.swing-journal-pnl.positive { color: #8bc34a; }
.swing-journal-pnl.negative { color: #ef5350; }

.swing-journal-details { display: flex; justify-content: space-between; font-size: 10px; color: #aaa; margin-bottom: 3px; }
.swing-journal-meta { display: flex; justify-content: space-between; font-size: 9px; color: #666; flex-wrap: wrap; gap: 5px; }
.swing-journal-header { position: relative; }
.swing-journal-header-right { display: flex; align-items: center; gap: 8px; }
.swing-journal-actions { display: flex; gap: 4px; }
.swing-journal-edit-btn, .swing-journal-full-edit-btn, .swing-journal-delete-btn { background: transparent; border: 1px solid #444; color: #888; padding: 2px 6px; border-radius: 4px; cursor: pointer; font-size: 11px; transition: all 0.2s; }
.swing-journal-edit-btn:hover, .swing-journal-full-edit-btn:hover { border-color: var(--hf-accent, #caa14a); color: var(--hf-accent, #caa14a); }
.swing-journal-delete-btn:hover { border-color: #ef5350; color: #ef5350; }
.swing-journal-tags { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 5px; }
.swing-journal-tag { background: #2a2a2a; color: var(--hf-accent, #caa14a); padding: 2px 8px; border-radius: 4px; font-size: 9px; }
.swing-journal-notes-preview { font-size: 10px; color: #888; margin-top: 4px; font-style: italic; }
.swing-journal-entry-editing .swing-journal-inline-edit { display: block; }
.swing-journal-inline-edit { display: flex; flex-direction: column; gap: 8px; }
.swing-journal-inline-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.swing-journal-inline-fields { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }
.swing-journal-inline-fields label { font-size: 10px; color: #888; }
.swing-inline-input { width: 70px; background: #1a1a1a; border: 1px solid #333; color: #fff; padding: 4px 8px; border-radius: 4px; font-size: 12px; }
.swing-journal-inline-actions { display: flex; gap: 8px; }
.swing-undo-banner { background: #2a3a2a; border: 1px solid #66bb6a; border-radius: 6px; padding: 10px 15px; margin-bottom: 10px; font-size: 12px; color: #8bc34a; display: flex; align-items: center; gap: 10px; }
.swing-undo-btn { background: #66bb6a; border: none; color: #fff; padding: 5px 12px; border-radius: 4px; cursor: pointer; font-size: 11px; font-weight: bold; }
.swing-undo-btn:hover { background: #7bc34a; }
.pos-merge-notice { background: #2a2a1a; border: 1px solid var(--hf-accent, #caa14a); border-radius: 6px; padding: 10px; margin-bottom: 10px; font-size: 12px; color: #aaa; }
.pos-merge-buttons { display: flex; gap: 8px; margin-top: 8px; }
.merge-existing-info { font-size: 11px; color: #888; margin-top: 5px; }
.merge-shares-context { background: #1a2a2a; border: 1px solid #333; border-radius: 6px; padding: 10px; font-size: 12px; color: #aaa; }
.merge-shares-context span { display: block; margin-bottom: 4px; }
.merge-shares-context span:last-child { margin-bottom: 0; }
.merge-no-loose { background: #2a2a1a; border: 1px solid #554422; border-radius: 6px; padding: 10px; font-size: 12px; color: #ffd54f; }
.merge-result-value { font-size: 13px; font-weight: bold; color: var(--hf-accent, #caa14a); margin-top: 5px; }

/* Add Position Modal */
.add-position-form { display: flex; flex-direction: column; gap: 15px; }
.form-group { display: flex; flex-direction: column; gap: 5px; }
.form-group label { font-size: 11px; color: #888; text-transform: uppercase; }
.form-row { display: flex; gap: 15px; }
.form-row .form-group { flex: 1; }
.form-hint { font-size: 10px; color: #666; }

.form-summary { background: #1a1a1a; padding: 15px; border-radius: 6px; }
.summary-row { display: flex; justify-content: space-between; margin-bottom: 8px; font-size: 12px; }
.summary-row:last-child { margin-bottom: 0; }
.summary-row span:first-child { color: #888; }
.summary-row span:last-child { font-weight: bold; color: #fff; }
.summary-row .positive { color: #8bc34a !important; }
.summary-row .negative { color: #ef5350 !important; }

.form-actions { display: flex; gap: 10px; }

.alfa-select { background: #1a1a1a; border: 1px solid #333; color: #fff; padding: 10px; border-radius: 6px; font-size: 13px; width: 100%; min-height: 40px; line-height: 1.4; box-sizing: border-box; }
.alfa-select:focus { border-color: var(--hf-accent, #caa14a); outline: none; }

/* --- TRANSACTION HISTORY MODAL CSS --- */
.tx-history-container { display: flex; flex-direction: column; gap: 15px; }
.tx-period-filter-row { display: flex; align-items: center; gap: 8px; padding-bottom: 10px; border-bottom: 1px solid #333; flex-wrap: wrap; }
.tx-period-label { font-size: 11px; color: #888; }
.tx-period-btn { background: transparent; border: 1px solid #444; color: #888; padding: 4px 10px; border-radius: 4px; cursor: pointer; font-size: 10px; transition: all 0.2s; }
.tx-period-btn:hover { border-color: var(--hf-accent, #caa14a); color: #fff; }
.tx-period-btn.active { background: var(--hf-accent, #caa14a); border-color: var(--hf-accent, #caa14a); color: #fff; }
.tx-period-badge { display: inline-block; background: var(--hf-accent, #caa14a)33; color: var(--hf-accent, #caa14a); padding: 4px 12px; border-radius: 12px; font-size: 11px; font-weight: bold; margin-bottom: 10px; }
.tx-tabs { display: flex; gap: 5px; border-bottom: 1px solid #333; padding-bottom: 10px; }
.tx-tab { background: transparent; border: 1px solid #444; color: #888; padding: 6px 15px; border-radius: 4px; cursor: pointer; font-size: 11px; transition: all 0.2s; }
.tx-tab:hover { border-color: var(--hf-accent, #caa14a); color: #fff; }
.tx-tab.active { background: var(--hf-accent, #caa14a); border-color: var(--hf-accent, #caa14a); color: #fff; }
.tx-tab-content { min-height: 200px; }
.tx-summary-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
.tx-summary-card { background: #1a1a1a; padding: 15px; border-radius: 8px; text-align: center; border: 1px solid #252525; }
.tx-summary-card.pl-positive { border-color: #8bc34a44; }
.tx-summary-card.pl-negative { border-color: #ef535044; }
.tx-summary-label { display: block; font-size: 10px; color: #888; text-transform: uppercase; margin-bottom: 6px; }
.tx-summary-value { display: block; font-size: 18px; font-weight: bold; color: #fff; }
.tx-summary-card.pl-positive .tx-summary-value { color: #8bc34a; }
.tx-summary-card.pl-negative .tx-summary-value { color: #ef5350; }
.tx-summary-sub { display: block; font-size: 11px; color: #666; margin-top: 4px; }
.tx-table { width: 100%; border-collapse: collapse; font-size: 11px; }
.tx-table th { text-align: left; padding: 8px; color: #888; border-bottom: 1px solid #333; font-weight: bold; text-transform: uppercase; font-size: 10px; position: sticky; top: 0; background: #1e1e1e; }
.tx-table td { padding: 8px; border-bottom: 1px solid #252525; }
.tx-table tbody tr:hover { background: #252525; }
.tx-actions { display: flex; gap: 10px; padding-top: 15px; border-top: 1px solid #333; }

/* --- QUICK BUY/SELL MODAL CSS --- */
.vault-trade-view { padding: 12px 0; }
.vault-trade-view-inner { display: flex; flex-direction: column; gap: 15px; }
.vault-trade-header { display: flex; justify-content: space-between; align-items: center; padding-bottom: 10px; border-bottom: 1px solid #333; }
.vault-trade-symbol { font-size: 20px; font-weight: bold; color: #fff; }
.vault-trade-price { font-size: 16px; color: #8bc34a; }
.quick-trade-container { display: flex; flex-direction: column; gap: 15px; }
.quick-trade-header { display: flex; justify-content: space-between; align-items: center; padding-bottom: 10px; border-bottom: 1px solid #333; }
.quick-trade-symbol { font-size: 24px; font-weight: bold; color: #fff; }
.quick-trade-price { font-size: 16px; font-weight: bold; color: var(--hf-accent, #caa14a); }
.quick-trade-info { background: #1a1a1a; padding: 12px; border-radius: 6px; }
.quick-trade-info-row { display: flex; justify-content: space-between; padding: 6px 0; font-size: 12px; color: #ccc; border-bottom: 1px solid #252525; }
.quick-trade-info-row:last-child { border-bottom: none; }
.quick-trade-protection { background: #2a2a1a; border: 1px solid #554422; border-radius: 6px; padding: 10px; margin-top: 5px; }
.quick-trade-protection-item { font-size: 11px; color: #ffd54f; margin-bottom: 5px; }
.quick-trade-protection-item:last-child { margin-bottom: 0; }
.quick-trade-input-section { display: flex; flex-direction: column; gap: 8px; }
.quick-trade-label { font-size: 11px; color: #888; text-transform: uppercase; }
.quick-trade-hint { font-size: 10px; color: #666; }
.quick-trade-calc { background: #151515; padding: 12px; border-radius: 6px; margin-top: 5px; }
.quick-trade-calc-row { display: flex; justify-content: space-between; padding: 6px 0; font-size: 12px; color: #ccc; border-bottom: 1px solid #252525; }
.quick-trade-calc-row:last-child { border-bottom: none; }
.quick-trade-calc-row.highlight { background: #1a2a1a; margin: 0 -12px; padding: 8px 12px; font-weight: bold; color: #fff; }
.quick-trade-warning { margin-top: 8px; padding: 8px; background: #2a1a1a; border-radius: 4px; font-size: 11px; }
.quick-trade-actions { display: flex; gap: 10px; margin-top: 5px; }
.qb-btn-sell { flex: 1; border-color: #ef5350 !important; color: #ef5350 !important; }
.qb-btn-sell:hover:not(:disabled) { background: #ef5350 !important; color: #fff !important; }
.qb-btn-sell:disabled { border-color: #444 !important; color: #666 !important; }
`;

  // src/stocks/styles.js
  var STYLE_ID2 = "hf-stocks-styles";
  function injectStocksStyles() {
    if (document.getElementById(STYLE_ID2)) {
      return;
    }
    const style = document.createElement("style");
    style.id = STYLE_ID2;
    style.textContent = `
    ${LEGACY_STYLES}

    .hf-stocks-root {
      font-family: var(--hf-font);
      color: var(--hf-text);
    }

    .hf-stocks-root,
    .alfa-modal-overlay.hf-torn {
      --hf-stock-card-bg: rgba(21, 22, 26, 0.92);
      --hf-stock-card-bg-strong: rgba(27, 29, 34, 0.96);
      --hf-stock-card-hover: rgba(202, 161, 74, 0.08);
      --hf-stock-border-soft: rgba(202, 161, 74, 0.16);
    }

    .hf-stocks-root .alfa-vault-section {
      background: transparent;
      border: none;
      padding: 0;
      margin-bottom: 0;
      font-family: var(--hf-font);
      color: var(--hf-text);
    }

    /* Space content away from collapsible module header border */
    .hf-stocks-root .vault-pl-content,
    .hf-stocks-root .gamble-content,
    .hf-stocks-root .swing-content,
    .hf-stocks-root .stocks-content {
      padding-top: 12px;
    }

    .hf-stocks-root .alfa-vault-empty,
    .hf-stocks-root .alfa-vault-summary,
    .hf-stocks-root .alfa-vault-income-row,
    .hf-stocks-root .alfa-vault-pl-section,
    .hf-stocks-root .alfa-gamble-section,
    .hf-stocks-root .alfa-swing-section,
    .hf-stocks-root .alfa-stocks-section,
    .hf-stocks-root .vault-stock-row,
    .hf-stocks-root .swing-position,
    .hf-stocks-root .swing-signal-card,
    .hf-stocks-root .swing-journal-entry,
    .hf-stocks-root .tx-summary-card,
    .hf-stocks-root .vault-pl-item,
    .hf-stocks-root .swing-summary-item,
    .hf-stocks-root .swing-stat-card,
    .hf-stocks-root .rebal-section,
    .hf-stocks-root .rebal-summary,
    .hf-stocks-root .quick-trade-info,
    .hf-stocks-root .quick-trade-calc {
      background: var(--hf-stock-card-bg);
      border: 1px solid var(--hf-panel-border);
      border-radius: var(--hf-radius);
      color: var(--hf-text);
      box-shadow: none;
    }

    .hf-stocks-root .alfa-vault-summary,
    .hf-stocks-root .alfa-vault-income-row {
      background: linear-gradient(180deg, rgba(32, 34, 40, 0.96), rgba(21, 22, 26, 0.96));
      border-color: var(--hf-stock-border-soft);
    }

    .hf-stocks-root .alfa-vault-total-value,
    .hf-stocks-root .vault-stock-value,
    .hf-stocks-root .vault-income-value,
    .hf-stocks-root .gamble-cash-value,
    .hf-stocks-root .quick-trade-price,
    .hf-stocks-root .vault-trade-price {
      color: var(--hf-success);
    }

    .hf-stocks-root .gamble-presets-group .gamble-cash-row {
      margin-bottom: 6px;
    }

    .hf-stocks-root .gamble-available-value {
      font-weight: 700;
      color: var(--hf-accent-text);
    }

    .hf-stocks-root .alfa-vault-total-label,
    .hf-stocks-root .vault-pl-label,
    .hf-stocks-root .swing-summary-label,
    .hf-stocks-root .swing-stat-label,
    .hf-stocks-root .quick-block-label,
    .hf-stocks-root .quick-trade-label,
    .hf-stocks-root .form-group label,
    .hf-stocks-root .tx-summary-label,
    .hf-stocks-root .pl-stat-label,
    .alfa-modal-overlay.hf-torn .form-group label,
    .alfa-modal-overlay.hf-torn .settings-section-title,
    .alfa-modal-overlay.hf-torn .vault-config-title {
      color: var(--hf-text-muted);
      font-size: 10px;
      font-weight: 700;
      letter-spacing: 0.07em;
      text-transform: uppercase;
    }

    .hf-stocks-root .vault-pl-title,
    .hf-stocks-root .gamble-title,
    .hf-stocks-root .swing-title,
    .hf-stocks-root .stocks-title,
    .hf-stocks-root .pl-period-title,
    .hf-stocks-root .swing-signal-group-title,
    .alfa-modal-overlay.hf-torn .settings-section-title,
    .alfa-modal-overlay.hf-torn .gamble-config-title,
    .alfa-modal-overlay.hf-torn .vault-config-title {
      color: var(--hf-accent-text);
    }

    .hf-stocks-root .vault-pl-header,
    .hf-stocks-root .gamble-header,
    .hf-stocks-root .swing-header,
    .hf-stocks-root .stocks-header {
      background: var(--hf-panel-header-bg);
      border-bottom: 1px solid var(--hf-panel-border);
    }

    .hf-stocks-root .alfa-vault-pl-section,
    .hf-stocks-root .alfa-gamble-section,
    .hf-stocks-root .alfa-swing-section,
    .hf-stocks-root .alfa-stocks-section {
      overflow: hidden;
    }

    .hf-stocks-root .vault-pl-header:hover,
    .hf-stocks-root .gamble-header:hover,
    .hf-stocks-root .swing-header:hover,
    .hf-stocks-root .stocks-header:hover,
    .hf-stocks-root .vault-stock-row:hover,
    .hf-stocks-root .swing-position:hover,
    .hf-stocks-root .swing-signal-card:hover,
    .hf-stocks-root .swing-journal-entry:hover {
      background: var(--hf-stock-card-hover);
      border-color: var(--hf-accent-muted);
    }

    .hf-stocks-root .alfa-vault-header,
    .hf-stocks-root .alfa-vault-title {
      display: none;
    }

    .hf-stocks-root .alfa-vault-control-group,
    .hf-stocks-root .vault-pl-actions,
    .hf-stocks-root .swing-actions,
    .hf-stocks-root .quick-block-actions,
    .hf-stocks-root .quick-trade-actions,
    .alfa-modal-overlay.hf-torn .form-actions,
    .alfa-modal-overlay.hf-torn .settings-actions,
    .alfa-modal-overlay.hf-torn .vault-config-actions,
    .alfa-modal-overlay.hf-torn .gamble-config-actions {
      gap: 8px;
    }

    /* Gamble Presets: Deposit | Withdraw side by side */
    .alfa-modal-overlay.hf-torn .gamble-config-modal {
      display: grid;
      grid-template-columns: 1fr 1fr;
      gap: 14px 20px;
      align-items: start;
    }

    .alfa-modal-overlay.hf-torn .gamble-config-hint,
    .alfa-modal-overlay.hf-torn .gamble-config-actions {
      grid-column: 1 / -1;
    }

    .alfa-modal-overlay.hf-torn .gamble-config-row {
      width: 100%;
    }

    .alfa-modal-overlay.hf-torn .gamble-config-label {
      width: 64px !important;
      flex-shrink: 0;
    }

    .alfa-modal-overlay.hf-torn .gamble-config-amount {
      flex: 1 1 auto !important;
      width: auto !important;
      min-width: 0;
    }

    @media (max-width: 560px) {
      .alfa-modal-overlay.hf-torn .gamble-config-modal {
        grid-template-columns: 1fr;
      }
    }

    .hf-stocks-root .alfa-main-btn,
    .hf-stocks-root .alfa-mini-btn,
    .hf-stocks-root .alfa-btn,
    .hf-stocks-root .pl-period-btn,
    .hf-stocks-root .swing-tab,
    .hf-stocks-root .tx-tab,
    .hf-stocks-root .tx-period-btn,
    .hf-stocks-root .gamble-preset-btn,
    .hf-stocks-root .vault-action-btn,
    .hf-stocks-root .rebal-action-btn,
    .hf-stocks-root .swing-close-btn,
    .hf-stocks-root .swing-merge-btn,
    .hf-stocks-root .swing-edit-btn,
    .hf-stocks-root .swing-signal-track-btn,
    .hf-stocks-root .swing-journal-edit-btn,
    .hf-stocks-root .swing-journal-full-edit-btn,
    .hf-stocks-root .swing-journal-delete-btn,
    .alfa-modal-overlay.hf-torn .alfa-main-btn,
    .alfa-modal-overlay.hf-torn .alfa-mini-btn,
    .alfa-modal-overlay.hf-torn .settings-tab,
    .alfa-modal-overlay.hf-torn .vault-action-btn,
    .alfa-modal-overlay.hf-torn .gamble-preset-btn {
      appearance: none;
      border: 1px solid var(--hf-panel-border);
      border-radius: 5px;
      background: var(--hf-surface);
      color: var(--hf-accent-text);
      cursor: pointer;
      font: inherit;
      font-size: 11px;
      font-weight: 700;
      min-height: 28px;
      padding: 5px 10px;
      text-transform: none;
      transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease, filter 0.15s ease;
    }

    .hf-stocks-root .alfa-mini-btn,
    .alfa-modal-overlay.hf-torn .alfa-mini-btn,
    .hf-stocks-root .vault-action-btn {
      margin-left: 0;
      min-height: 24px;
      padding: 3px 8px;
      font-size: 10px;
    }

    .hf-stocks-root .alfa-mini-btn:hover,
    .hf-stocks-root .alfa-btn:hover,
    .hf-stocks-root .alfa-main-btn:hover,
    .hf-stocks-root .pl-period-btn:hover,
    .hf-stocks-root .swing-tab:hover,
    .hf-stocks-root .tx-tab:hover,
    .hf-stocks-root .tx-period-btn:hover,
    .hf-stocks-root .gamble-preset-btn:hover,
    .hf-stocks-root .vault-action-btn:hover:not(:disabled),
    .hf-stocks-root .rebal-action-btn:hover:not(:disabled),
    .hf-stocks-root .swing-merge-btn:hover,
    .hf-stocks-root .swing-edit-btn:hover,
    .alfa-modal-overlay.hf-torn .alfa-main-btn:hover,
    .alfa-modal-overlay.hf-torn .alfa-mini-btn:hover,
    .alfa-modal-overlay.hf-torn .settings-tab:hover,
    .alfa-modal-overlay.hf-torn .vault-action-btn:hover:not(:disabled),
    .alfa-modal-overlay.hf-torn .gamble-preset-btn:hover {
      background: var(--hf-accent-subtle);
      border-color: var(--hf-hover);
      color: var(--hf-hover);
    }

    .hf-stocks-root .alfa-mini-btn.active,
    .hf-stocks-root .alfa-btn.active,
    .hf-stocks-root .pl-period-btn.active,
    .hf-stocks-root .swing-tab.active,
    .hf-stocks-root .tx-tab.active,
    .hf-stocks-root .tx-period-btn.active,
    .alfa-modal-overlay.hf-torn .settings-tab.active {
      background: var(--hf-accent);
      border-color: var(--hf-accent);
      color: #1b1d22;
    }

    .hf-stocks-root .vault-spread-btn,
    .hf-stocks-root .qb-btn-buy,
    .hf-stocks-root .swing-signal-track-btn,
    .alfa-modal-overlay.hf-torn .qb-btn-buy {
      background: var(--hf-success-bg);
      border-color: var(--hf-success-border) !important;
      color: var(--hf-success) !important;
    }

    .hf-stocks-root .vault-withdraw-btn,
    .hf-stocks-root .qb-btn-sell,
    .hf-stocks-root .swing-close-btn,
    .hf-stocks-root .swing-journal-delete-btn,
    .alfa-modal-overlay.hf-torn .qb-btn-sell {
      background: var(--hf-danger-bg);
      border-color: var(--hf-danger-border) !important;
      color: var(--hf-danger) !important;
    }

    .hf-stocks-root .vault-rebalance-btn,
    .hf-stocks-root .vault-analyze-btn,
    .hf-stocks-root .qb-btn-rebalance {
      background: var(--hf-warning-bg);
      border-color: var(--hf-warning-border) !important;
      color: var(--hf-warning) !important;
    }

    .hf-stocks-root button:disabled,
    .alfa-modal-overlay.hf-torn button:disabled {
      cursor: not-allowed;
      filter: grayscale(0.45);
      opacity: 0.55;
    }

    .hf-stocks-root .alfa-input,
    .hf-stocks-root .alfa-select,
    .hf-stocks-root .alfa-tbl-input,
    .hf-stocks-root .swing-inline-input,
    .hf-stocks-root textarea.alfa-input,
    .alfa-modal-overlay.hf-torn .alfa-input,
    .alfa-modal-overlay.hf-torn .alfa-select,
    .alfa-modal-overlay.hf-torn .alfa-tbl-input,
    .alfa-modal-overlay.hf-torn .swing-inline-input,
    .alfa-modal-overlay.hf-torn textarea.alfa-input {
      background: var(--hf-surface);
      border: 1px solid var(--hf-panel-border);
      border-radius: 5px;
      color: var(--hf-accent-text);
      font: inherit;
      min-height: 30px;
      padding: 6px 8px;
    }

    .hf-stocks-root .alfa-input:focus,
    .hf-stocks-root .alfa-select:focus,
    .hf-stocks-root .alfa-tbl-input:focus,
    .hf-stocks-root .swing-inline-input:focus,
    .alfa-modal-overlay.hf-torn .alfa-input:focus,
    .alfa-modal-overlay.hf-torn .alfa-select:focus,
    .alfa-modal-overlay.hf-torn .alfa-tbl-input:focus,
    .alfa-modal-overlay.hf-torn .swing-inline-input:focus {
      border-color: var(--hf-accent);
      box-shadow: 0 0 0 2px var(--hf-accent-subtle);
      outline: none;
    }

    .hf-stocks-root .vault-stock-bar-container,
    .hf-stocks-root .vault-block-progress,
    .hf-stocks-root .quick-block-progress-bar {
      background: rgba(0, 0, 0, 0.3);
      border: 1px solid rgba(255, 255, 255, 0.04);
    }

    .hf-stocks-root .vault-stock-bar,
    .hf-stocks-root .vault-block-progress-fill,
    .hf-stocks-root .quick-block-progress-fill {
      background: linear-gradient(90deg, var(--hf-accent), var(--hf-hover));
    }

    .hf-stocks-root .vault-row-locked {
      background: linear-gradient(90deg, rgba(255, 217, 102, 0.08), var(--hf-stock-card-bg) 38%) !important;
      border-color: var(--hf-warning-border) !important;
    }

    .hf-stocks-root .vault-pl-warning,
    .hf-stocks-root .settings-warning,
    .hf-stocks-root .quick-block-rebalance-info,
    .hf-stocks-root .quick-trade-protection,
    .hf-stocks-root .merge-no-loose,
    .alfa-modal-overlay.hf-torn .settings-warning,
    .alfa-modal-overlay.hf-torn .quick-block-rebalance-info,
    .alfa-modal-overlay.hf-torn .quick-trade-protection,
    .alfa-modal-overlay.hf-torn .merge-no-loose {
      background: var(--hf-warning-bg);
      border: 1px solid var(--hf-warning-border);
      border-radius: var(--hf-radius);
      color: var(--hf-warning);
    }

    .hf-stocks-root .settings-api-status,
    .hf-stocks-root .vault-stock-grid,
    .hf-stocks-root .settings-exclude-grid,
    .hf-stocks-root .settings-checkbox-label,
    .hf-stocks-root .vault-stock-option,
    .hf-stocks-root .quick-block-status,
    .hf-stocks-root .quick-block-progress-section,
    .hf-stocks-root .quick-block-details,
    .hf-stocks-root .quick-block-afford,
    .hf-stocks-root .form-summary,
    .alfa-modal-overlay.hf-torn .settings-api-status,
    .alfa-modal-overlay.hf-torn .vault-stock-grid,
    .alfa-modal-overlay.hf-torn .settings-exclude-grid,
    .alfa-modal-overlay.hf-torn .settings-checkbox-label,
    .alfa-modal-overlay.hf-torn .vault-stock-option,
    .alfa-modal-overlay.hf-torn .quick-block-status,
    .alfa-modal-overlay.hf-torn .quick-block-progress-section,
    .alfa-modal-overlay.hf-torn .quick-block-details,
    .alfa-modal-overlay.hf-torn .quick-block-afford,
    .alfa-modal-overlay.hf-torn .form-summary {
      background: var(--hf-stock-card-bg);
      border: 1px solid var(--hf-panel-border);
      border-radius: var(--hf-radius);
    }

    .hf-stocks-root .vault-stock-option.selected,
    .alfa-modal-overlay.hf-torn .vault-stock-option.selected {
      background: var(--hf-accent-subtle);
      border-color: var(--hf-accent);
    }

    .hf-stocks-root .vault-stock-name,
    .hf-stocks-root .swing-pos-symbol,
    .hf-stocks-root .swing-signal-symbol,
    .hf-stocks-root .swing-journal-symbol,
    .hf-stocks-root .quick-block-symbol,
    .hf-stocks-root .vault-trade-symbol,
    .hf-stocks-root .quick-trade-symbol,
    .alfa-modal-overlay.hf-torn .quick-block-symbol,
    .alfa-modal-overlay.hf-torn .vault-trade-symbol,
    .alfa-modal-overlay.hf-torn .quick-trade-symbol {
      color: var(--hf-accent-text);
    }

    .hf-stocks-root .alfa-table th,
    .hf-stocks-root .tx-table th,
    .alfa-modal-overlay.hf-torn .alfa-table th,
    .alfa-modal-overlay.hf-torn .tx-table th {
      background: var(--hf-panel-header-bg);
      border-bottom: 1px solid var(--hf-panel-border);
      color: var(--hf-text-muted);
    }

    .hf-stocks-root .alfa-table td,
    .hf-stocks-root .tx-table td,
    .alfa-modal-overlay.hf-torn .alfa-table td,
    .alfa-modal-overlay.hf-torn .tx-table td {
      border-bottom: 1px solid var(--hf-panel-border);
      color: var(--hf-text);
    }

    .hf-stocks-root .tx-table tbody tr:hover,
    .alfa-modal-overlay.hf-torn .tx-table tbody tr:hover {
      background: var(--hf-stock-card-hover);
    }

    .hf-stocks-root .alfa-hero:hover {
      border-color: var(--hf-accent);
    }

    .hf-stocks-root .alfa-overlay {
      z-index: 10050;
    }

    .hf-stocks-root .alfa-modal {
      font-family: var(--hf-font);
    }

    .alfa-modal-overlay.hf-torn {
      align-items: center;
      justify-content: center;
      background: rgba(0, 0, 0, 0.62);
      backdrop-filter: blur(2px);
      color: var(--hf-text);
      font-family: var(--hf-font);
    }

    .alfa-modal-overlay.hf-torn .alfa-modal {
      display: flex;
      flex-direction: column;
      width: min(680px, 94vw);
      max-height: min(86vh, 760px);
      overflow: hidden;
      background: var(--hf-panel-bg);
      border: 1px solid var(--hf-panel-border);
      border-radius: var(--hf-radius);
      box-shadow: var(--hf-shadow);
      color: var(--hf-text);
      font-family: var(--hf-font);
    }

    .alfa-modal-overlay.hf-torn .alfa-modal-header {
      display: flex;
      align-items: center;
      justify-content: space-between;
      gap: 12px;
      background: var(--hf-panel-header-bg);
      border-bottom: 1px solid var(--hf-panel-border);
      padding: 12px 14px;
      min-height: 48px;
      box-sizing: border-box;
    }

    .alfa-modal-overlay.hf-torn .alfa-modal-header h3 {
      margin: 0;
      padding: 0;
      color: var(--hf-accent-text);
      font-size: 14px;
      font-weight: 600;
      letter-spacing: 0.02em;
      line-height: 1.25;
      display: flex;
      align-items: center;
      gap: 6px;
      flex: 1;
      min-width: 0;
    }

    .alfa-modal-overlay.hf-torn .alfa-modal-close {
      display: inline-flex;
      align-items: center;
      justify-content: center;
      flex-shrink: 0;
      width: 28px;
      height: 28px;
      margin: 0;
      padding: 0;
      color: var(--hf-text-muted);
      font-size: 22px;
      line-height: 1;
    }

    .alfa-modal-overlay.hf-torn .alfa-modal-close:hover {
      color: var(--hf-hover);
    }

    .alfa-modal-overlay.hf-torn .alfa-modal-body {
      flex: 1 1 auto;
      min-height: 0;
      max-height: none; /* override legacy 80vh \u2014 was causing modal + body double scroll */
      overflow-y: auto;
      color: var(--hf-text);
      padding: 14px;
    }

    /* Rebalance / Spread / Withdraw layout
       Warnings/summary/actions stay content-sized; only .rebal-section grows + scrolls.
       (Previous grid 1fr row was assigned to the first warning \u2014 huge empty notice box.) */
    .alfa-modal-overlay.hf-torn .alfa-modal-body:has(.rebal-container) {
      display: flex;
      flex-direction: column;
      overflow: hidden;
    }

    .alfa-modal-overlay.hf-torn .rebal-container {
      flex: 1 1 auto;
      min-height: 0;
      display: flex;
      flex-direction: column;
      gap: 10px;
      overflow: hidden;
    }

    .alfa-modal-overlay.hf-torn .rebal-container > .rebal-summary,
    .alfa-modal-overlay.hf-torn .rebal-container > .rebal-warning,
    .alfa-modal-overlay.hf-torn .rebal-container > .rebal-actions,
    .alfa-modal-overlay.hf-torn .rebal-container > [id$="-progress"],
    .alfa-modal-overlay.hf-torn .rebal-container > [id$="-cash-received"] {
      flex: 0 0 auto;
    }

    .alfa-modal-overlay.hf-torn .rebal-warning {
      padding: 8px 10px;
      line-height: 1.35;
    }

    .alfa-modal-overlay.hf-torn .rebal-section {
      display: flex;
      flex-direction: column;
      flex: 1 1 auto;
      min-height: 160px;
      overflow: hidden;
    }

    .alfa-modal-overlay.hf-torn .rebal-section-header,
    .alfa-modal-overlay.hf-torn .rebal-subtotal {
      flex: 0 0 auto;
    }

    .alfa-modal-overlay.hf-torn .rebal-table {
      flex: 1 1 auto;
      min-height: 0;
      max-height: none;
      overflow-x: hidden;
      overflow-y: auto;
    }

    /* Portfolio rebalance: sell | buy side by side */
    .alfa-modal-overlay.hf-torn .rebal-container:has(> .rebal-section ~ .rebal-section) {
      display: grid;
      grid-template-columns: 1fr 1fr;
      grid-auto-rows: auto;
      align-content: stretch;
      gap: 10px;
    }

    .alfa-modal-overlay.hf-torn .rebal-container:has(> .rebal-section ~ .rebal-section) > .rebal-summary,
    .alfa-modal-overlay.hf-torn .rebal-container:has(> .rebal-section ~ .rebal-section) > .rebal-warning,
    .alfa-modal-overlay.hf-torn .rebal-container:has(> .rebal-section ~ .rebal-section) > .rebal-actions,
    .alfa-modal-overlay.hf-torn .rebal-container:has(> .rebal-section ~ .rebal-section) > [id$="-progress"],
    .alfa-modal-overlay.hf-torn .rebal-container:has(> .rebal-section ~ .rebal-section) > [id$="-cash-received"] {
      grid-column: 1 / -1;
    }

    .alfa-modal-overlay.hf-torn .rebal-container:has(> .rebal-section ~ .rebal-section) > .rebal-section {
      min-height: min(36vh, 260px);
      max-height: min(42vh, 320px);
      height: auto;
      flex: unset;
    }

    .alfa-modal-overlay.hf-torn .rebal-row {
      display: flex;
      align-items: center;
      gap: 6px;
      min-width: 0;
      overflow: hidden;
    }

    .alfa-modal-overlay.hf-torn .rebal-sym {
      flex: 0 0 36px;
      min-width: 36px;
    }

    .alfa-modal-overlay.hf-torn .rebal-signal {
      flex: 0 0 44px;
      min-width: 44px;
    }

    .alfa-modal-overlay.hf-torn .rebal-shares {
      flex: 1 1 auto;
      min-width: 0;
      text-align: right;
    }

    .alfa-modal-overlay.hf-torn .rebal-value {
      flex: 0 0 64px;
      min-width: 64px;
      text-align: right;
    }

    .alfa-modal-overlay.hf-torn .rebal-reason {
      flex: 0 0 16px;
      width: 16px;
      min-width: 16px;
      color: var(--hf-text-muted);
      font-size: 12px;
      line-height: 1;
      text-align: center;
    }

    .alfa-modal-overlay.hf-torn .rebal-reason:empty {
      display: none;
    }

    .alfa-modal-overlay.hf-torn .rebal-reason-tip {
      display: inline-block;
      cursor: help;
      color: var(--hf-accent-text);
      opacity: 0.85;
    }

    .alfa-modal-overlay.hf-torn .rebal-reason-tip:hover {
      opacity: 1;
      color: var(--hf-hover);
    }

    /* Mobile / PDA: modal body scrolls so Execute/Close stay reachable */
    @media (max-width: 560px) {
      .alfa-modal-overlay.hf-torn {
        align-items: flex-start;
        padding: 8px 0;
        overflow-y: auto;
        -webkit-overflow-scrolling: touch;
      }

      .alfa-modal-overlay.hf-torn .alfa-modal {
        width: min(680px, 96vw);
        max-height: min(92dvh, 92vh);
        margin: auto;
      }

      .alfa-modal-overlay.hf-torn .alfa-modal-body:has(.rebal-container) {
        overflow-x: hidden;
        overflow-y: auto;
        -webkit-overflow-scrolling: touch;
      }

      .alfa-modal-overlay.hf-torn .rebal-container {
        overflow: visible;
        flex: 0 0 auto;
        min-height: auto;
        height: auto;
      }

      .alfa-modal-overlay.hf-torn .rebal-container:has(> .rebal-section ~ .rebal-section) {
        display: flex;
        flex-direction: column;
        grid-template-columns: unset;
      }

      .alfa-modal-overlay.hf-torn .rebal-section,
      .alfa-modal-overlay.hf-torn .rebal-container:has(> .rebal-section ~ .rebal-section) > .rebal-section {
        flex: 0 0 auto;
        min-height: 0;
        max-height: none;
        height: auto;
        overflow: visible;
      }

      .alfa-modal-overlay.hf-torn .rebal-table {
        flex: 0 0 auto;
        max-height: min(40vw, 200px);
        overflow-y: auto;
      }

      .alfa-modal-overlay.hf-torn .rebal-actions {
        position: sticky;
        bottom: 0;
        z-index: 2;
        margin-top: 4px;
        padding-top: 10px;
        padding-bottom: 2px;
        background: var(--hf-panel-bg);
        flex-wrap: wrap;
      }

      .alfa-modal-overlay.hf-torn .rebal-actions .alfa-main-btn {
        flex: 1 1 auto;
        min-width: 120px;
      }
    }

    .alfa-modal-overlay.hf-torn .settings-tabs,
    .alfa-modal-overlay.hf-torn .swing-tabs,
    .alfa-modal-overlay.hf-torn .tx-tabs {
      border-bottom: 1px solid var(--hf-panel-border);
      gap: 6px;
      padding-bottom: 8px;
    }

    .alfa-modal-overlay.hf-torn .settings-section,
    .alfa-modal-overlay.hf-torn .vault-config-section,
    .alfa-modal-overlay.hf-torn .tx-summary-card,
    .alfa-modal-overlay.hf-torn .quick-block-container {
      color: var(--hf-text);
    }

    .alfa-modal-overlay.hf-torn .add-position-form {
      color: var(--hf-text);
      gap: 8px;
    }

    .alfa-modal-overlay.hf-torn .add-position-form .form-group {
      gap: 3px;
    }

    .alfa-modal-overlay.hf-torn .add-position-form .form-summary {
      padding: 10px 12px;
    }

    .alfa-modal-overlay.hf-torn .add-position-form #pos-notes.journal-notes-textarea {
      min-height: 44px;
    }

    .alfa-modal-overlay.hf-torn .pos-source-modes {
      margin: 0;
    }

    .alfa-modal-overlay.hf-torn .pos-source-options {
      display: flex;
      flex-wrap: wrap;
      gap: 6px 12px;
      margin-top: 4px;
    }

    .alfa-modal-overlay.hf-torn .pos-source-option,
    .alfa-modal-overlay.hf-torn .swing-sell-toggle {
      display: inline-flex;
      align-items: center;
      gap: 6px;
      font-size: 12px;
      color: var(--hf-text);
      cursor: pointer;
      user-select: none;
    }

    .alfa-modal-overlay.hf-torn .swing-sell-toggle {
      font-weight: 600;
    }

    /* Add Swing Position: compact custom stock dropdown (~half native height) */
    .alfa-modal-overlay.hf-torn .pos-symbol-wrap {
      position: relative;
    }

    .alfa-modal-overlay.hf-torn .pos-symbol-native {
      position: absolute !important;
      width: 1px !important;
      height: 1px !important;
      opacity: 0 !important;
      pointer-events: none !important;
      margin: 0 !important;
      padding: 0 !important;
      border: 0 !important;
    }

    .alfa-modal-overlay.hf-torn .pos-symbol-trigger {
      width: 100%;
      text-align: left;
      cursor: pointer;
    }

    .alfa-modal-overlay.hf-torn .pos-symbol-menu {
      position: absolute;
      z-index: 40;
      left: 0;
      right: 0;
      top: calc(100% + 4px);
      max-height: 200px;
      overflow-x: hidden;
      overflow-y: auto;
      background: var(--hf-stock-card-bg-strong);
      border: 1px solid var(--hf-panel-border);
      border-radius: 5px;
      box-shadow: var(--hf-shadow);
      padding: 4px;
    }

    .alfa-modal-overlay.hf-torn .pos-symbol-option {
      display: block;
      width: 100%;
      appearance: none;
      border: none;
      background: transparent;
      color: var(--hf-text);
      font: inherit;
      font-size: 12px;
      text-align: left;
      padding: 6px 8px;
      border-radius: 4px;
      cursor: pointer;
    }

    .alfa-modal-overlay.hf-torn .pos-symbol-option:hover,
    .alfa-modal-overlay.hf-torn .pos-symbol-option.is-selected {
      background: var(--hf-accent-subtle);
      color: var(--hf-accent-text);
    }

    #smart-stocks-panel.hf-panel--grow .hf-panel-body {
      padding-bottom: 16px;
    }

    .hf-stocks-root .alfa-vault-breakdown {
      max-height: 400px;
    }

    .hf-stocks-root #vault-main-content {
      overflow: visible;
    }
  `;
    document.head.appendChild(style);
  }

  // src/stocks/main.js
  var openVaultSettings = null;
  var panel = null;
  function buildMountRoot() {
    const root = document.createElement("div");
    root.id = "smart-stocks-root";
    root.className = "hf-stocks-root";
    return root;
  }
  async function init() {
    HF.theme.injectStyles();
    injectStocksStyles();
    panel = ui.createPanel({
      id: "smart-stocks-panel",
      badge: "Smart Stock Vault",
      mount: "stocksBar",
      pageBody: "grow",
      collapsedLegacyKeys: ["alfa_vault_collapsed"],
      onSettings: () => openVaultSettings?.(),
      tabs: [
        {
          id: "vault",
          label: "Vault",
          render: async () => buildMountRoot()
        }
      ]
    });
    panel.ensureMounted();
    bootstrapStocksApp({
      mountRoot: "#smart-stocks-root",
      registerSettings: (open) => {
        openVaultSettings = open;
      }
    });
  }
  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", init);
  } else {
    init();
  }
})();