✈️ Sets Tracker

Overseas sets companion — Plushies · Flowers · Prehistoric · Special · Xanax · Upgrade Planner — Torn

2026/03/13のページです。最新版はこちら

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

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

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name         ✈️ Sets Tracker
// @namespace    https://osdevscape.com
// @version      8.1.0
// @author       Phillip_J_Fry [2184575] (OSMays8338) — OSDevscape
// @license      All Rights Reserved © 2026 OSDevscape
// @homepageURL  https://greatest.deepsurf.us/users/OSMays8338
// @description  Overseas sets companion — Plushies · Flowers · Prehistoric · Special · Xanax · Upgrade Planner — Torn
// @match        https://www.torn.com/*
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_xmlhttpRequest
// @grant        unsafeWindow
// @connect      yata.yt
// @connect      api.torn.com
// @run-at       document-end
// ==/UserScript==

// ╔══════════════════════════════════════════════════════════════╗
// ║              ✈️  SETS TRACKER  v8.1.0                         ║
// ║                                                              ║
// ║  Author  :  Phillip_J_Fry [2184575] (Torn) · OSMays8338     ║
// ║  Company :  OSDevscape                                       ║
// ║  License :  All Rights Reserved © 2026 OSDevscape           ║
// ║                                                              ║
// ║  Based on "Points Museum" by SuperNovae [2637223]            ║
// ║  Rebuilt under OSDevscape suite architecture v7              ║
// ╚══════════════════════════════════════════════════════════════╝

(function () {
'use strict';

/* ─────────────────────────────────────────
   STORAGE — dual layer: localStorage + GM
   Mirrors Target Tracker / Race Tracker
───────────────────────────────────────── */
const store = {
    get(k, d) {
        try { const v = localStorage.getItem('lt_' + k); if (v !== null) return v; } catch(e) {}
        try { return GM_getValue(k, d); } catch(e) {}
        return d;
    },
    set(k, v) {
        try { localStorage.setItem('lt_' + k, v); } catch(e) {}
        try { GM_setValue(k, v); } catch(e) {}
    },
    getJSON(k, d) {
        try { const v = localStorage.getItem('lt_' + k); if (v !== null) return JSON.parse(v); } catch(e) {}
        try { return JSON.parse(GM_getValue(k, JSON.stringify(d))); } catch(e) {}
        return d;
    },
    setJSON(k, v) {
        const s = JSON.stringify(v);
        try { localStorage.setItem('lt_' + k, s); } catch(e) {}
        try { GM_setValue(k, s); } catch(e) {}
    }
};

/* ─────────────────────────────────────────
   CONFIG
───────────────────────────────────────── */
const cfg = {
    get apiKey()  { return store.get('lt_api', ''); },
    set apiKey(v) { store.set('lt_api', v); },
    get userId()  { return store.get('lt_uid', ''); },
    set userId(v) { store.set('lt_uid', v); },
    getSectionVis() { return store.getJSON('lt_sections', { prehistoric: true, plushies: true, flowers: true, special: true, xanax: true }); },
    setSectionVis(v){ store.setJSON('lt_sections', v); },
    getXanCount()   { return store.getJSON('lt_xan_count', 0); },
    setXanCount(v)  { store.setJSON('lt_xan_count', v); },
    getXanCarry()       { return store.getJSON('lt_xan_carry', 0); },
    setXanCarry(v)      { store.setJSON('lt_xan_carry', v); },
    getXanPriority()    { return store.getJSON('lt_xan_priority', false); },
    setXanPriority(v)   { store.setJSON('lt_xan_priority', v); },
    getXanThreshold()   { return store.getJSON('lt_xan_threshold', 50); },
    setXanThreshold(v)  { store.setJSON('lt_xan_threshold', v); },
    getXanRuns()        { return store.getJSON('lt_xan_runs', []); },
    setXanRuns(v)       { store.setJSON('lt_xan_runs', v); },
    getMuseumPin()      { return store.getJSON('lt_museum_pin', false); },
    setMuseumPin(v)     { store.setJSON('lt_museum_pin', v); },
};

/* ─────────────────────────────────────────
   TORN THEME DETECTION
   ─────────────────────────────────────────
   Torn signals light mode via one or more of:
     • document.body classList  → check TORN_LIGHT_CLASSES
     • document.documentElement → same list
     • data-theme / data-color-scheme attribute

   ⚠️  CONFIRM YOUR CLASS:
   Open DevTools → Elements → inspect <html> or <body>
   Switch Torn between light/dark and watch what class
   appears/disappears. Then add it to TORN_LIGHT_CLASSES.
───────────────────────────────────────── */
const TORN_LIGHT_CLASSES = [
    // Add / remove entries once you've confirmed via DevTools:
    'day-mode',         // most common Torn convention
    't-theme-day',
    'theme-light',
    'light-mode',
    'light',
    'daymode',
];

function isLightMode() {
    const targets = [document.documentElement, document.body];
    for (const el of targets) {
        if (!el) continue;
        for (const cls of TORN_LIGHT_CLASSES) {
            if (el.classList.contains(cls)) return true;
        }
        // data-theme / data-color-scheme attribute fallback
        const dt = el.getAttribute('data-theme') || el.getAttribute('data-color-scheme') || '';
        if (dt === 'light' || dt === 'day') return true;
    }
    return false;
}

/* ─────────────────────────────────────────
   COLOUR PALETTES  (dark = default)
───────────────────────────────────────── */
const DARK_PALETTE = {
    bg:        '#001214',
    bg2:       '#001e24',
    border:    'rgba(0,180,210,0.35)',
    gold:      '#00c8e0',
    goldDim:   'rgba(0,200,224,0.65)',
    goldGlow:  'rgba(0,200,224,0.10)',
    green:     '#8BC34A',
    greenDim:  'rgba(139,195,74,0.7)',
    text:      '#d0f0f8',
    textDim:   'rgba(140,220,235,0.55)',
    abroad:    '#7fe0f0',
    stockHi:   '#00ff00',
    stockMid:  '#ffa500',
    stockLo:   '#ff0000',
    okay:      '#66dd66',
    mono:      '"Share Tech Mono",Consolas,monospace',
    sans:      'Rajdhani,"Segoe UI",Arial,sans-serif',
    // settings popup extras
    settBg:    '#000e12',
    settBorder:'rgba(0,180,210,0.55)',
    settNote:  'rgba(0,80,100,0.3)',
    settHdr:   'rgba(0,80,110,0.3)',
};

const LIGHT_PALETTE = {
    bg:        '#f0fdff',
    bg2:       '#d8f6fc',
    border:    'rgba(0,140,170,0.3)',
    gold:      '#007a8f',       // darkened so it reads on white
    goldDim:   'rgba(0,140,170,0.75)',
    goldGlow:  'rgba(0,170,200,0.10)',
    green:     '#4a7c10',
    greenDim:  'rgba(60,110,15,0.75)',
    text:      '#001a22',
    textDim:   'rgba(0,80,100,0.55)',
    abroad:    '#007a9a',
    stockHi:   '#1a7a00',
    stockMid:  '#8a5500',
    stockLo:   '#bb0000',
    okay:      '#1a7a00',
    mono:      '"Share Tech Mono",Consolas,monospace',
    sans:      'Rajdhani,"Segoe UI",Arial,sans-serif',
    // settings popup extras
    settBg:    '#f0fdff',
    settBorder:'rgba(0,140,170,0.45)',
    settNote:  'rgba(0,170,200,0.15)',
    settHdr:   'rgba(0,160,190,0.15)',
};

// C is a live proxy — always reflects current Torn theme
let C = isLightMode() ? { ...LIGHT_PALETTE } : { ...DARK_PALETTE };

function syncTheme() {
    const light = isLightMode();
    const src   = light ? LIGHT_PALETTE : DARK_PALETTE;
    Object.assign(C, src);
}

// Watch for URL/page changes to trigger items page scrape
(function watchNavigation() {
    let lastHref = window.location.href;
    const navObs = new MutationObserver(() => {
        const href = window.location.href;
        if (href !== lastHref) { lastHref = href; watchItemsPage(); }
    });
    navObs.observe(document.body, { childList: true, subtree: true });
    // Also check on load in case we're already on items page
    watchItemsPage();
})();

// Watch <html> and <body> for class/attribute changes (user switches theme mid-session)
(function watchTheme() {
    const observer = new MutationObserver(() => {
        syncTheme();
        if (panelEl) renderPanel(); // re-render immediately on theme switch
    });
    const opts = { attributes: true, attributeFilter: ['class','data-theme','data-color-scheme'] };
    if (document.documentElement) observer.observe(document.documentElement, opts);
    if (document.body)            observer.observe(document.body, opts);
})();

/* ─────────────────────────────────────────
   POINTS & THRESHOLDS
───────────────────────────────────────── */
const PRE_PTS = 25, FLO_PTS = 10, PLU_PTS = 10, MET_PTS = 15, FOS_PTS = 20;
const PLU_THRESH = 2000, FLO_THRESH = 5000;
const XANAX_ID   = 206;
const POINTS_ENDPOINT = 'https://api.torn.com/v2/market/pointsmarket';

const POINTS_CACHE_DUR  = 300000;
const POINTS_HIST_SIZE  = 5;
let pointsPriceCache    = { time: 0, price: 0, history: [] };

/* ─────────────────────────────────────────
   LOCATION MAP
───────────────────────────────────────── */
const LOCATIONS = {
    'Mexico':         { flag: '🇲🇽', label: 'Mexico' },
    'Hawaii':         { flag: '🏝️',  label: 'Hawaii' },
    'South Africa':   { flag: '🇿🇦', label: 'South Africa' },
    'Japan':          { flag: '🇯🇵', label: 'Japan' },
    'China':          { flag: '🇨🇳', label: 'China' },
    'Argentina':      { flag: '🇦🇷', label: 'Argentina' },
    'Switzerland':    { flag: '🇨🇭', label: 'Switzerland' },
    'Canada':         { flag: '🇨🇦', label: 'Canada' },
    'UK':             { flag: '🇬🇧', label: 'United Kingdom' },
    'UAE':            { flag: '🇦🇪', label: 'UAE' },
    'Cayman Islands': { flag: '🇰🇾', label: 'Cayman Islands' },
    'BoB':            { flag: '🏪',  label: "Bits n' Bobs" },
};

/* ─────────────────────────────────────────
   ITEM GROUPS
───────────────────────────────────────── */
const GROUPS = {
    Prehistoric: {
        pts: PRE_PTS, icon: '🪨',
        items: {
            'Quartz Point':     { id: 619, s: 'Quartz',   loc: 'Canada'       },
            'Chalcedony Point': { id: 620, s: 'Chalced',  loc: 'Argentina'    },
            'Basalt Point':     { id: 621, s: 'Basalt',   loc: 'Hawaii'       },
            'Quartzite Point':  { id: 622, s: 'Quartzit', loc: 'South Africa' },
            'Chert Point':      { id: 623, s: 'Chert',    loc: 'UK'           },
            'Obsidian Point':   { id: 624, s: 'Obsidian', loc: 'Mexico'       },
        }
    },
    Plushies: {
        pts: PLU_PTS, icon: '🧸',
        items: {
            'Sheep Plushie':      { id: 186, s: 'Sheep',     loc: 'BoB'           },
            'Teddy Bear Plushie': { id: 187, s: 'Teddy',     loc: 'BoB'           },
            'Kitten Plushie':     { id: 215, s: 'Kitten',    loc: 'BoB'           },
            'Jaguar Plushie':     { id: 258, s: 'Jaguar',    loc: 'Mexico'        },
            'Wolverine Plushie':  { id: 261, s: 'Wolverine', loc: 'Canada'        },
            'Nessie Plushie':     { id: 266, s: 'Nessie',    loc: 'UK'            },
            'Red Fox Plushie':    { id: 268, s: 'Fox',       loc: 'UK'            },
            'Monkey Plushie':     { id: 269, s: 'Monkey',    loc: 'Argentina'     },
            'Chamois Plushie':    { id: 273, s: 'Chamois',   loc: 'Switzerland'   },
            'Panda Plushie':      { id: 274, s: 'Panda',     loc: 'China'         },
            'Lion Plushie':       { id: 281, s: 'Lion',      loc: 'South Africa'  },
            'Camel Plushie':      { id: 384, s: 'Camel',     loc: 'UAE'           },
            'Stingray Plushie':   { id: 618, s: 'Stingray',  loc: 'Cayman Islands'},
        }
    },
    Flowers: {
        pts: FLO_PTS, icon: '🌸',
        items: {
            'Dahlia':            { id: 260, s: 'Dahlia',    loc: 'Mexico'        },
            'Orchid':            { id: 264, s: 'Orchid',    loc: 'Hawaii'        },
            'African Violet':    { id: 282, s: 'Violet',    loc: 'South Africa'  },
            'Cherry Blossom':    { id: 277, s: 'Blossoms',  loc: 'Japan'         },
            'Peony':             { id: 276, s: 'Peony',     loc: 'China'         },
            'Ceibo Flower':      { id: 271, s: 'Ceibo',     loc: 'Argentina'     },
            'Edelweiss':         { id: 272, s: 'Edelweiss', loc: 'Switzerland'   },
            'Crocus':            { id: 263, s: 'Crocus',    loc: 'Canada'        },
            'Heather':           { id: 267, s: 'Heather',   loc: 'UK'            },
            'Tribulus Omanense': { id: 385, s: 'Tribulus',  loc: 'UAE'           },
            'Banana Orchid':     { id: 617, s: 'B.Orchid',  loc: 'Cayman Islands'},
        }
    },
};

const SPECIAL_ITEMS = {
    'Meteorite Fragment': { id: 512, s: 'Meteor', loc: 'Argentina', pts: MET_PTS },
    'Patagonian Fossil':  { id: 513, s: 'Fossil', loc: 'Argentina', pts: FOS_PTS },
};

const BOB_IDS = new Set([186, 187, 215]);
const itemImg = id => `https://www.torn.com/images/items/${id}/large.png`;

/* ─────────────────────────────────────────
   BUILD ID→NAME LOOKUP for YATA parsing
───────────────────────────────────────── */
function buildIdMap() {
    const m = {};
    Object.values(GROUPS).forEach(g => Object.entries(g.items).forEach(([name, d]) => { m[d.id] = name; }));
    Object.entries(SPECIAL_ITEMS).forEach(([name, d]) => { m[d.id] = name; });
    m[XANAX_ID] = 'Xanax';
    return m;
}
const ID_TO_NAME = buildIdMap();

/* ─────────────────────────────────────────
   STATE
───────────────────────────────────────── */
let toggleEl  = null;
let panelEl   = null;
let panelOpen = false;
let activeTab = 'sets';   // sets | xanax | travel
let isLoading = false;
let pollTimer = null;

// cached data
let invCache    = {};   // display items { name: qty }
let abroadCache = {};   // BoB shop stock { name: qty } via torn/?selections=shopsandstocks
let bobCache    = {};   // same source — BoB specific quantities for display
let xanSACache  = { qty: 0, price: 0 };
let xanPersonal = 0; // populated from items page scrape, persisted via cfg.setXanCount
let xanFacCache = null;
let pointsPrice = 0;

/* ─────────────────────────────────────────
   STYLESHEET — only keyframes & scrollbar
   All layout uses inline styles (CSP-safe)
───────────────────────────────────────── */
function injectCSS() {
    if (document.getElementById('lt-css')) return;
    const s = document.createElement('style');
    s.id = 'lt-css';
    s.textContent = `
#lt-toggle { position:fixed; z-index:999990; user-select:none; touch-action:none; cursor:grab; }
#lt-toggle:active { cursor:grabbing; }
#lt-panel  { position:fixed; z-index:999989; overflow:hidden; display:flex; flex-direction:column; }
#lt-panel .lt-body { overflow-y:auto; overflow-x:hidden; flex:1; scrollbar-width:thin; scrollbar-color:rgba(0,180,210,0.4) transparent; touch-action:pan-y; -webkit-overflow-scrolling:touch; }
#lt-panel .lt-body::-webkit-scrollbar { width:3px; }
#lt-panel .lt-body::-webkit-scrollbar-thumb { background:rgba(0,180,210,0.5); border-radius:2px; }
#lt-panel a { text-decoration:none !important; color:inherit; }
@keyframes lt-spin     { to{transform:rotate(360deg)} }
@keyframes lt-pulse    { 0%,100%{box-shadow:0 0 0 0 rgba(0,200,224,0.5)} 50%{box-shadow:0 0 0 5px rgba(0,200,224,0)} }
@keyframes lt-hi-pulse { 0%,100%{box-shadow:0 0 0 0 rgba(0,255,0,0.4)}   50%{box-shadow:0 0 0 3px rgba(0,255,0,0)} }
@keyframes lt-wa-pulse { 0%,100%{box-shadow:0 0 0 0 rgba(255,165,0,0.4)} 50%{box-shadow:0 0 0 3px rgba(255,165,0,0)} }
@keyframes lt-da-pulse { 0%,100%{box-shadow:0 0 0 0 rgba(255,0,0,0.4)}   50%{box-shadow:0 0 0 3px rgba(255,0,0,0)} }
@keyframes lt-float    { 0%,100%{transform:translateY(0)} 50%{transform:translateY(-2px)} }
.lt-spin   { animation:lt-spin  0.8s linear     infinite; display:inline-block; }
.lt-float  { animation:lt-float 3s   ease-in-out infinite; }
.lt-hi     { animation:lt-hi-pulse 2s ease-in-out infinite; }
.lt-wa     { animation:lt-wa-pulse 2s ease-in-out infinite; }
.lt-da     { animation:lt-da-pulse 2s ease-in-out infinite; }
`;
    (document.head || document.documentElement).appendChild(s);
}

/* ─────────────────────────────────────────
   TOAST
───────────────────────────────────────── */
function toast(msg, dur) {
    dur = dur || 3000;
    const old = document.getElementById('lt-toast'); if (old) old.remove();
    const el = document.createElement('div');
    el.id = 'lt-toast'; el.textContent = msg;
    el.setAttribute('style',
        'position:fixed !important;top:18px !important;left:50% !important;' +
        'transform:translateX(-50%) !important;background:#00121a !important;' +
        'border:1px solid rgba(0,200,224,0.7) !important;border-radius:8px !important;' +
        'padding:10px 20px !important;color:#00c8e0 !important;font-size:13px !important;' +
        'font-weight:700 !important;font-family:Arial,sans-serif !important;' +
        'z-index:2147483647 !important;max-width:360px !important;text-align:center !important;' +
        'box-shadow:0 4px 20px rgba(0,0,0,0.8) !important;pointer-events:none !important;'
    );
    document.body.appendChild(el);
    setTimeout(() => {
        el.style.setProperty('opacity','0','important');
        el.style.setProperty('transition','opacity 0.3s','important');
        setTimeout(() => el.remove(), 320);
    }, dur);
}

/* ─────────────────────────────────────────
   SETTINGS POPUP — fully inline, CSP-safe
───────────────────────────────────────── */
function openSettings() {
    const existing = document.getElementById('lt-settings-wrap');
    if (existing) { existing.remove(); return; }

    function el(tag, css) { const e = document.createElement(tag); if (css) e.setAttribute('style', css); return e; }
    function imp(css) { return css.split(';').filter(Boolean).map(r => r.trim() + ' !important').join(';') + ';'; }

    const S = {
        wrap:   'position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.85);z-index:2147483647;display:flex;align-items:flex-start;justify-content:center;padding-top:5vh;box-sizing:border-box;overflow-y:auto;',
        box:    `background:${C.settBg};border:2px solid ${C.settBorder};border-radius:12px;width:370px;max-width:94vw;color:${C.text};font-family:Arial,sans-serif;overflow:hidden;box-shadow:0 16px 60px rgba(0,0,0,0.85);margin-bottom:20px;`,
        hdr:    `background:${C.settHdr};padding:13px 18px;font-size:12px;font-weight:700;letter-spacing:1.4px;color:${C.gold};border-bottom:1px solid ${C.border};`,
        body:   'padding:16px;',
        note:   `background:${C.settNote};border-left:3px solid ${C.border};padding:9px 12px;border-radius:4px;font-size:11px;color:${C.goldDim};margin-bottom:16px;line-height:1.5;`,
        secHdr: `font-size:9px;font-weight:700;letter-spacing:1.2px;text-transform:uppercase;color:${C.goldDim};padding-bottom:5px;margin:16px 0 8px;border-bottom:1px solid ${C.border};font-family:Consolas,monospace;`,
        lbl:    `display:block;margin-bottom:5px;font-size:10px;font-weight:700;letter-spacing:1px;text-transform:uppercase;color:${C.green};`,
        inp:    `display:block;width:100%;box-sizing:border-box;padding:9px 11px;background:${C.bg};border:1px solid ${C.border};border-radius:5px;color:${C.text};font-size:13px;font-family:Consolas,monospace;outline:none;`,
        hint:   `font-size:9px;color:${C.textDim};margin-top:4px;font-family:Consolas,monospace;`,
        btnRow: 'display:flex;gap:8px;margin-top:18px;',
        btn:    `flex:1;padding:10px 0;border-radius:7px;font-size:12px;font-weight:700;cursor:pointer;border:1px solid ${C.border};font-family:Arial,sans-serif;letter-spacing:0.5px;`,
    };

    const wrap = el('div'); wrap.id = 'lt-settings-wrap'; wrap.setAttribute('style', imp(S.wrap));
    const box  = el('div'); box.setAttribute('style', imp(S.box));
    const hdr  = el('div'); hdr.textContent = '⚙  LOOT TRACKER — Settings'; hdr.setAttribute('style', imp(S.hdr));
    const body = el('div'); body.setAttribute('style', imp(S.body));

    const note = el('div');
    note.innerHTML = '&#128274; Requires a <b>Limited Access</b> API key (Display permission only). Your API key and all item data stay on your device — nothing is transmitted externally.';
    note.setAttribute('style', imp(S.note));

    function field(labelText, id, placeholder, value, hint, isSecret) {
        const g = el('div'); g.setAttribute('style', imp('margin-bottom:12px;'));
        const lbl = el('label'); lbl.textContent = labelText; lbl.setAttribute('style', imp(S.lbl));
        const inp = document.createElement('input');
        inp.setAttribute('type', isSecret ? 'password' : 'text');
        inp.id = id; inp.placeholder = placeholder; inp.value = value || '';
        inp.setAttribute('style', imp(S.inp));
        inp.addEventListener('focus', () => inp.style.setProperty('border-color','rgba(0,190,215,0.8)','important'));
        inp.addEventListener('blur',  () => inp.style.setProperty('border-color','rgba(0,140,170,0.4)','important'));
        if (isSecret) {
            const row = el('div'); row.setAttribute('style', imp('display:flex;align-items:center;gap:6px;'));
            const tog = el('div'); tog.textContent = '👁'; tog.setAttribute('style', imp('cursor:pointer;font-size:14px;flex-shrink:0;opacity:0.45;user-select:none;padding:2px;'));
            tog.addEventListener('click', () => {
                const shown = inp.getAttribute('type') === 'text';
                inp.setAttribute('type', shown ? 'password' : 'text');
                tog.style.setProperty('opacity', shown ? '0.45' : '1', 'important');
            });
            row.appendChild(inp); row.appendChild(tog);
            const h = el('div'); h.textContent = hint; h.setAttribute('style', imp(S.hint));
            g.appendChild(lbl); g.appendChild(row); g.appendChild(h);
        } else {
            const h = el('div'); h.textContent = hint; h.setAttribute('style', imp(S.hint));
            g.appendChild(lbl); g.appendChild(inp); g.appendChild(h);
        }
        return g;
    }

    // ── Credentials ──
    const credHdr = el('div'); credHdr.textContent = '🔑 Credentials'; credHdr.setAttribute('style', imp(S.secHdr));
    const fAPI = field('API Key (Limited — Display only)', 'lt-si-api', 'Enter your Torn API key', cfg.apiKey, 'Torn → Settings → API → Limited (Display access)', true);
    const fUID = field('Your User ID', 'lt-si-uid', 'Enter your Torn player ID', cfg.userId, 'Used to fetch your display items from the API');

    const apiLink = el('div');
    apiLink.setAttribute('style', imp('margin-top:-8px;margin-bottom:12px;font-size:10px;color:rgba(0,170,200,0.75);'));
    apiLink.innerHTML = '🔑 No key? <a href="https://www.torn.com/preferences.php#tab=api?step=addNewKey&title=LootTracker&access_level=1" style="color:#00c8e0;font-weight:700;text-decoration:underline !important;">Create a Limited (Display) key on Torn</a>';

    // ── Section Visibility ──
    const secHdr = el('div'); secHdr.textContent = '👁 Section Visibility'; secHdr.setAttribute('style', imp(S.secHdr));

    const vis = cfg.getSectionVis();
    const sections = [
        { key: 'prehistoric', label: '🪨 Prehistoric Points' },
        { key: 'plushies',    label: '🧸 Plushies'           },
        { key: 'flowers',     label: '🌸 Flowers'            },
        { key: 'special',     label: '☄️ Special Items'     },
        { key: 'xanax',       label: '🧪 Xanax'             },
    ];

    const visGrid = el('div'); visGrid.setAttribute('style', imp('display:flex;flex-direction:column;gap:4px;'));
    sections.forEach(sec => {
        const row = el('div'); row.setAttribute('style', imp('display:flex;align-items:center;gap:8px;padding:5px 8px;border-radius:5px;background:rgba(0,200,224,0.04);border:1px solid rgba(0,140,170,0.18);cursor:pointer;'));
        const chk = document.createElement('input'); chk.type = 'checkbox'; chk.id = 'lt-vis-' + sec.key; chk.checked = vis[sec.key] !== false;
        chk.setAttribute('style', imp('width:14px;height:14px;cursor:pointer;accent-color:#00c8e0;flex-shrink:0;'));
        const lbl = el('label'); lbl.textContent = sec.label; lbl.setAttribute('for', 'lt-vis-' + sec.key);
        lbl.setAttribute('style', imp('font-size:10px;font-weight:600;color:#e8f0d0;cursor:pointer;flex:1;'));
        row.appendChild(chk); row.appendChild(lbl);
        row.addEventListener('click', e => { if (e.target !== chk) chk.checked = !chk.checked; });
        visGrid.appendChild(row);
    });

    // ── Preferences ──
    const prefHdr = el('div'); prefHdr.textContent = '💡 Preferences'; prefHdr.setAttribute('style', imp(S.secHdr));

    const tooltipRow = el('div');
    tooltipRow.setAttribute('style', imp('display:flex;align-items:center;gap:8px;padding:5px 8px;border-radius:5px;background:rgba(0,200,224,0.04);border:1px solid rgba(0,140,170,0.18);cursor:pointer;'));
    const tooltipChk = document.createElement('input'); tooltipChk.type = 'checkbox'; tooltipChk.id = 'lt-pref-tooltip';
    let tooltipAlreadySeen = false;
    try { tooltipAlreadySeen = !!localStorage.getItem('lt_tooltip_seen'); } catch(e) {}
    tooltipChk.checked = !tooltipAlreadySeen;
    tooltipChk.setAttribute('style', imp('width:14px;height:14px;cursor:pointer;accent-color:#00c8e0;flex-shrink:0;'));
    const tooltipLbl = el('label'); tooltipLbl.textContent = '✈️ Show welcome tooltip on next load'; tooltipLbl.setAttribute('for', 'lt-pref-tooltip');
    tooltipLbl.setAttribute('style', imp('font-size:10px;font-weight:600;color:#e8f0d0;cursor:pointer;flex:1;'));
    tooltipRow.appendChild(tooltipChk); tooltipRow.appendChild(tooltipLbl);
    tooltipRow.addEventListener('click', e => { if (e.target !== tooltipChk) tooltipChk.checked = !tooltipChk.checked; });

    // ── 🧪 Xanax ──
    const xanSettHdr = el('div'); xanSettHdr.textContent = '🧪 Xanax'; xanSettHdr.setAttribute('style', imp(S.secHdr));

    // Adjust Count row
    const xanCarryGroup = el('div'); xanCarryGroup.setAttribute('style', imp('margin-bottom:10px;'));
    const xanCarryLbl = el('label'); xanCarryLbl.textContent = 'Carry Limit'; xanCarryLbl.setAttribute('for','lt-si-xan-carry'); xanCarryLbl.setAttribute('style', imp(S.lbl));
    const xanCarryRow = el('div'); xanCarryRow.setAttribute('style', imp('display:flex;align-items:center;gap:6px;'));
    const xanCarDecBtn = el('button'); xanCarDecBtn.textContent = '−'; xanCarDecBtn.setAttribute('type','button');
    xanCarDecBtn.setAttribute('style', imp('padding:6px 12px;border-radius:5px;border:1px solid rgba(0,200,224,0.35);background:rgba(0,40,60,0.5);color:#00c8e0;font-weight:800;font-size:15px;cursor:pointer;line-height:1;'));
    const xanCarryInp = document.createElement('input'); xanCarryInp.type = 'number'; xanCarryInp.min = '0';
    xanCarryInp.id = 'lt-si-xan-carry'; xanCarryInp.value = cfg.getXanCarry();
    xanCarryInp.setAttribute('style', imp('font-family:Consolas,monospace;font-size:14px;font-weight:700;text-align:center;width:72px;padding:6px 4px;background:#000e14;border:1px solid rgba(0,140,170,0.4);border-radius:5px;color:#e8f0d0;outline:none;-moz-appearance:textfield;'));
    xanCarryInp.addEventListener('focus', () => xanCarryInp.style.setProperty('border-color','rgba(0,190,215,0.8)','important'));
    xanCarryInp.addEventListener('blur',  () => xanCarryInp.style.setProperty('border-color','rgba(0,140,170,0.4)','important'));
    const xanCarIncBtn = el('button'); xanCarIncBtn.textContent = '+'; xanCarIncBtn.setAttribute('type','button');
    xanCarIncBtn.setAttribute('style', imp('padding:6px 12px;border-radius:5px;border:1px solid rgba(0,200,224,0.35);background:rgba(0,40,60,0.5);color:#00c8e0;font-weight:800;font-size:15px;cursor:pointer;line-height:1;'));
    xanCarDecBtn.addEventListener('click', () => { xanCarryInp.value = Math.max(0,(parseInt(xanCarryInp.value)||0)-1); });
    xanCarIncBtn.addEventListener('click', () => { xanCarryInp.value = (parseInt(xanCarryInp.value)||0)+1; });
    xanCarryRow.appendChild(xanCarDecBtn); xanCarryRow.appendChild(xanCarryInp); xanCarryRow.appendChild(xanCarIncBtn);
    const xanCarryHint = el('div'); xanCarryHint.textContent = 'Max Xanax you can carry per trip'; xanCarryHint.setAttribute('style', imp(S.hint));
    xanCarryGroup.appendChild(xanCarryLbl); xanCarryGroup.appendChild(xanCarryRow); xanCarryGroup.appendChild(xanCarryHint);

    // Xanax Priority section
    const xanPriRowWrap = el('div'); xanPriRowWrap.setAttribute('style', imp('margin-bottom:8px;'));
    const xanPriRow = el('div'); xanPriRow.setAttribute('style', imp('display:flex;align-items:center;gap:8px;padding:5px 8px;border-radius:5px;background:rgba(0,200,224,0.04);border:1px solid rgba(0,140,170,0.18);cursor:pointer;'));
    const xanPriChk = document.createElement('input'); xanPriChk.type = 'checkbox'; xanPriChk.id = 'lt-si-xan-priority'; xanPriChk.checked = cfg.getXanPriority();
    xanPriChk.setAttribute('style', imp('width:14px;height:14px;cursor:pointer;accent-color:#00c8e0;flex-shrink:0;'));
    const xanPriLbl = el('label'); xanPriLbl.textContent = '🚨 Prioritise Xanax run in Travel planner'; xanPriLbl.setAttribute('for','lt-si-xan-priority');
    xanPriLbl.setAttribute('style', imp('font-size:10px;font-weight:600;color:#e8f0d0;cursor:pointer;flex:1;'));
    xanPriRow.appendChild(xanPriChk); xanPriRow.appendChild(xanPriLbl);
    xanPriRow.addEventListener('click', e => { if (e.target !== xanPriChk) xanPriChk.checked = !xanPriChk.checked; updateThresholdVis(); });
    xanPriChk.addEventListener('change', updateThresholdVis);
    xanPriRowWrap.appendChild(xanPriRow);

    // Threshold field — only visible when priority is on
    const xanThreshGroup = el('div'); xanThreshGroup.id = 'lt-xan-thresh-group';
    xanThreshGroup.setAttribute('style', imp('margin-top:6px;padding:8px 10px;background:rgba(0,200,224,0.03);border:1px solid rgba(0,140,170,0.2);border-radius:5px;' + (cfg.getXanPriority() ? '' : 'display:none;')));
    const xanThreshLbl = el('label'); xanThreshLbl.textContent = 'Stop prioritising above'; xanThreshLbl.setAttribute('for','lt-si-xan-thresh'); xanThreshLbl.setAttribute('style', imp(S.lbl));
    const xanThreshRow = el('div'); xanThreshRow.setAttribute('style', imp('display:flex;align-items:center;gap:6px;'));
    const xanThreshInp = document.createElement('input'); xanThreshInp.type = 'number'; xanThreshInp.min = '0';
    xanThreshInp.id = 'lt-si-xan-thresh'; xanThreshInp.value = cfg.getXanThreshold();
    xanThreshInp.setAttribute('style', imp('font-family:Consolas,monospace;font-size:14px;font-weight:700;text-align:center;width:72px;padding:6px 4px;background:#000e14;border:1px solid rgba(0,140,170,0.4);border-radius:5px;color:#e8f0d0;outline:none;-moz-appearance:textfield;'));
    xanThreshInp.addEventListener('focus', () => xanThreshInp.style.setProperty('border-color','rgba(0,190,215,0.8)','important'));
    xanThreshInp.addEventListener('blur',  () => xanThreshInp.style.setProperty('border-color','rgba(0,140,170,0.4)','important'));
    const xanThreshUnit = el('div'); xanThreshUnit.textContent = 'Xanax'; xanThreshUnit.setAttribute('style', imp('font-size:10px;color:rgba(200,220,160,0.55);font-family:Consolas,monospace;'));
    xanThreshRow.appendChild(xanThreshInp); xanThreshRow.appendChild(xanThreshUnit);
    const xanThreshHint = el('div'); xanThreshHint.textContent = 'South Africa stays top-ranked until personal count exceeds this'; xanThreshHint.setAttribute('style', imp(S.hint));
    xanThreshGroup.appendChild(xanThreshLbl); xanThreshGroup.appendChild(xanThreshRow); xanThreshGroup.appendChild(xanThreshHint);

    function updateThresholdVis() {
        const on = document.getElementById('lt-si-xan-priority') && document.getElementById('lt-si-xan-priority').checked;
        xanThreshGroup.style.setProperty('display', on ? 'block' : 'none', 'important');
    }

    // ── Buttons ──
    const btnRow    = el('div'); btnRow.setAttribute('style', imp(S.btnRow));
    const btnCancel = el('button'); btnCancel.textContent = 'Cancel';       btnCancel.setAttribute('type','button');
    const btnSave   = el('button'); btnSave.textContent   = 'Save & Refresh'; btnSave.setAttribute('type','button');
    btnCancel.setAttribute('style', imp(S.btn + `background:${C.bg};color:${C.green};`));
    btnSave.setAttribute('style',   imp(S.btn + `background:${C.settNote};color:${C.gold};`));
    btnRow.appendChild(btnCancel); btnRow.appendChild(btnSave);

    body.appendChild(note);
    body.appendChild(credHdr);
    body.appendChild(fAPI);
    body.appendChild(apiLink);
    body.appendChild(fUID);
    body.appendChild(secHdr);
    body.appendChild(visGrid);
    body.appendChild(xanSettHdr);
    body.appendChild(xanCarryGroup);
    body.appendChild(xanPriRowWrap);
    body.appendChild(xanThreshGroup);

    // ── Museum Day toggle ──
    const museumSettHdr = el('div'); museumSettHdr.textContent = '🏛️ Museum Day'; museumSettHdr.setAttribute('style', imp(S.secHdr));
    const museumPinRow = el('div'); museumPinRow.setAttribute('style', imp('display:flex;align-items:center;gap:8px;padding:5px 8px;border-radius:5px;background:rgba(255,184,48,0.04);border:1px solid rgba(255,184,48,0.18);cursor:pointer;margin-bottom:8px;'));
    const museumPinChk = document.createElement('input'); museumPinChk.type = 'checkbox'; museumPinChk.id = 'lt-museum-pin';
    museumPinChk.checked = cfg.getMuseumPin();
    museumPinChk.setAttribute('style', imp('width:14px;height:14px;cursor:pointer;accent-color:#ffb830;flex-shrink:0;'));
    const museumPinLbl = el('label'); museumPinLbl.textContent = '🏛️ Always show Museum Day bonus'; museumPinLbl.setAttribute('for', 'lt-museum-pin');
    museumPinLbl.setAttribute('style', imp('font-size:10px;font-weight:600;color:#e8f0d0;cursor:pointer;flex:1;'));
    museumPinRow.appendChild(museumPinChk); museumPinRow.appendChild(museumPinLbl);
    museumPinRow.addEventListener('click', e => { if (e.target !== museumPinChk) museumPinChk.checked = !museumPinChk.checked; });
    body.appendChild(museumSettHdr);
    body.appendChild(museumPinRow);

    body.appendChild(prefHdr);
    body.appendChild(tooltipRow);
    body.appendChild(btnRow);
    box.appendChild(hdr); box.appendChild(body);
    wrap.appendChild(box);
    document.body.appendChild(wrap);

    setTimeout(() => { const i = document.getElementById('lt-si-api'); if (i) i.focus(); }, 50);

    function doSave() {
        const key = (document.getElementById('lt-si-api').value || '').trim();
        const uid = (document.getElementById('lt-si-uid').value || '').trim();
        if (!key) { toast('⚠ API key is required'); return; }
        cfg.apiKey = key; cfg.userId = uid;
        const newVis = {};
        sections.forEach(sec => { newVis[sec.key] = document.getElementById('lt-vis-' + sec.key).checked; });
        cfg.setSectionVis(newVis);
        // Xanax count, carry, priority, threshold
        cfg.setXanCarry(Math.max(0, parseInt(document.getElementById('lt-si-xan-carry').value)||0));
        cfg.setXanPriority(document.getElementById('lt-si-xan-priority').checked);
        cfg.setXanThreshold(Math.max(0, parseInt(document.getElementById('lt-si-xan-thresh').value)||0));
        // Retrigger tooltip: clear seen key so carousel shows again on next load
        const showTooltip   = document.getElementById('lt-pref-tooltip').checked;
            const museumPin     = document.getElementById('lt-museum-pin').checked;
            cfg.setMuseumPin(museumPin);
        try {
            if (showTooltip) localStorage.removeItem('lt_tooltip_seen');
            else             localStorage.setItem('lt_tooltip_seen', '1');
        } catch(e) {}
        wrap.remove();
        toast('✓ Saved!');
        invCache = {}; abroadCache = {}; xanSACache = { qty:0, price:0 }; xanFacCache = null; xanPersonal = 0; // bobCache intentionally kept — shows last known BoB stock while refreshing
        if (panelEl) renderPanel();
        refreshAll();
    }
    btnSave.addEventListener('click', doSave);
    btnCancel.addEventListener('click', () => wrap.remove());
    wrap.addEventListener('click', e => { if (e.target === wrap) wrap.remove(); });
    wrap.addEventListener('keydown', e => { if (e.key === 'Escape') wrap.remove(); });
}

/* ─────────────────────────────────────────
   API / FETCH
───────────────────────────────────────── */
function gmFetch(url, timeoutMs) {
    timeoutMs = timeoutMs || 12000;
    const req = new Promise(resolve => {
        if (typeof GM_xmlhttpRequest !== 'undefined') {
            GM_xmlhttpRequest({
                method: 'GET', url, timeout: timeoutMs,
                onload:    r => { try { resolve(JSON.parse(r.responseText)); } catch { resolve({}); } },
                onerror:   () => resolve({}),
                ontimeout: () => resolve({}),
            });
        } else {
            fetch(url).then(r => r.json()).then(resolve).catch(() => resolve({}));
        }
    });
    return Promise.race([req, new Promise(r => setTimeout(() => r({}), timeoutMs))]);
}

async function fetchInventory() {
    if (!cfg.apiKey || !cfg.userId) throw new Error('No API key or User ID');
    const uid = parseInt(String(cfg.userId).replace(/\D/g,''));
    const d = await gmFetch(`https://api.torn.com/user/${uid}?selections=display&key=${cfg.apiKey}`);
    if (d.error) throw new Error(d.error.error || 'API error');
    const items = {};
    (d.display || []).forEach(item => { items[item.name] = (items[item.name] || 0) + item.quantity; });
    return items;
}

function scrapeXanaxFromItemsPage() {
    // Only scrape if we're on the items page
    if (!window.location.href.includes('item')) return;
    try {
        // Torn items page renders item names and quantities in the DOM
        // Look for any element containing "Xanax" and grab the adjacent quantity
        const allText = document.querySelectorAll('[class*="name"],[class*="title"],[class*="item"]');
        for (const el of allText) {
            if (el.textContent.trim() !== 'Xanax') continue;
            // Try siblings and parent children for quantity
            const parent = el.closest('[class*="item"],[class*="row"],[class*="wrap"]') || el.parentElement;
            if (!parent) continue;
            const qtyEl = parent.querySelector('[class*="qty"],[class*="amount"],[class*="quantity"],[class*="count"]');
            if (qtyEl) {
                const qty = parseInt(qtyEl.textContent.replace(/[^0-9]/g,'')) || 0;
                if (qty > 0) {
                    cfg.setXanCount(qty);
                    xanPersonal = qty;
                    console.log('[SetsTracker] scraped xanax from items page:', qty);
                    if (panelEl) renderPanel();
                    return;
                }
            }
            // Fallback: look for a number near the Xanax text
            const nearby = parent.textContent.match(/[xX](?:\s*)(\d+)|quantity[:\s]*(\d+)|(\d+)\s*[xX]/);
            if (nearby) {
                const qty = parseInt(nearby[1] || nearby[2] || nearby[3]) || 0;
                if (qty > 0) {
                    cfg.setXanCount(qty);
                    xanPersonal = qty;
                    console.log('[SetsTracker] scraped xanax (fallback):', qty);
                    if (panelEl) renderPanel();
                    return;
                }
            }
        }
    } catch(e) { console.warn('[SetsTracker] scrapeXanaxFromItemsPage threw:', e); }
}

function watchItemsPage() {
    // Watch for DOM changes on items page to trigger scrape
    if (!window.location.href.includes('item')) return;
    console.log('[SetsTracker] on items page — scraping xanax count');
    // Give the page time to render items
    setTimeout(scrapeXanaxFromItemsPage, 1500);
    setTimeout(scrapeXanaxFromItemsPage, 3000);
}

async function fetchYataData() {
    // Single YATA fetch — populates both abroadCache and xanSACache
    // YATA: { stocks: { "mex": { stocks: [{id, name, quantity, cost}] }, "sou": {...} } }
    const map = {};
    let sa = { qty: 0, price: 0 };
    try {
        const data = await gmFetch('https://yata.yt/api/v1/travel/export/');
        if (!data || !data.stocks) return { map, sa };
        Object.entries(data.stocks).forEach(([code, country]) => {
            const isSA = code === 'sou';
            (country.stocks || []).forEach(item => {
                const name = ID_TO_NAME[Number(item.id)];
                const qty  = Number(item.quantity || 0);
                if (name) map[name] = (map[name] || 0) + qty;
                if (isSA && Number(item.id) === XANAX_ID) {
                    sa = { qty, price: Number(item.cost || 0) };
                }
            });
        });
        console.log('[SetsTracker] YATA loaded — countries:', Object.keys(data.stocks).length, '| SA xanax qty:', sa.qty, 'price:', sa.price);
    } catch(e) { console.warn('[SetsTracker] fetchYataData threw:', e); }
    return { map, sa };
}

// fetchAbroad and fetchXanaxSA are handled by a single fetchYataData() call in refreshAll()

async function fetchBobStock() {
    // torn/?selections=cityshops — confirmed working
    // Shop name: "Bits 'n' Bobs" (id 103)
    // Items absent when out of stock, present with in_stock count when available
    if (!cfg.apiKey) return {};
    const bobMap = {};
    try {
        const data = await gmFetch(`https://api.torn.com/torn/?selections=cityshops&key=${cfg.apiKey}`);
        if (!data || data.error) { console.warn('[SetsTracker] fetchBobStock:', data && data.error ? data.error.error : 'no data'); return bobMap; }
        const shops = data.cityshops || {};
        Object.values(shops).forEach(shop => {
            const n = (shop.name || '').toLowerCase();
            if (!n.includes('bit') && !n.includes('bob')) return;
            const inv = shop.inventory || {};
            // Map all items we know about — plushies will appear here when in stock
            Object.entries(inv).forEach(([idStr, item]) => {
                const name = ID_TO_NAME[Number(idStr)];
                if (name) bobMap[name] = Number(item.in_stock || 0);
            });
            // Explicitly zero out BoB plushies not in the response (= out of stock)
            [186, 187, 215].forEach(id => {
                const name = ID_TO_NAME[id];
                if (name && bobMap[name] === undefined) bobMap[name] = 0;
            });
        });
        console.log('[SetsTracker] BoB stock:', JSON.stringify(bobMap));
    } catch(e) { console.warn('[SetsTracker] fetchBobStock threw:', e); }
    return bobMap;
}

async function fetchXanaxFaction() {
    if (!cfg.apiKey) return null;
    try {
        // faction/?selections=drugs — confirmed working with Full Access key
        // Returns: { drugs: [ { ID, name, type, quantity }, ... ] }
        const data = await gmFetch(`https://api.torn.com/faction/?selections=drugs&key=${cfg.apiKey}`);
        if (!data || data.error) { console.warn('[SetsTracker] fetchXanaxFaction:', data && data.error ? JSON.stringify(data.error) : 'no data'); return null; }
        const drugs = Array.isArray(data.drugs) ? data.drugs : Object.values(data.drugs || {});
        const xan   = drugs.find(d => Number(d.ID || d.id) === XANAX_ID);
        if (xan) {
            console.log('[SetsTracker] faction Xanax qty:', xan.quantity);
            return Number(xan.quantity || 0);
        }
        console.log('[SetsTracker] faction drugs found but no Xanax. IDs:', drugs.map(d => d.ID || d.id));
        return 0;
    } catch(e) { console.warn('[SetsTracker] fetchXanaxFaction threw:', e); }
    return null;
}

async function fetchPointsPrice() {
    const now = Date.now();
    if (pointsPriceCache.time && now - pointsPriceCache.time < POINTS_CACHE_DUR) return pointsPriceCache.price;
    if (!cfg.apiKey) return 0;
    try {
        const data = await gmFetch(`${POINTS_ENDPOINT}?key=${cfg.apiKey}`);
        if (data.pointsmarket) {
            const listings = Object.values(data.pointsmarket).filter(l => l.quantity > 0).map(l => l.cost).sort((a,b) => a-b);
            if (listings.length) {
                const top = listings.slice(0, Math.min(5, listings.length));
                const avg = Math.round(top.reduce((s,p) => s+p, 0) / top.length);
                pointsPriceCache.history.push(avg);
                if (pointsPriceCache.history.length > POINTS_HIST_SIZE) pointsPriceCache.history.shift();
                const stable = Math.round(pointsPriceCache.history.reduce((s,p) => s+p, 0) / pointsPriceCache.history.length);
                pointsPriceCache = { time: now, price: stable, history: pointsPriceCache.history };
                pointsPrice = stable;
                return stable;
            }
        }
    } catch(e) {}
    return pointsPrice || 0;
}

/* ─────────────────────────────────────────
   CALCULATION HELPERS
───────────────────────────────────────── */
function calcSet(inv, items) {
    const vals = Object.keys(items).map(k => inv[k] || 0);
    return vals.length ? Math.min(...vals) : 0;
}

function getSortedItems(inv, items, sets) {
    return Object.entries(items)
        .map(([name, data]) => ({ name, data, remaining: (inv[name] || 0) - sets }))
        .sort((a, b) => a.remaining - b.remaining);
}

function getBottleneck(inv, items, sets) {
    const sorted = getSortedItems(inv, items, sets).filter(i => i.remaining < 5);
    if (!sorted.length) return null;
    const parts = sorted.map(i => {
        const locLabel = LOCATIONS[i.data.loc]?.label || i.data.loc;
        return `${i.data.s} → ${locLabel}`;
    });
    return 'Need ' + parts.join(' & ');
}

function stockClass(name, qty) {
    if (qty === 0) return 'lt-da';
    if (GROUPS.Plushies.items[name]) return qty >= PLU_THRESH ? 'lt-hi' : 'lt-wa';
    if (GROUPS.Flowers.items[name])  return qty >= FLO_THRESH ? 'lt-hi' : 'lt-wa';
    return qty > 0 ? 'lt-hi' : 'lt-da';
}

function stockColor(name, qty) {
    if (qty === 0) return C.stockLo;
    if (GROUPS.Plushies.items[name]) return qty >= PLU_THRESH ? C.stockHi : C.stockMid;
    if (GROUPS.Flowers.items[name])  return qty >= FLO_THRESH ? C.stockHi : C.stockMid;
    return qty > 0 ? C.stockHi : C.stockLo;
}

/* ─────────────────────────────────────────
   RENDER HELPERS
───────────────────────────────────────── */
function makeEmpty(icon, msg) {
    const el = document.createElement('div');
    el.style.cssText = `padding:28px 16px;text-align:center;color:${C.textDim};font-size:11px;line-height:1.6;font-family:Arial,sans-serif;`;
    el.innerHTML = `<div style="font-size:26px;margin-bottom:8px;">${icon}</div>${msg}`;
    return el;
}

function makeSectionLabel(text, setCount, pts) {
    const el = document.createElement('div');
    el.style.cssText = `display:flex;justify-content:space-between;align-items:center;padding:4px 10px;font-size:9px;font-weight:700;letter-spacing:1px;text-transform:uppercase;color:${C.goldDim};background:rgba(0,60,80,0.15);border-top:1px solid rgba(0,140,180,0.2);border-bottom:1px solid rgba(0,140,180,0.15);font-family:Consolas,monospace;`;
    const left = document.createElement('span'); left.textContent = text;
    const right = document.createElement('span');
    right.style.cssText = `color:${C.gold};font-weight:700;font-size:8.5px;`;
    right.textContent = setCount !== undefined ? `${setCount} sets · ${setCount * pts} pts` : '';
    el.appendChild(left); el.appendChild(right);
    return el;
}

function makeAlertRow(msg) {
    const el = document.createElement('div');
    el.style.cssText = `margin:3px 8px;padding:5px 8px 5px 20px;position:relative;background:rgba(160,20,20,0.18);border-left:2px solid rgba(220,50,50,0.8);border-radius:3px;font-size:9.5px;font-weight:600;color:#ff8888;line-height:1.3;font-family:Arial,sans-serif;`;
    el.innerHTML = `<span style="position:absolute;left:6px;top:50%;transform:translateY(-50%);font-size:9px;opacity:0.9;color:#ff6666;">!</span>${msg}`;
    return el;
}

/* ─────────────────────────────────────────
   ITEM ROW BUILDER
───────────────────────────────────────── */
function makeItemRow(name, data, remaining, abroadQty, isBob) {
    const row = document.createElement('div');
    row.style.cssText = `display:grid;grid-template-columns:34px 34px 36px 1fr;gap:6px;align-items:center;padding:4px 8px;border-bottom:1px solid rgba(0,80,100,0.2);min-height:38px;transition:background 0.15s;`;
    row.addEventListener('mouseover', () => row.style.background = 'rgba(0,200,224,0.05)');
    row.addEventListener('mouseout',  () => row.style.background = 'transparent');

    // col 1 — item image
    const imgWrap = document.createElement('div');
    imgWrap.style.cssText = 'position:relative;width:32px;height:32px;';
    const img = document.createElement('img');
    img.src = itemImg(data.id); img.alt = data.s; img.title = name;
    img.style.cssText = 'width:30px;height:30px;object-fit:contain;border-radius:2px;background:rgba(255,255,255,0.04);border:1px solid rgba(255,255,255,0.08);display:block;transition:transform 0.2s;';
    img.addEventListener('mouseover', () => img.style.transform = 'scale(1.15)');
    img.addEventListener('mouseout',  () => img.style.transform = 'scale(1)');
    const imgFallback = document.createElement('span');
    imgFallback.style.cssText = 'display:none;width:30px;height:30px;font-size:7px;font-weight:700;text-align:center;line-height:30px;border-radius:2px;border:1px solid rgba(255,255,255,0.1);background:rgba(255,255,255,0.05);color:#c0e0ff;font-family:Consolas,monospace;';
    imgFallback.textContent = data.s;
    img.addEventListener('error', () => { img.style.display = 'none'; imgFallback.style.display = 'block'; });
    imgWrap.appendChild(img); imgWrap.appendChild(imgFallback);

    // col 2 — own count (remaining after sets)
    const ownEl = document.createElement('div');
    ownEl.style.cssText = `color:${C.green};background:rgba(0,180,210,0.08);font-weight:700;text-align:center;border:1px solid rgba(0,180,210,0.15);font-family:Consolas,monospace;padding:2px 3px;border-radius:2px;font-size:10px;`;
    ownEl.title = `You have ${remaining} extra after completing sets`;
    ownEl.textContent = remaining;

    // col 3 — abroad / BoB button
    let abroadEl;
    if (isBob) {
        const bobQty = bobCache[name] !== undefined ? bobCache[name] : null;
        const bobCol = bobQty === null ? C.gold : bobQty > 0 ? C.okay : C.textDim;
        abroadEl = document.createElement('a');
        abroadEl.href = 'https://www.torn.com/shops.php?step=bitsnbobs';
        abroadEl.style.cssText = `display:flex;align-items:center;justify-content:center;font-size:9px;font-weight:700;color:${bobCol};background:${bobCol}1a;border:1px solid ${bobCol}44;border-radius:3px;padding:2px 4px;cursor:pointer;white-space:nowrap;text-decoration:none !important;transition:all 0.15s;font-family:Consolas,monospace;`;
        abroadEl.textContent = bobQty !== null ? '🏪 ' + bobQty : '🏪 BoB';
        abroadEl.title = bobQty !== null ? "Bits n' Bobs stock: " + bobQty + "\nClick to open shop" : "Open Bits n' Bobs shop";
        abroadEl.addEventListener('mouseover', () => { abroadEl.style.background = bobCol + '33'; abroadEl.style.borderColor = bobCol + '88'; });
        abroadEl.addEventListener('mouseout',  () => { abroadEl.style.background = bobCol + '1a'; abroadEl.style.borderColor = bobCol + '44'; });
    } else {
        const sc = stockClass(name, abroadQty);
        const col = stockColor(name, abroadQty);
        abroadEl = document.createElement('div');
        abroadEl.className = sc;
        abroadEl.style.cssText = `color:${col};background:${col}1a;font-weight:700;text-align:center;border:1px solid ${col}44;font-family:Consolas,monospace;padding:2px 3px;border-radius:2px;font-size:10px;transition:all 0.3s;text-shadow:0 0 4px ${col}66;`;
        abroadEl.title = `Overseas stock (Torn API): ${abroadQty}`;
        abroadEl.textContent = abroadQty;
    }

    // col 4 — flag / location
    const loc    = LOCATIONS[data.loc] || { flag: '❓', label: data.loc };
    const flagEl = document.createElement('div');
    flagEl.style.cssText = 'display:flex;align-items:center;justify-content:center;font-size:16px;line-height:1;';
    flagEl.title = loc.label;
    flagEl.textContent = loc.flag;

    row.appendChild(imgWrap);
    row.appendChild(ownEl);
    row.appendChild(abroadEl);
    row.appendChild(flagEl);
    return row;
}

/* ─────────────────────────────────────────
   COLUMN HEADER ROW
───────────────────────────────────────── */
function makeColHeader() {
    const row = document.createElement('div');
    row.style.cssText = `display:grid;grid-template-columns:34px 34px 36px 1fr;gap:6px;padding:3px 8px;background:rgba(0,200,224,0.04);border-bottom:1px solid rgba(0,150,180,0.15);`;
    ['', 'Own', 'Abroad', ''].forEach((txt, i) => {
        const s = document.createElement('span');
        s.textContent = txt;
        s.style.cssText = `font:600 8px Consolas,monospace;color:rgba(0,200,224,0.45);text-align:center;text-transform:uppercase;letter-spacing:.4px;`;
        if (txt === 'Own')    s.title = 'Your display items minus completed sets. Lowest = bottleneck.';
        if (txt === 'Abroad') s.title = 'Live overseas stock from Torn API.\n🟢 Plushie ≥2000 / Flower ≥5000\n🟠 Below threshold  🔴 Zero\n🏪 BoB = Bits n\' Bobs shop shortcut';
        row.appendChild(s);
    });
    return row;
}

/* ─────────────────────────────────────────
   TAB: SETS
───────────────────────────────────────── */
function renderSetsBody() {
    const body = panelEl.querySelector('.lt-body');
    body.innerHTML = '';

    if (!cfg.apiKey) {
        body.appendChild(makeEmpty('🔑', 'Set your API key in Settings<br>to track your item sets'));
        return;
    }
    if (!Object.keys(invCache).length) {
        body.appendChild(makeEmpty('◌', 'Loading your items…'));
        return;
    }

    const vis          = cfg.getSectionVis();
    const runs         = cfg.getXanRuns();
    const activeRun    = runs.find(r => r.active);
    const xanPriority  = cfg.getXanPriority();
    const focusMode    = !!(activeRun || xanPriority);

    // ── FOCUS BANNER ──
    if (focusMode) {
        const isRun    = !!activeRun;
        const bannerCol = isRun ? '#00c8e0' : C.gold;
        const banner   = document.createElement('div');
        banner.style.cssText = `margin:6px 8px 4px;padding:7px 10px;border-radius:6px;background:${bannerCol}0f;border:1px solid ${bannerCol}44;display:flex;align-items:center;gap:8px;`;

        if (isRun) {
            const total     = activeRun.trips.reduce((s,t)=>s+t.bought,0);
            const remaining = Math.max(0, activeRun.contractQty - total);
            banner.innerHTML = `<span style="font-size:14px;flex-shrink:0;">✈</span>
                <div style="flex:1;font-family:Consolas,monospace;">
                    <div style="font-size:9.5px;font-weight:700;color:${bannerCol};letter-spacing:.4px;">RUN: ${activeRun.client}</div>
                    <div style="font-size:8.5px;color:rgba(0,200,224,0.6);margin-top:1px;">
                        ${total} collected · ${remaining} needed · ${activeRun.contractQty} total
                    </div>
                </div>
                <div style="font-size:11px;font-weight:700;font-family:Consolas,monospace;color:${bannerCol};">${Math.min(100,Math.round(total/activeRun.contractQty*100))}%</div>`;
        } else {
            // personal priority banner
            const threshold = cfg.getXanThreshold();
            const personal  = xanPersonal;
            const needed    = Math.max(0, threshold - personal);
            banner.innerHTML = `<span style="font-size:14px;flex-shrink:0;">🧪</span>
                <div style="flex:1;font-family:Consolas,monospace;">
                    <div style="font-size:9.5px;font-weight:700;color:${bannerCol};letter-spacing:.4px;">XANAX PRIORITY</div>
                    <div style="font-size:8.5px;color:rgba(255,180,0,0.6);margin-top:1px;">
                        ${personal} collected · ${needed} needed · ${threshold} target
                    </div>
                </div>
                <div style="font-size:11px;font-weight:700;font-family:Consolas,monospace;color:${needed===0?'#66bb66':bannerCol};">${personal}/${threshold}</div>`;
        }
        body.appendChild(banner);
    }

    // ── Museum Day Bonus Line ──
    (function() {
        const now         = new Date();
        const museumStart = new Date(Date.UTC(2026, 4, 17, 10, 0, 0)); // May 17 10:00 TCT
        const museumEnd   = new Date(Date.UTC(2026, 4, 19,  0, 0, 0)); // May 19 (48h window)
        const msUntil     = museumStart - now;
        const daysUntil   = msUntil / 86400000;
        const isActive    = now >= museumStart && now <= museumEnd;
        const isPast      = now > museumEnd;
        const inWindow    = isActive || (!isPast && daysUntil <= 7);
        const pinned      = cfg.getMuseumPin();

        if (!(inWindow || pinned)) return;
        if (!pointsPrice || !Object.keys(invCache).length) return;

        const vis2 = cfg.getSectionVis();
        let tSets = 0, tPts = 0;
        Object.entries(GROUPS).forEach(([n, g]) => {
            if (vis2[n.toLowerCase()] === false) return;
            const s = calcSet(invCache, g.items);
            tSets += s;
            tPts  += s * g.pts;
        });
        if (vis2.special !== false) {
            Object.entries(SPECIAL_ITEMS).forEach(([name, data]) => {
                const qty = invCache[name] || 0;
                tSets += qty;
                tPts  += qty * data.pts;
            });
        }
        if (!tSets) return;

        const bonusSets = Math.floor(tSets * 0.1);          // 200 sets ÷ 10% = 20
        const musSets   = tSets + bonusSets;                 // 200 + 20 = 220
        const musPts    = Math.round(tPts * 1.1);            // pts scale proportionally
        const musVal    = musPts * pointsPrice;
        const musFmt    = musVal >= 1e9 ? `$${(musVal/1e9).toFixed(2)}B`
                        : musVal >= 1e6 ? `$${(musVal/1e6).toFixed(1)}M`
                        : `$${Math.round(musVal/1000)}k`;

        let timeTag = '';
        if (isActive)             timeTag = ' · 🟢 ACTIVE';
        else if (daysUntil <= 1)  timeTag = ' · tomorrow';
        else if (daysUntil <= 7)  timeTag = ` · in ${Math.ceil(daysUntil)}d`;

        const GOLD     = '#ffb830';
        const GOLD_DIM = 'rgba(255,184,48,0.7)';
        const GOLD_BG  = 'rgba(255,184,48,0.06)';
        const GOLD_BDR = 'rgba(255,184,48,0.22)';

        const museumRow = document.createElement('div');
        museumRow.style.cssText = `display:flex;align-items:center;gap:6px;padding:4px 8px;background:${GOLD_BG};border-bottom:1px solid ${GOLD_BDR};font-family:Consolas,monospace;font-size:8px;`;
        museumRow.title = "Museum Day (May 17-19) gives 10% bonus on point redemptions. The bonus shown is what you would earn extra by waiting.";

        const icon = document.createElement('span');
        icon.textContent = '🏛️';
        icon.style.cssText = 'font-size:10px;flex-shrink:0;';

        const label = document.createElement('span');
        label.style.cssText = `color:${GOLD};font-weight:700;letter-spacing:.5px;text-transform:uppercase;flex:1;`;
        label.textContent = `Museum Day Bonus: ${musSets.toLocaleString()} sets · ${musPts.toLocaleString()} pts · ${musFmt}${timeTag}`;

        museumRow.appendChild(icon);
        museumRow.appendChild(label);
        body.appendChild(museumRow);
    })();

    // ── GROUPS (hidden in focus mode) ──
    if (!focusMode) {
        Object.entries(GROUPS).forEach(([groupName, g]) => {
            if (vis[groupName.toLowerCase()] === false) return;
            const sets = calcSet(invCache, g.items);
            const warn = getBottleneck(invCache, g.items, sets);
            body.appendChild(makeSectionLabel(g.icon + ' ' + groupName.toUpperCase(), sets, g.pts));
            if (warn) body.appendChild(makeAlertRow(warn));
            body.appendChild(makeColHeader());
            getSortedItems(invCache, g.items, sets).forEach(({ name, data, remaining }) => {
                const isBob = BOB_IDS.has(data.id);
                const abroad = abroadCache[name] || 0;
                body.appendChild(makeItemRow(name, data, remaining, abroad, isBob));
            });
        });

        // ── SPECIAL ──
        if (vis.special !== false) {
            const specCount = Object.values(SPECIAL_ITEMS).reduce((s, d) => {
                const name = Object.keys(SPECIAL_ITEMS).find(k => SPECIAL_ITEMS[k] === d);
                return s + (invCache[name] || 0);
            }, 0);
            body.appendChild(makeSectionLabel('☄️ SPECIAL', specCount, 0));
            body.appendChild(makeColHeader());
            Object.entries(SPECIAL_ITEMS).forEach(([name, data]) => {
                const own    = invCache[name] || 0;
                const abroad = abroadCache[name] || 0;
                body.appendChild(makeItemRow(name, data, own, abroad, false));
            });
        }
    }

    // ── XANAX FOCUS ROW (shown in focus mode only) ──
    if (focusMode) {
        const xanOwn    = xanPersonal;
        const xanAbroad = xanSACache.qty || 0;
        body.appendChild(makeSectionLabel('🧪 XANAX', xanOwn, 0));
        body.appendChild(makeColHeader());
        body.appendChild(makeItemRow('Xanax', { id: XANAX_ID, s: 'Xanax', loc: 'South Africa' }, xanOwn, xanAbroad, false));
    }
}

/* ─────────────────────────────────────────
   TAB: XANAX
───────────────────────────────────────── */

/* ─────────────────────────────────────────
   XANAX RUN — helper functions
───────────────────────────────────────── */
function loadActiveXanRun() {
    const runs = cfg.getXanRuns();
    activeXanRun = runs.find(r => r.active) || null;
}

function saveXanRun(run) {
    const runs = cfg.getXanRuns();
    const idx  = runs.findIndex(r => r.id === run.id);
    if (idx >= 0) runs[idx] = run; else runs.push(run);
    cfg.setXanRuns(runs);
}

function openXanTripLogModal(run, zIndex) {
    const overlay = document.createElement('div');
    overlay.style.cssText = `position:fixed;inset:0;background:rgba(0,0,0,0.78);z-index:${zIndex||1000020};display:flex;align-items:center;justify-content:center;padding:16px;`;
    overlay.addEventListener('click', e => { if (e.target === overlay) overlay.remove(); });

    const box = document.createElement('div');
    box.style.cssText = 'background:linear-gradient(145deg,#000c12,#000810);border:1px solid rgba(0,200,224,0.35);border-radius:12px;padding:18px;width:100%;max-width:290px;max-height:75vh;display:flex;flex-direction:column;font-family:Arial,sans-serif;';

    const hdr = document.createElement('div');
    hdr.style.cssText = 'display:flex;align-items:center;margin-bottom:12px;';
    const htitle = document.createElement('div');
    htitle.textContent = '✈ Trip Log — ' + run.client;
    htitle.style.cssText = 'font-size:11px;font-weight:700;letter-spacing:.8px;text-transform:uppercase;color:#00c8e0;flex:1;';
    const closeBtn = document.createElement('button'); closeBtn.type='button'; closeBtn.textContent='✕';
    closeBtn.style.cssText = 'background:none;border:none;color:rgba(180,220,230,0.4);font-size:14px;cursor:pointer;padding:0 2px;';
    closeBtn.addEventListener('click', () => overlay.remove());
    hdr.appendChild(htitle); hdr.appendChild(closeBtn);
    box.appendChild(hdr);

    // Summary strip
    const total = run.trips.reduce((s,t) => s+t.bought, 0);
    const carry = cfg.getXanCarry() || 0;
    const summary = document.createElement('div');
    summary.style.cssText = 'display:flex;gap:6px;margin-bottom:10px;font-size:8.5px;font-family:Consolas,monospace;flex-wrap:wrap;';
    const mkBadge = (txt, col) => {
        const s = document.createElement('span');
        s.textContent = txt;
        s.style.cssText = `color:${col};background:${col}18;border:1px solid ${col}44;border-radius:3px;padding:2px 7px;`;
        return s;
    };
    summary.appendChild(mkBadge(run.trips.length + ' trips', C.goldDim));
    summary.appendChild(mkBadge(total + ' 🧪 total', C.okay));
    box.appendChild(summary);

    // Trip list
    const list = document.createElement('div');
    list.style.cssText = 'overflow-y:auto;flex:1;display:flex;flex-direction:column;gap:4px;margin-bottom:12px;';

    if (run.trips.length === 0) {
        const empty = document.createElement('div');
        empty.textContent = 'No trips logged yet.';
        empty.style.cssText = 'font-size:10px;color:rgba(180,220,230,0.3);font-family:Consolas,monospace;text-align:center;padding:20px 0;';
        list.appendChild(empty);
    } else {
        [...run.trips].reverse().forEach((t, i) => {
            const tripNum = run.trips.length - i;
            const d = new Date(t.ts);
            const time = d.toLocaleTimeString([], { hour:'2-digit', minute:'2-digit' });
            const date = d.toLocaleDateString([], { month:'short', day:'numeric' });
            const row = document.createElement('div');
            row.style.cssText = `display:flex;align-items:center;gap:8px;padding:6px 8px;border-radius:5px;background:rgba(0,14,22,0.5);border:1px solid rgba(0,140,170,0.12);font-size:8.5px;font-family:Consolas,monospace;`;
            row.innerHTML = `<span style="color:${C.goldDim};width:28px;flex-shrink:0;">T${tripNum}</span>
                <span style="flex:1;color:rgba(180,220,230,0.45);">${date} ${time}</span>
                <span style="color:${C.okay};font-weight:700;">+${t.bought} 🧪</span>`;
            list.appendChild(row);
        });
    }
    box.appendChild(list);

    // Log trip + close buttons
    const btnRow = document.createElement('div'); btnRow.style.cssText = 'display:flex;gap:8px;';
    const doneBtn = document.createElement('button'); doneBtn.type='button'; doneBtn.textContent='Done';
    doneBtn.style.cssText = 'flex:1;padding:9px 0;border-radius:7px;border:1px solid rgba(80,80,80,0.3);background:rgba(20,20,20,0.5);color:rgba(200,200,200,0.5);font-size:11px;cursor:pointer;font-family:Arial,sans-serif;';
    doneBtn.addEventListener('click', () => overlay.remove());
    if (run.active) {
        const logBtn = document.createElement('button'); logBtn.type='button';
        logBtn.textContent = carry > 0 ? `✈ Log Trip (+${carry})` : '✈ Log Trip';
        logBtn.style.cssText = `flex:1;padding:9px 0;border-radius:7px;border:1px solid rgba(0,200,224,0.45);background:rgba(0,30,40,0.7);color:#00c8e0;font-size:11px;font-weight:700;cursor:pointer;font-family:Arial,sans-serif;`;
        logBtn.addEventListener('click', () => {
            logXanTrip(run.id);
            overlay.remove();
            openXanTripLogModal(cfg.getXanRuns().find(r => r.id === run.id) || run, zIndex);
        });
        btnRow.appendChild(logBtn);
    }
    btnRow.appendChild(doneBtn);
    box.appendChild(btnRow);

    overlay.appendChild(box);
    document.body.appendChild(overlay);
}

function openXanRunModal(existing) {
    const overlay = document.createElement('div');
    overlay.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.72);z-index:1000010;display:flex;align-items:center;justify-content:center;padding:16px;';

    const box = document.createElement('div');
    box.style.cssText = 'background:linear-gradient(145deg,#000c12,#000810);border:1px solid rgba(0,200,224,0.35);border-radius:12px;padding:18px;width:100%;max-width:290px;font-family:Arial,sans-serif;';

    const title = document.createElement('div');
    title.textContent = existing ? '✎ Edit Run' : '✈ New Xanax Run';
    title.style.cssText = 'font-size:12px;font-weight:700;letter-spacing:1px;text-transform:uppercase;color:#00c8e0;margin-bottom:14px;';
    box.appendChild(title);

    function field(lbl, placeholder, val, type) {
        const g = document.createElement('div'); g.style.cssText = 'margin-bottom:11px;';
        const l = document.createElement('div'); l.textContent = lbl;
        l.style.cssText = 'font-size:9px;font-weight:700;letter-spacing:.8px;text-transform:uppercase;color:rgba(0,200,224,0.6);margin-bottom:4px;';
        const inp = document.createElement('input'); inp.type = type||'text'; inp.placeholder = placeholder;
        inp.value = val !== undefined && val !== null ? val : '';
        inp.style.cssText = 'width:100%;box-sizing:border-box;background:#000e16;border:1px solid rgba(0,140,170,0.35);border-radius:6px;color:#e8f0d0;font-size:13px;font-family:Consolas,monospace;padding:7px 10px;outline:none;';
        inp.addEventListener('focus', () => inp.style.borderColor = 'rgba(0,200,224,0.7)');
        inp.addEventListener('blur',  () => inp.style.borderColor = 'rgba(0,140,170,0.35)');
        g.appendChild(l); g.appendChild(inp);
        box.appendChild(g);
        return inp;
    }

    const clientInp = field('Client / Faction Name', 'Client or Faction name', existing ? existing.client : '');
    const qtyInp    = field('Contract Qty (xanax)', 'Total Xanax to deliver', existing ? existing.contractQty : '', 'number');
    const priceInp  = field('Your Sell Price ($ / xanax)', 'Price per Xanax', existing ? existing.manualPrice : '', 'number');

    if (xanSACache.price > 0) {
        const hint = document.createElement('div');
        hint.style.cssText = 'font-size:8.5px;color:rgba(0,190,215,0.55);font-family:Consolas,monospace;margin-top:-7px;margin-bottom:11px;';
        const updateHint = () => {
            const margin = (parseInt(priceInp.value)||0) - xanSACache.price;
            hint.textContent = `SA: $${xanSACache.price.toLocaleString()} — margin: ${margin>=0?'+':''}$${margin.toLocaleString()} /xan`;
        };
        updateHint();
        priceInp.addEventListener('input', updateHint);
        box.appendChild(hint);
    }

    const btnRow = document.createElement('div'); btnRow.style.cssText = 'display:flex;gap:8px;margin-top:4px;';
    const saveBtn = document.createElement('button'); saveBtn.type='button';
    saveBtn.textContent = existing ? 'Save Changes' : '⚡ Start Run';
    saveBtn.style.cssText = 'flex:1;padding:9px 0;border-radius:7px;border:1px solid rgba(0,200,224,0.45);background:rgba(0,30,40,0.7);color:#00c8e0;font-size:11px;font-weight:700;cursor:pointer;font-family:Arial,sans-serif;';
    const cancelBtn = document.createElement('button'); cancelBtn.type='button'; cancelBtn.textContent='Cancel';
    cancelBtn.style.cssText = 'padding:9px 14px;border-radius:7px;border:1px solid rgba(80,80,80,0.3);background:rgba(20,20,20,0.5);color:rgba(200,200,200,0.5);font-size:11px;cursor:pointer;font-family:Arial,sans-serif;';
    cancelBtn.addEventListener('click', () => overlay.remove());
    overlay.addEventListener('click', e => { if (e.target === overlay) overlay.remove(); });

    saveBtn.addEventListener('click', () => {
        const client = clientInp.value.trim();
        const qty    = parseInt(qtyInp.value)   || 0;
        const price  = parseInt(priceInp.value) || 0;
        if (!client) { clientInp.style.borderColor = '#ff4444'; return; }
        if (!qty)    { qtyInp.style.borderColor    = '#ff4444'; return; }
        const run = existing ? { ...existing } : {
            id: Date.now(), active: true, startedAt: Date.now(),
            endedAt: null, trips: [], payment: 'unpaid',
        };
        run.client = client; run.contractQty = qty;
        run.manualPrice = price; run.saPrice = xanSACache.price;
        saveXanRun(run);
        overlay.remove();
        if (panelEl && activeTab === 'xanax') renderXanaxBody();
        toast('✈ Run started!', 2000);
    });

    btnRow.appendChild(saveBtn); btnRow.appendChild(cancelBtn);
    box.appendChild(btnRow);
    overlay.appendChild(box);
    document.body.appendChild(overlay);
    setTimeout(() => clientInp.focus(), 80);
}

function logXanTrip(runId) {
    const runs = cfg.getXanRuns();
    const run  = runs.find(r => r.id === runId);
    if (!run) return;
    const carry = cfg.getXanCarry() || 0;
    run.trips.push({ ts: Date.now(), bought: carry });
    saveXanRun(run);
    if (panelEl && activeTab === 'xanax') renderXanaxBody();
    toast(`✈ Trip logged — +${carry} 🧪`, 1800);
}

function endXanRun(runId) {
    const runs = cfg.getXanRuns();
    const run  = runs.find(r => r.id === runId);
    if (!run) return;
    run.active = false; run.endedAt = Date.now();
    saveXanRun(run);
    if (panelEl && activeTab === 'xanax') renderXanaxBody();
    toast('✓ Run complete!', 2000);
}

function renderXanRunSection(wrap, secTitle) {
    const runs   = cfg.getXanRuns();
    const active = runs.filter(r => r.active);
    const past   = runs.filter(r => !r.active);

    const sec = document.createElement('div');
    sec.appendChild(secTitle('✈ Xanax Runs'));

    // ── Start button ──
    const startBtn = document.createElement('button'); startBtn.type='button';
    startBtn.textContent = '⚡ Start New Run';
    startBtn.style.cssText = `width:100%;padding:10px 0;border-radius:7px;font-size:11px;font-weight:700;cursor:pointer;font-family:Arial,sans-serif;background:rgba(0,20,30,0.6);border:1px solid rgba(0,200,224,0.35);color:#00c8e0;letter-spacing:.5px;margin-bottom:${(active.length||past.length)?'10px':'0'};`;
    startBtn.addEventListener('click', () => openXanRunModal(null));
    sec.appendChild(startBtn);

    // ── Active runs ──
    if (active.length > 0) {
        const actHdr = document.createElement('div');
        actHdr.textContent = `⚡ ACTIVE (${active.length})`;
        actHdr.style.cssText = `font-size:9px;font-weight:700;letter-spacing:1px;color:${C.goldDim};font-family:Consolas,monospace;margin-bottom:6px;`;
        sec.appendChild(actHdr);

        active.forEach(r => {
            const total = r.trips.reduce((s,t) => s+t.bought, 0);
            const carry = cfg.getXanCarry() || 0;
            const pct   = r.contractQty > 0 ? Math.min(100, Math.round(total / r.contractQty * 100)) : 0;
            const margin  = r.manualPrice && r.saPrice ? r.manualPrice - r.saPrice : null;
            const profit  = margin !== null && total > 0 ? margin * total : null;
            const payCol  = r.payment === 'paid' ? '#66bb66' : '#ffaa00';
            const barCol  = pct >= 100 ? '#66bb66' : pct > 50 ? '#00c8e0' : '#ffaa00';

            const card = document.createElement('div');
            card.style.cssText = `background:rgba(0,16,26,0.65);border:1px solid rgba(0,200,224,0.28);border-radius:8px;padding:11px;margin-bottom:8px;`;

            // Header: client name + edit btn
            const hdr = document.createElement('div'); hdr.style.cssText = 'display:flex;align-items:center;gap:6px;margin-bottom:7px;';
            const nm = document.createElement('span'); nm.textContent = r.client;
            nm.style.cssText = 'font-size:13px;font-weight:700;color:#e8f0d0;font-family:Arial,sans-serif;flex:1;';
            const editBtn = document.createElement('button'); editBtn.type='button'; editBtn.textContent='✎';
            editBtn.style.cssText = `padding:2px 7px;border-radius:4px;font-size:11px;cursor:pointer;background:rgba(0,20,30,0.5);border:1px solid rgba(0,140,170,0.3);color:${C.goldDim};font-family:Arial,sans-serif;`;
            editBtn.addEventListener('click', () => openXanRunModal(r));
            hdr.appendChild(nm); hdr.appendChild(editBtn);
            card.appendChild(hdr);

            // Progress bar
            const pw = document.createElement('div'); pw.style.cssText = 'background:rgba(0,140,170,0.12);border-radius:4px;height:7px;margin-bottom:7px;overflow:hidden;';
            const pb = document.createElement('div'); pb.style.cssText = `height:100%;width:${pct}%;background:${barCol};border-radius:4px;transition:width 0.4s;`;
            pw.appendChild(pb); card.appendChild(pw);

            // Stats grid
            const stats = document.createElement('div'); stats.style.cssText = 'display:grid;grid-template-columns:1fr 1fr 1fr;gap:5px;margin-bottom:7px;';
            [
                { label:'Bought',   val:`${total}/${r.contractQty}`, col: pct>=100?'#66bb66':C.okay },
                { label:'Trips',    val: r.trips.length,              col: C.gold },
                { label:'Progress', val: pct+'%',                     col: barCol },
            ].forEach(({ label, val, col }) => {
                const c = document.createElement('div');
                c.style.cssText = `background:rgba(0,14,22,0.5);border:1px solid rgba(0,140,170,0.15);border-radius:5px;padding:5px 3px;text-align:center;`;
                c.innerHTML = `<div style="font-size:7.5px;color:${C.textDim};font-family:Consolas,monospace;margin-bottom:1px;">${label}</div>
                               <div style="font-size:12px;font-weight:700;color:${col};font-family:Consolas,monospace;">${val}</div>`;
                stats.appendChild(c);
            });
            card.appendChild(stats);

            // Price strip
            if (r.manualPrice || r.saPrice) {
                const ps = document.createElement('div'); ps.style.cssText = 'display:flex;gap:5px;flex-wrap:wrap;font-size:8px;font-family:Consolas,monospace;margin-bottom:7px;';
                const mkTag = (t, col) => { const s=document.createElement('span'); s.textContent=t; s.style.cssText=`color:${col};background:${col}18;border:1px solid ${col}44;border-radius:3px;padding:2px 5px;`; return s; };
                if (r.manualPrice) ps.appendChild(mkTag('Sell $'+r.manualPrice.toLocaleString(), C.gold));
                if (r.saPrice)     ps.appendChild(mkTag('SA $'+r.saPrice.toLocaleString(), C.textDim));
                if (margin!==null) { const col=margin>=0?'#66bb66':'#ff6666'; ps.appendChild(mkTag((margin>=0?'+':'')+'$'+margin.toLocaleString()+'/xan', col)); }
                if (profit!==null) { const col=profit>=0?'#66bb66':'#ff6666'; ps.appendChild(mkTag((profit>=0?'+':'')+'$'+profit.toLocaleString()+' profit', col)); }
                card.appendChild(ps);
            }

            // Action row: 📋 Log | ✈ Log Trip | payment toggle | ✓ End
            const btnRow = document.createElement('div'); btnRow.style.cssText = 'display:flex;gap:6px;flex-wrap:wrap;';

            const logBtn = document.createElement('button'); logBtn.type='button'; logBtn.textContent='📋 Log';
            logBtn.style.cssText = `padding:7px 10px;border-radius:6px;font-size:10px;font-weight:700;cursor:pointer;font-family:Arial,sans-serif;background:rgba(0,20,30,0.6);border:1px solid rgba(0,140,170,0.3);color:${C.goldDim};`;
            logBtn.addEventListener('click', () => openXanTripLogModal(cfg.getXanRuns().find(x=>x.id===r.id)||r));

            const tripBtn = document.createElement('button'); tripBtn.type='button';
            tripBtn.textContent = carry > 0 ? `✈ +${carry}` : '✈ Trip';
            tripBtn.style.cssText = `flex:1;padding:7px 0;border-radius:6px;font-size:10.5px;font-weight:700;cursor:pointer;font-family:Arial,sans-serif;background:rgba(0,30,40,0.7);border:1px solid rgba(0,200,224,0.4);color:#00c8e0;`;
            tripBtn.addEventListener('click', () => logXanTrip(r.id));

            const payBtn = document.createElement('button'); payBtn.type='button';
            payBtn.textContent = r.payment === 'paid' ? '✓ Paid' : '$ Unpaid';
            payBtn.style.cssText = `padding:7px 9px;border-radius:6px;font-size:9.5px;font-weight:700;cursor:pointer;font-family:Arial,sans-serif;background:${payCol}18;border:1px solid ${payCol}55;color:${payCol};`;
            payBtn.addEventListener('click', () => {
                const all = cfg.getXanRuns();
                const idx = all.findIndex(x=>x.id===r.id);
                if (idx<0) return;
                all[idx].payment = all[idx].payment === 'paid' ? 'unpaid' : 'paid';
                cfg.setXanRuns(all);
                renderXanaxBody();
            });

            const endBtn = document.createElement('button'); endBtn.type='button'; endBtn.textContent='✓ End';
            endBtn.style.cssText = `padding:7px 10px;border-radius:6px;font-size:10px;font-weight:700;cursor:pointer;font-family:Arial,sans-serif;background:rgba(10,40,5,0.6);border:1px solid rgba(60,150,20,0.4);color:#66bb66;`;
            endBtn.addEventListener('click', () => { if (confirm(`End run for ${r.client}?`)) endXanRun(r.id); });

            btnRow.appendChild(logBtn); btnRow.appendChild(tripBtn); btnRow.appendChild(payBtn); btnRow.appendChild(endBtn);
            card.appendChild(btnRow);
            sec.appendChild(card);
        });
    }

    // ── Past runs button ──
    if (past.length > 0) {
        const pastBtn = document.createElement('button'); pastBtn.type='button';
        pastBtn.textContent = `📋 Past Runs (${past.length})`;
        pastBtn.style.cssText = `width:100%;padding:9px 0;border-radius:7px;font-size:11px;font-weight:700;cursor:pointer;font-family:Arial,sans-serif;background:rgba(0,14,22,0.5);border:1px solid rgba(0,140,170,0.25);color:${C.goldDim};letter-spacing:.4px;margin-top:${active.length?'4px':'0'};`;
        pastBtn.addEventListener('click', () => openPastRunsOverlay());
        sec.appendChild(pastBtn);
    }

    wrap.appendChild(sec);
}

function openPastRunsOverlay() {
    const runs = cfg.getXanRuns();
    const past = runs.filter(r => !r.active).slice().reverse();

    const overlay = document.createElement('div');
    overlay.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.85);z-index:1000030;display:flex;flex-direction:column;font-family:Arial,sans-serif;';

    // ── Header bar ──
    const hdr = document.createElement('div');
    hdr.style.cssText = 'display:flex;align-items:center;gap:10px;padding:14px 16px;background:linear-gradient(145deg,#000c12,#000810);border-bottom:1px solid rgba(0,140,170,0.25);flex-shrink:0;';
    const htitle = document.createElement('div');
    htitle.textContent = `📋 Past Runs (${past.length})`;
    htitle.style.cssText = 'font-size:13px;font-weight:700;letter-spacing:.8px;text-transform:uppercase;color:#00c8e0;flex:1;';
    const closeBtn = document.createElement('button'); closeBtn.type='button'; closeBtn.textContent='✕ Close';
    closeBtn.style.cssText = 'padding:6px 14px;border-radius:6px;font-size:10px;font-weight:700;cursor:pointer;background:rgba(0,20,30,0.7);border:1px solid rgba(0,140,170,0.3);color:rgba(0,200,224,0.6);font-family:Arial,sans-serif;';
    closeBtn.addEventListener('click', () => overlay.remove());
    hdr.appendChild(htitle); hdr.appendChild(closeBtn);
    overlay.appendChild(hdr);

    // ── Scrollable list ──
    const list = document.createElement('div');
    list.style.cssText = 'flex:1;overflow-y:auto;padding:12px 14px;display:flex;flex-direction:column;gap:8px;';

    if (past.length === 0) {
        const empty = document.createElement('div');
        empty.textContent = 'No completed runs yet.';
        empty.style.cssText = 'font-size:11px;color:rgba(180,220,230,0.3);font-family:Consolas,monospace;text-align:center;padding:40px 0;';
        list.appendChild(empty);
    } else {
        past.forEach(r => {
            const total  = r.trips.reduce((s,t)=>s+t.bought,0);
            const profit = r.manualPrice && r.saPrice ? (r.manualPrice - r.saPrice) * total : null;
            const payCol = r.payment === 'paid' ? '#66bb66' : '#ffaa00';

            const card = document.createElement('div');
            card.style.cssText = 'background:rgba(0,12,20,0.7);border:1px solid rgba(0,140,170,0.18);border-radius:9px;padding:12px 14px;';

            // Top row: client + date
            const top = document.createElement('div'); top.style.cssText = 'display:flex;align-items:baseline;gap:6px;margin-bottom:7px;';
            const nm  = document.createElement('span'); nm.textContent = r.client;
            nm.style.cssText = 'font-size:14px;font-weight:700;color:#e8f0d0;font-family:Arial,sans-serif;flex:1;';
            const dt  = document.createElement('span');
            dt.textContent = new Date(r.startedAt).toLocaleDateString([],{month:'short',day:'numeric',year:'numeric'});
            dt.style.cssText = 'font-size:9px;color:rgba(180,220,230,0.3);font-family:Consolas,monospace;';
            top.appendChild(nm); top.appendChild(dt);

            // Tags row
            const mid = document.createElement('div'); mid.style.cssText = 'display:flex;gap:6px;flex-wrap:wrap;font-size:8.5px;font-family:Consolas,monospace;margin-bottom:10px;';
            const mkTag = (t, col) => { const s=document.createElement('span'); s.textContent=t; s.style.cssText=`color:${col};background:${col}18;border:1px solid ${col}44;border-radius:3px;padding:2px 7px;`; return s; };
            mkTag(r.contractQty + ' contracted', C.textDim);
            mid.appendChild(mkTag(r.trips.length + ' trips', C.goldDim));
            mid.appendChild(mkTag(total + ' 🧪 delivered', C.okay));
            if (r.manualPrice) mid.appendChild(mkTag('$' + r.manualPrice.toLocaleString() + '/xan', C.gold));
            if (profit !== null) { const col = profit >= 0 ? '#66bb66' : '#ff6666'; mid.appendChild(mkTag((profit>=0?'+':'')+'$'+profit.toLocaleString()+' profit', col)); }
            mid.appendChild(mkTag(r.payment === 'paid' ? '✓ Paid' : '$ Unpaid', payCol));

            // Actions
            const acts = document.createElement('div'); acts.style.cssText = 'display:flex;gap:7px;';

            const viewBtn = document.createElement('button'); viewBtn.type='button'; viewBtn.textContent='📋 Trip Log';
            viewBtn.style.cssText = `padding:6px 12px;border-radius:5px;font-size:10px;font-weight:700;cursor:pointer;font-family:Arial,sans-serif;background:rgba(0,20,30,0.6);border:1px solid rgba(0,140,170,0.3);color:${C.goldDim};`;
            viewBtn.addEventListener('click', () => openXanTripLogModal(r, 1000040));

            if (r.payment !== 'paid') {
                const mkPaid = document.createElement('button'); mkPaid.type='button'; mkPaid.textContent='✓ Mark Paid';
                mkPaid.style.cssText = 'padding:6px 12px;border-radius:5px;font-size:10px;font-weight:700;cursor:pointer;font-family:Arial,sans-serif;background:rgba(10,40,5,0.5);border:1px solid rgba(40,130,20,0.4);color:#66bb66;';
                mkPaid.addEventListener('click', () => {
                    const all = cfg.getXanRuns(); const idx = all.findIndex(x=>x.id===r.id);
                    if (idx>=0) { all[idx].payment='paid'; cfg.setXanRuns(all); }
                    // refresh overlay
                    overlay.remove(); openPastRunsOverlay();
                    if (panelEl && activeTab === 'xanax') renderXanaxBody();
                });
                acts.appendChild(mkPaid);
            }

            const delBtn = document.createElement('button'); delBtn.type='button'; delBtn.textContent='🗑 Delete';
            delBtn.style.cssText = 'padding:6px 12px;border-radius:5px;font-size:10px;cursor:pointer;font-family:Arial,sans-serif;background:rgba(30,8,8,0.5);border:1px solid rgba(120,30,30,0.3);color:rgba(200,80,80,0.5);margin-left:auto;';
            delBtn.addEventListener('click', () => {
                if (!confirm('Delete this run?')) return;
                cfg.setXanRuns(cfg.getXanRuns().filter(x=>x.id!==r.id));
                overlay.remove();
                if (past.length - 1 > 0) openPastRunsOverlay();
                if (panelEl && activeTab === 'xanax') renderXanaxBody();
                toast('🗑 Deleted');
            });

            acts.appendChild(viewBtn); acts.appendChild(delBtn);
            card.appendChild(top); card.appendChild(mid); card.appendChild(acts);
            list.appendChild(card);
        });
    }

    overlay.appendChild(list);
    document.body.appendChild(overlay);
}

function renderXanaxBody() {
    const body = panelEl.querySelector('.lt-body');
    body.innerHTML = '';

    const xanCount = xanPersonal;
    const xanCarry = cfg.getXanCarry();
    const vis = cfg.getSectionVis();

    if (vis.xanax === false) {
        body.appendChild(makeEmpty('🧪', 'Xanax section is hidden.<br>Enable it in Settings.'));
        return;
    }

    const wrap = document.createElement('div');
    wrap.style.cssText = 'padding:10px;display:flex;flex-direction:column;gap:10px;';

    function secTitle(text) {
        const t = document.createElement('div'); t.textContent = text;
        t.style.cssText = `font-size:9px;font-weight:700;letter-spacing:1.2px;text-transform:uppercase;color:${C.goldDim};margin-bottom:6px;padding-bottom:4px;border-bottom:1px solid rgba(0,140,170,0.3);font-family:Consolas,monospace;`;
        return t;
    }

    // ── Personal count card ──
    const cardSec = document.createElement('div'); cardSec.appendChild(secTitle('🧪 Personal Count'));

    // Never-scraped notice — show redirect banner if count has never been set
    const neverScraped = xanPersonal === 0 && cfg.getXanCount() === 0;
    if (neverScraped) {
        const notice = document.createElement('a');
        notice.href = 'https://www.torn.com/item.php';
        notice.style.cssText = `display:flex;align-items:center;gap:10px;padding:10px 12px;border-radius:8px;background:rgba(255,160,0,0.08);border:1px solid rgba(255,160,0,0.4);text-decoration:none !important;cursor:pointer;margin-bottom:2px;`;
        notice.innerHTML = `<span style="font-size:20px;flex-shrink:0;">📦</span>
            <div style="flex:1;">
                <div style="font-size:9.5px;font-weight:700;color:#ffaa00;font-family:Arial,sans-serif;letter-spacing:.3px;">VISIT YOUR ITEMS PAGE</div>
                <div style="font-size:8.5px;color:rgba(255,200,100,0.75);font-family:Consolas,monospace;margin-top:2px;">Tap to open Items — Sets Tracker will<br>auto-read your Xanax count on arrival.</div>
            </div>
            <span style="font-size:14px;color:rgba(255,160,0,0.6);">›</span>`;
        cardSec.appendChild(notice);
    }

    const card = document.createElement('div');
    card.style.cssText = `background:rgba(0,16,22,0.6);border:1px solid rgba(0,140,170,0.3);border-radius:8px;padding:12px;display:flex;align-items:center;gap:12px;`;

    const xImg = document.createElement('img');
    xImg.src = itemImg(XANAX_ID); xImg.alt = 'Xanax';
    xImg.style.cssText = 'width:40px;height:40px;object-fit:contain;border-radius:3px;flex-shrink:0;';

    const countRight = document.createElement('div'); countRight.style.cssText = 'flex:1;';
    const countVal = document.createElement('div');
    countVal.style.cssText = `font-size:36px;font-weight:700;color:${C.gold};font-family:Consolas,monospace;line-height:1;`;
    countVal.textContent = xanCount;
    const countSub = document.createElement('div');
    countSub.style.cssText = `font-size:9px;color:${C.textDim};font-family:Consolas,monospace;margin-top:2px;`;
    countSub.textContent = `Carry limit: ${xanCarry || '—'}`;
    countRight.appendChild(countVal); countRight.appendChild(countSub);

    card.appendChild(xImg); card.appendChild(countRight);
    cardSec.appendChild(card);

    // ── Live Data ──
    const infSec = document.createElement('div'); infSec.appendChild(secTitle('📡 Live Data'));
    const infGrid = document.createElement('div'); infGrid.style.cssText = 'display:grid;grid-template-columns:1fr 1fr;gap:8px;';
    [
        { label: 'SA Stock',  value: xanSACache.qty > 0 ? xanSACache.qty.toLocaleString() : '0',        color: xanSACache.qty > 0 ? C.okay : C.textDim },
        { label: 'SA Price',  value: xanSACache.price > 0 ? '$' + xanSACache.price.toLocaleString() : '—', color: C.gold },
        { label: 'Faction',   value: xanFacCache !== null ? xanFacCache.toLocaleString() : '—',             color: C.green },
        { label: 'Carry Lmt', value: xanCarry || '—',                                                       color: C.goldDim },
    ].forEach(item => {
        const c = document.createElement('div');
        c.style.cssText = `background:rgba(0,16,22,0.5);border:1px solid rgba(0,140,170,0.22);border-radius:6px;padding:8px 10px;text-align:center;`;
        c.innerHTML = `<div style="font-size:8.5px;color:${C.textDim};font-family:Consolas,monospace;letter-spacing:.5px;margin-bottom:3px;">${item.label}</div>
                       <div style="font-size:16px;font-weight:700;color:${item.color};font-family:Consolas,monospace;">${item.value}</div>`;
        infGrid.appendChild(c);
    });
    infSec.appendChild(infGrid);

    wrap.appendChild(cardSec); wrap.appendChild(infSec);
    renderXanRunSection(wrap, secTitle);
    body.appendChild(wrap);
}

/* ─────────────────────────────────────────
   TAB: TRAVEL
───────────────────────────────────────── */
function renderTravelBody() {
    const body = panelEl.querySelector('.lt-body');
    body.innerHTML = '';

    const vis = cfg.getSectionVis();

    const wrap = document.createElement('div');
    wrap.style.cssText = 'padding:10px;display:flex;flex-direction:column;gap:10px;';

    function secTitle(text) {
        const t = document.createElement('div'); t.textContent = text;
        t.style.cssText = `font-size:9px;font-weight:700;letter-spacing:1.2px;text-transform:uppercase;color:${C.goldDim};margin-bottom:6px;padding-bottom:4px;border-bottom:1px solid rgba(0,140,170,0.3);font-family:Consolas,monospace;`;
        return t;
    }

    // ── Loot Run Planner ──
    const escSec = document.createElement('div'); escSec.appendChild(secTitle('💰 Loot Run Planner'));

    const lootRuns = [
        {
            flag: '🇲🇽', name: 'Mexico', col: '#ffb830', time: '~1.5h',
            loot: ['Dahlia', 'Jaguar Plushie', 'Obsidian Point'],
        },
        {
            flag: '🇦🇷', name: 'Argentina', col: '#74c9ff', time: '~14h',
            loot: ['Ceibo Flower', 'Monkey Plushie', 'Chalcedony Point', 'Meteorite Fragment', 'Patagonian Fossil'],
        },
        {
            flag: '🇬🇧', name: 'UK', col: '#cc88ff', time: '~10h',
            loot: ['Heather', 'Nessie Plushie', 'Red Fox Plushie', 'Chert Point'],
        },
        {
            flag: '🇨🇦', name: 'Canada', col: '#ff7070', time: '~9h',
            loot: ['Crocus', 'Wolverine Plushie', 'Quartz Point'],
        },
        {
            flag: '🇿🇦', name: 'South Africa', col: '#60cc60', time: '~16h',
            loot: ['African Violet', 'Lion Plushie', 'Quartzite Point'],
        },
        {
            flag: '🇨🇭', name: 'Switzerland', col: '#ff9999', time: '~11h',
            loot: ['Edelweiss', 'Chamois Plushie'],
        },
        {
            flag: '🇯🇵', name: 'Japan', col: '#ffaacc', time: '~12h',
            loot: ['Cherry Blossom'],
        },
        {
            flag: '🇨🇳', name: 'China', col: '#ff6040', time: '~14h',
            loot: ['Peony', 'Panda Plushie'],
        },
        {
            flag: '🏝️', name: 'Hawaii', col: '#ffe066', time: '~6h',
            loot: ['Orchid', 'Basalt Point'],
        },
        {
            flag: '🇦🇪', name: 'UAE', col: '#88ddaa', time: '~14h',
            loot: ['Tribulus Omanense', 'Camel Plushie'],
        },
        {
            flag: '🇰🇾', name: 'Cayman Islands', col: '#66ccff', time: '~10h',
            loot: ['Banana Orchid', 'Stingray Plushie'],
        },
    ];

    lootRuns.forEach(run => {
        let needed = 0, total = 0;
        run.loot.forEach(itemName => {
            // Determine which group (and thus which section key) this item belongs to
            let groupKey = null;
            for (const [gName, g] of Object.entries(GROUPS)) {
                if (g.items[itemName]) { groupKey = gName.toLowerCase(); break; }
            }
            if (itemName === 'Meteorite Fragment' || itemName === 'Patagonian Fossil') groupKey = 'special';

            // Skip items whose section is hidden — don't count them at all
            if (groupKey && vis[groupKey] === false) return;

            total++;
            let sets = 0;
            for (const g of Object.values(GROUPS)) {
                if (g.items[itemName]) { sets = calcSet(invCache, g.items); break; }
            }
            const have = (invCache[itemName] || 0) - sets;
            if (have < 5) needed++;
        });
        run.needed = needed; run.total = total;
    });

    // Focus mode: active run beats personal priority
    const runs          = cfg.getXanRuns();
    const activeRun     = runs.find(r => r.active);
    const xanPriority   = cfg.getXanPriority();
    const xanThreshold  = cfg.getXanThreshold();
    const xanBelowThresh = xanPriority && xanPersonal < xanThreshold;
    const focusMode     = !!(activeRun || xanBelowThresh);

    lootRuns.sort((a, b) => {
        if (focusMode) {
            const aIsSA = a.name === 'South Africa';
            const bIsSA = b.name === 'South Africa';
            if (aIsSA && !bIsSA) return -1;
            if (bIsSA && !aIsSA) return  1;
        }
        return b.needed - a.needed;
    });

    const runGrid = document.createElement('div'); runGrid.style.cssText = 'display:flex;flex-direction:column;gap:5px;';

    // Focus banner
    if (focusMode) {
        const bannerCol = activeRun ? '#00c8e0' : C.green;
        const xanBanner = document.createElement('div');
        xanBanner.style.cssText = `display:flex;align-items:center;gap:8px;padding:7px 10px;border-radius:6px;background:${bannerCol}12;border:1px solid ${bannerCol}44;margin-bottom:2px;`;
        if (activeRun) {
            const total     = activeRun.trips.reduce((s,t)=>s+t.bought,0);
            const remaining = Math.max(0, activeRun.contractQty - total);
            xanBanner.innerHTML = `<span style="font-size:14px;">✈</span><div style="flex:1;"><div style="font-size:9.5px;font-weight:700;color:${bannerCol};font-family:Arial,sans-serif;">RUN: ${activeRun.client}</div><div style="font-size:8.5px;color:${bannerCol}99;font-family:Consolas,monospace;margin-top:1px;">${total} collected · ${remaining} needed · SA only</div></div>`;
        } else {
            xanBanner.innerHTML = `<span style="font-size:14px;">🧪</span><div style="flex:1;"><div style="font-size:9.5px;font-weight:700;color:${bannerCol};font-family:Arial,sans-serif;">Xanax Priority Active</div><div style="font-size:8.5px;color:${C.textDim};font-family:Consolas,monospace;margin-top:1px;">South Africa only — ${xanPersonal} / ${xanThreshold} collected</div></div>`;
        }
        runGrid.appendChild(xanBanner);
    }

    lootRuns.forEach(run => {
        if (run.total === 0) return; // all items in this country are from hidden sections — skip
        // Focus mode: only show South Africa
        if (focusMode && run.name !== 'South Africa') return;
        const urgency = run.needed / run.total;
        const isSAxanPinned = focusMode && run.name === 'South Africa';

        const a = document.createElement('div');
        // SA xanax-pinned: always full opacity with green xanax border, even if loot is complete
        const borderCol = isSAxanPinned ? 'rgba(102,187,102,0.7)' : `${run.col}${urgency > 0.5 ? '66' : '28'}`;
        const bgCol     = isSAxanPinned ? 'rgba(102,187,102,0.1)' : `${run.col}${urgency > 0.5 ? '14' : '07'}`;
        const opacity   = (urgency === 0 && !isSAxanPinned) ? '0.35' : '1';
        a.style.cssText = `display:flex;align-items:center;gap:8px;padding:8px 10px;border-radius:7px;cursor:pointer;border:1px solid ${borderCol};background:${bgCol};transition:opacity 0.2s;opacity:${opacity};`;
        a.addEventListener('mouseover', () => { if (urgency > 0 || isSAxanPinned) a.style.opacity = '0.8'; });
        a.addEventListener('mouseout',  () => { a.style.opacity = opacity; });
        a.onclick = function() {
            var FULL_NAMES = { 'UK': 'United Kingdom' };
            var travelName = FULL_NAMES[run.name] || run.name;
            var onTravelPage = (location.pathname + location.search).indexOf('sid=travel') !== -1;
            if (onTravelPage) {
                clickCountry(travelName);
            } else {
                window.location.href = 'https://www.torn.com/page.php?sid=travel#lt_travel=' + encodeURIComponent(travelName);
            }
        };

        const flag = document.createElement('span'); flag.textContent = run.flag; flag.style.cssText = 'font-size:16px;flex-shrink:0;';

        const mid = document.createElement('div'); mid.style.cssText = 'flex:1;min-width:0;';
        const nameRow = document.createElement('div'); nameRow.style.cssText = 'display:flex;align-items:baseline;gap:5px;';
        const nameEl  = document.createElement('span'); nameEl.textContent = run.name; nameEl.style.cssText = `font-size:10.5px;font-weight:700;color:${isSAxanPinned ? C.green : run.col};font-family:Arial,sans-serif;`;
        const timeEl  = document.createElement('span'); timeEl.textContent = run.time; timeEl.style.cssText = `font-size:8.5px;color:${C.textDim};font-family:Consolas,monospace;`;
        nameRow.appendChild(nameEl); nameRow.appendChild(timeEl);
        if (isSAxanPinned) {
            const xanTag = document.createElement('span');
            xanTag.textContent = '🧪 Xanax';
            xanTag.style.cssText = `font-size:7.5px;font-family:Consolas,monospace;font-weight:700;color:${C.green};background:rgba(102,187,102,0.15);border:1px solid rgba(102,187,102,0.45);border-radius:3px;padding:1px 5px;margin-left:2px;`;
            nameRow.appendChild(xanTag);
        }

        const tagWrap = document.createElement('div'); tagWrap.style.cssText = 'display:flex;flex-wrap:wrap;gap:3px;margin-top:3px;';
        run.loot.forEach(itemName => {
            // In focus mode on SA: only show Xanax tag
            if (focusMode && isSAxanPinned && itemName !== 'Xanax') return;
            // Skip items whose section is hidden
            let groupKey = null;
            for (const [gName, g] of Object.entries(GROUPS)) { if (g.items[itemName]) { groupKey = gName.toLowerCase(); break; } }
            if (itemName === 'Meteorite Fragment' || itemName === 'Patagonian Fossil') groupKey = 'special';
            if (groupKey && vis[groupKey] === false) return;

            let sets = 0;
            for (const g of Object.values(GROUPS)) { if (g.items[itemName]) { sets = calcSet(invCache, g.items); break; } }
            const have     = (invCache[itemName] || 0) - sets;
            // In focus mode never highlight xanax as "needed" based on loot surplus — run progress handles that
            const isNeeded = focusMode ? false : have < 5;
            const tag = document.createElement('span');
            tag.textContent = itemName;
            tag.style.cssText = isNeeded
                ? `font-size:8px;font-family:Consolas,monospace;color:#ff9966;background:rgba(200,80,20,0.2);border:1px solid rgba(200,80,20,0.45);border-radius:3px;padding:1px 4px;`
                : `font-size:8px;font-family:Consolas,monospace;color:${C.textDim};background:rgba(80,60,0,0.15);border:1px solid rgba(100,80,0,0.18);border-radius:3px;padding:1px 4px;opacity:0.5;`;
            tagWrap.appendChild(tag);
        });
        mid.appendChild(nameRow); mid.appendChild(tagWrap);

        const badge = document.createElement('div'); badge.style.cssText = 'flex-shrink:0;text-align:center;';
        if (isSAxanPinned) {
            // Focus mode: show trips needed
            const carry = cfg.getXanCarry() || 1;
            let xanNeeded = 0;
            if (activeRun) {
                const collected = activeRun.trips.reduce((s,t)=>s+t.bought,0);
                xanNeeded = Math.max(0, activeRun.contractQty - collected);
            } else {
                xanNeeded = Math.max(0, xanThreshold - xanPersonal);
            }
            const tripsExact  = xanNeeded / carry;
            const tripsNeeded = Math.ceil(tripsExact);
            const overage     = tripsNeeded > 0 ? (tripsNeeded * carry) - xanNeeded : 0;
            if (xanNeeded <= 0) {
                badge.innerHTML = `<div style="font-size:11px;color:${C.green};font-family:Consolas,monospace;">✓</div>`;
            } else {
                badge.innerHTML = `<div style="font-size:16px;font-weight:700;color:#00c8e0;font-family:Consolas,monospace;line-height:1;">${tripsNeeded}</div>
                    <div style="font-size:7px;color:${C.textDim};font-family:Consolas,monospace;">trips</div>
                    ${overage > 0 ? `<div style="font-size:7px;color:${C.goldDim};font-family:Consolas,monospace;margin-top:1px;">+${overage} over</div>` : ''}`;
            }
        } else if (run.needed > 0) {
            badge.innerHTML = `<div style="font-size:14px;font-weight:700;color:${run.col};font-family:Consolas,monospace;line-height:1;">${run.needed}</div><div style="font-size:7.5px;color:${C.textDim};font-family:Consolas,monospace;">needed</div>`;
        } else {
            badge.innerHTML = `<div style="font-size:11px;color:${C.green};font-family:Consolas,monospace;">✓</div>`;
        }

        a.appendChild(flag); a.appendChild(mid); a.appendChild(badge);
        runGrid.appendChild(a);
    });

    escSec.appendChild(runGrid);

    // ── Best items to carry per country ──
    const carSec = document.createElement('div'); carSec.appendChild(secTitle('🎯 Best Items per Country'));
    const countryGuide = [
        { flag: '🇲🇽', name: 'Mexico',        items: ['Dahlia', 'Jaguar Plushie', 'Obsidian Point'] },
        { flag: '🏝️',  name: 'Hawaii',         items: ['Orchid', 'Basalt Point'] },
        { flag: '🇿🇦', name: 'South Africa',   items: ['African Violet', 'Lion Plushie', 'Quartzite Point'] },
        { flag: '🇯🇵', name: 'Japan',          items: ['Cherry Blossom'] },
        { flag: '🇨🇳', name: 'China',          items: ['Peony', 'Panda Plushie'] },
        { flag: '🇦🇷', name: 'Argentina',      items: ['Ceibo Flower', 'Monkey Plushie', 'Chalcedony Point', 'Meteorite Fragment', 'Patagonian Fossil'] },
        { flag: '🇨🇭', name: 'Switzerland',    items: ['Edelweiss', 'Chamois Plushie'] },
        { flag: '🇨🇦', name: 'Canada',         items: ['Crocus', 'Wolverine Plushie', 'Quartz Point'] },
        { flag: '🇬🇧', name: 'United Kingdom', items: ['Heather', 'Nessie Plushie', 'Red Fox Plushie', 'Chert Point'] },
        { flag: '🇦🇪', name: 'UAE',            items: ['Tribulus Omanense', 'Camel Plushie'] },
        { flag: '🇰🇾', name: 'Cayman Islands', items: ['Banana Orchid', 'Stingray Plushie'] },
        { flag: '🏪',  name: "Bits n' Bobs",   items: ['Sheep Plushie', 'Teddy Bear Plushie', 'Kitten Plushie'] },
    ];

    countryGuide.forEach(({ flag, name, items: itemNames }) => {
        // Filter out items from hidden sections
        const visibleItems = itemNames.filter(iName => {
            let groupKey = null;
            for (const [gName, g] of Object.entries(GROUPS)) { if (g.items[iName]) { groupKey = gName.toLowerCase(); break; } }
            if (iName === 'Meteorite Fragment' || iName === 'Patagonian Fossil') groupKey = 'special';
            return !(groupKey && vis[groupKey] === false);
        });
        if (!visibleItems.length) return; // entire country hidden — skip row

        const row = document.createElement('div');
        row.style.cssText = `display:flex;gap:8px;padding:5px 0;border-bottom:1px solid rgba(0,80,100,0.2);align-items:flex-start;`;

        const flagEl = document.createElement('div');
        flagEl.textContent = flag; flagEl.style.cssText = 'font-size:16px;flex-shrink:0;padding-top:1px;';

        const nameEl = document.createElement('div');
        nameEl.style.cssText = `font-size:10px;font-weight:700;color:${C.text};font-family:Arial,sans-serif;flex-shrink:0;min-width:80px;`;
        nameEl.textContent = name;

        const tagsWrap = document.createElement('div'); tagsWrap.style.cssText = 'display:flex;flex-wrap:wrap;gap:3px;flex:1;';
        visibleItems.forEach(iName => {
            const tag = document.createElement('span');
            tag.style.cssText = `font-size:8.5px;font-family:Consolas,monospace;color:${C.goldDim};background:rgba(120,90,0,0.18);border:1px solid rgba(140,110,0,0.25);border-radius:3px;padding:1px 5px;`;
            tag.textContent = iName;
            tagsWrap.appendChild(tag);
        });

        row.appendChild(flagEl); row.appendChild(nameEl); row.appendChild(tagsWrap);
        carSec.appendChild(row);
    });

    wrap.appendChild(escSec); wrap.appendChild(carSec);
    body.appendChild(wrap);
}

/* ─────────────────────────────────────────
   STATUS BAR
───────────────────────────────────────── */
function renderStatusBar() {
    const bar = panelEl ? panelEl.querySelector('#lt-sbar') : null;
    if (!bar) return;
    bar.innerHTML = '';

    if (activeTab === 'sets') {
        const vis = cfg.getSectionVis();
        let totalSets = 0, totalPts = 0;
        Object.entries(GROUPS).forEach(([n, g]) => {
            if (vis[n.toLowerCase()] === false) return;
            const s = calcSet(invCache, g.items); totalSets += s; totalPts += s * g.pts;
        });
        // Special items: each individual item counts toward pts (no "set" mechanic)
        if (vis.special !== false) {
            Object.entries(SPECIAL_ITEMS).forEach(([name, data]) => {
                const qty = invCache[name] || 0;
                totalPts += qty * data.pts;
                totalSets += qty; // each special item counts as its own unit
            });
        }
        const sSpan = document.createElement('span'); sSpan.style.cssText = `color:${C.goldDim};font-weight:700;`; sSpan.textContent = totalSets + ' sets · ' + totalPts + ' pts';
        bar.appendChild(sSpan);
        if (pointsPrice > 0 && totalPts > 0) {
            const pv    = totalPts * pointsPrice;
            const pvFmt = pv >= 1e6 ? `$${(pv/1e6).toFixed(1)}M` : pv >= 1000 ? `$${Math.round(pv/1000)}k` : `$${pv}`;
            const vSpan = document.createElement('span');
            vSpan.style.cssText = `margin-left:auto;color:${C.gold};font-weight:700;`;
            vSpan.title = `${totalPts} pts × $${pointsPrice.toLocaleString()} per pt`;
            vSpan.textContent = pvFmt;
            bar.appendChild(vSpan);
        }
    } else if (activeTab === 'xanax') {
        const span = document.createElement('span'); span.style.cssText = `color:${C.goldDim};font-weight:700;`;
        span.textContent = 'Personal: ' + (xanPersonal) + ' · Faction: ' + (xanFacCache !== null ? xanFacCache : '—');
        bar.appendChild(span);
    } else if (activeTab === 'travel') {
        const span = document.createElement('span'); span.style.cssText = `color:${C.goldDim};`;
        span.textContent = 'Loot runs ranked by items needed';
        bar.appendChild(span);
    }

    if (isLoading) {
        const sp = document.createElement('span'); sp.className = 'lt-spin'; sp.textContent = '◌';
        sp.style.cssText = `margin-left:auto;color:${C.goldDim};`;
        bar.appendChild(sp);
    }
}

/* ─────────────────────────────────────────
   MAIN PANEL RENDER
───────────────────────────────────────── */
function renderPanel() {
    if (!panelEl) return;
    syncTheme();
    renderStatusBar();
    if (activeTab === 'sets')   renderSetsBody();
    if (activeTab === 'xanax')  renderXanaxBody();
    if (activeTab === 'travel') renderTravelBody();
}

/* ─────────────────────────────────────────
   BUILD PANEL
───────────────────────────────────────── */
function buildPanel() {
    const light = isLightMode();
    const panelBg = light
        ? 'linear-gradient(158deg,rgba(240,253,255,0.99),rgba(225,248,252,0.98))'
        : 'linear-gradient(158deg,rgba(0,14,18,0.98),rgba(0,8,12,0.97))';
    const tabDimCol = light ? 'rgba(0,100,130,0.4)' : 'rgba(0,160,190,0.45)';

    panelEl.setAttribute('style',
        'position:fixed !important;width:0 !important;opacity:0 !important;' +
        'max-height:84vh !important;' +
        `background:${panelBg} !important;` +
        `border:1px solid ${C.border} !important;` +
        'border-radius:11px !important;z-index:999989 !important;' +
        'display:flex !important;flex-direction:column !important;' +
        `box-shadow:0 10px 44px ${light ? 'rgba(0,0,0,0.25)' : 'rgba(0,0,0,0.9)'} !important;` +
        'backdrop-filter:blur(14px) !important;overflow:hidden !important;' +
        'transition:width 0.26s ease,opacity 0.2s ease !important;' +
        `font-family:Arial,sans-serif !important;color:${C.text} !important;`
    );

    // ── HEADER ──
    const hdr = document.createElement('div');
    hdr.setAttribute('style', `padding:8px 12px;background:${C.settHdr};border-bottom:1px solid ${C.border};display:flex;align-items:center;gap:7px;flex-shrink:0;`);

    const titleEl = document.createElement('span');
    titleEl.setAttribute('style', `font-size:10.5px;font-weight:700;letter-spacing:1.5px;text-transform:uppercase;color:${C.gold};flex:1;white-space:nowrap;font-family:Arial,sans-serif;`);
    titleEl.innerHTML = '<span class="lt-float" style="display:inline-block;margin-right:4px;">✈</span> Sets Tracker';

    function iconBtn(svg, title) {
        const b = document.createElement('span'); b.title = title; b.innerHTML = svg;
        b.setAttribute('style', `width:20px;height:20px;cursor:pointer;flex-shrink:0;display:flex;align-items:center;justify-content:center;color:${C.goldDim};transition:color 0.18s;`);
        b.addEventListener('mouseover', () => b.style.color = C.gold);
        b.addEventListener('mouseout',  () => b.style.color = C.goldDim);
        return b;
    }
    const SVG_REFRESH = '<svg width="14" height="14" viewBox="0 0 16 16" fill="none"><path d="M13.5 8A5.5 5.5 0 1 1 8 2.5c1.8 0 3.4.87 4.4 2.2" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/><polyline points="11,1 13.5,3.5 11,6" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>';
    const SVG_GEAR    = '<svg width="14" height="14" viewBox="0 0 16 16" fill="none"><circle cx="8" cy="8" r="2.5" stroke="currentColor" stroke-width="1.4"/><path d="M8 1v2M8 13v2M1 8h2M13 8h2M2.93 2.93l1.41 1.41M11.66 11.66l1.41 1.41M2.93 13.07l1.41-1.41M11.66 4.34l1.41-1.41" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/></svg>';

    const btnRefresh  = iconBtn(SVG_REFRESH, 'Refresh data');
    const btnSettings = iconBtn(SVG_GEAR,    'Settings');

    btnRefresh.addEventListener('click', () => {
        invCache = {}; abroadCache = {}; xanSACache = { qty:0, price:0 }; xanFacCache = null; xanPersonal = 0; // bobCache intentionally kept — shows last known BoB stock while refreshing
        renderPanel(); refreshAll(); toast('Refreshing…', 1500);
    });
    btnSettings.addEventListener('click', openSettings);

    hdr.appendChild(titleEl); hdr.appendChild(btnRefresh); hdr.appendChild(btnSettings);

    // ── TABS ──
    const tabs = document.createElement('div');
    tabs.setAttribute('style', `display:flex;flex-shrink:0;border-bottom:1px solid ${C.border};`);

    function makeTab(label, key) {
        const t = document.createElement('div');
        t.textContent = label; t.dataset.tab = key;
        t.setAttribute('style', `flex:1;padding:6px 3px;font-size:9.5px;font-weight:700;letter-spacing:.6px;text-transform:uppercase;color:${tabDimCol};cursor:pointer;text-align:center;border-bottom:2px solid transparent;transition:all 0.2s;user-select:none;font-family:Arial,sans-serif;`);
        t.addEventListener('mouseover', () => { if (!t.classList.contains('lt-tab-active')) t.style.color = C.gold; });
        t.addEventListener('mouseout',  () => { if (!t.classList.contains('lt-tab-active')) t.style.color = tabDimCol; });
        t.addEventListener('click', () => {
            tabs.querySelectorAll('[data-tab]').forEach(x => {
                x.classList.remove('lt-tab-active');
                x.style.color = tabDimCol;
                x.style.borderBottomColor = 'transparent';
                x.style.background = 'transparent';
            });
            t.classList.add('lt-tab-active');
            t.style.color = C.gold;
            t.style.borderBottomColor = C.gold;
            t.style.background = C.goldGlow;
            activeTab = key;
            renderPanel();
        });
        return t;
    }

    const tabSets   = makeTab('🎒 Sets',   'sets');
    const tabXanax  = makeTab('🧪 Xanax',  'xanax');
    const tabTravel = makeTab('✈ Travel',  'travel');

    tabSets.classList.add('lt-tab-active');
    tabSets.style.color = C.gold; tabSets.style.borderBottomColor = C.gold; tabSets.style.background = C.goldGlow;
    tabs.appendChild(tabSets); tabs.appendChild(tabXanax); tabs.appendChild(tabTravel);

    // ── STATUS BAR ──
    const sbar = document.createElement('div');
    sbar.id = 'lt-sbar';
    sbar.setAttribute('style', `padding:3px 10px;font-size:9px;letter-spacing:.4px;color:${C.textDim};border-bottom:1px solid ${C.border};display:flex;gap:8px;align-items:center;flex-shrink:0;font-family:Consolas,monospace;`);
    sbar.textContent = 'Loading…';

    // ── BODY ──
    const bdy = document.createElement('div'); bdy.className = 'lt-body';

    panelEl.appendChild(hdr);
    panelEl.appendChild(tabs);
    panelEl.appendChild(sbar);
    panelEl.appendChild(bdy);
}

/* ─────────────────────────────────────────
   TOGGLE BUTTON
───────────────────────────────────────── */
function buildToggle() {
    const SVG = `<svg width="28" height="28" viewBox="0 0 44 44" fill="none">
        <path d="M6 22 L38 10 L32 22 L38 34 Z" fill="rgba(0,200,224,0.18)" stroke="rgba(0,200,224,0.8)" stroke-width="1.3" stroke-linejoin="round"/>
        <path d="M20 22 L32 22 L38 34 L20 28 Z" fill="rgba(0,200,224,0.12)" stroke="rgba(0,200,224,0.55)" stroke-width="1" stroke-linejoin="round"/>
        <path d="M18 16 L26 12 L28 18 L18 22 Z" fill="rgba(0,200,224,0.15)" stroke="rgba(0,190,220,0.6)" stroke-width="0.9" stroke-linejoin="round"/>
        <circle cx="14" cy="22" r="2" fill="rgba(0,200,224,0.7)"/>
    </svg>`;

    toggleEl.innerHTML = SVG;
    toggleEl.title = 'Loot Tracker · Right-click = Settings';
    const toggleBg = isLightMode()
        ? 'radial-gradient(circle at 38% 34%,#d0f8ff,#a8eef8)'
        : 'radial-gradient(circle at 38% 34%,#001a20,#000c10)';
    toggleEl.setAttribute('style',
        'position:fixed !important;width:46px !important;height:46px !important;' +
        `background:${toggleBg} !important;` +
        `border:2px solid ${C.border} !important;` +
        'border-radius:50% !important;' +
        'display:flex !important;align-items:center !important;justify-content:center !important;' +
        'cursor:grab !important;z-index:999990 !important;' +
        `box-shadow:0 0 16px ${C.goldGlow},inset 0 0 12px rgba(0,0,0,0.2) !important;` +
        'user-select:none !important;touch-action:none !important;overflow:visible !important;'
    );
    toggleEl.addEventListener('mouseover', () => {
        toggleEl.style.setProperty('box-shadow', `0 0 24px ${C.gold}aa,inset 0 0 12px rgba(0,0,0,0.2)`, 'important');
        toggleEl.style.setProperty('border-color', C.gold, 'important');
    });
    toggleEl.addEventListener('mouseout', () => {
        toggleEl.style.setProperty('box-shadow', `0 0 16px ${C.goldGlow},inset 0 0 12px rgba(0,0,0,0.2)`, 'important');
        toggleEl.style.setProperty('border-color', C.border, 'important');
    });
}

/* ─────────────────────────────────────────
   PANEL OPEN/CLOSE + POSITION
───────────────────────────────────────── */
function positionPanel() {
    const tr = toggleEl.getBoundingClientRect();
    panelEl.style.setProperty('top', Math.min(tr.top, window.innerHeight - 80) + 'px', 'important');
    panelEl.style.setProperty('bottom', 'auto', 'important');
    if (toggleEl._side === 'left') {
        panelEl.style.setProperty('left',  (tr.right + 7) + 'px', 'important');
        panelEl.style.setProperty('right', 'auto', 'important');
    } else {
        panelEl.style.setProperty('right', (window.innerWidth - tr.left + 7) + 'px', 'important');
        panelEl.style.setProperty('left',  'auto', 'important');
    }
}

function openPanel()  { panelOpen = true;  panelEl.style.setProperty('width','310px','important'); panelEl.style.setProperty('opacity','1','important'); positionPanel(); renderPanel(); }
function closePanel() { panelOpen = false; panelEl.style.setProperty('width','0','important');     panelEl.style.setProperty('opacity','0','important'); }

/* ─────────────────────────────────────────
   DRAG TO SNAP
───────────────────────────────────────── */
function setupDrag() {
    const SZ = 46, EDGE = 6;

    function snap(side, top) {
        toggleEl._side = side;
        const t = Math.max(EDGE, Math.min(top, window.innerHeight - SZ - EDGE));
        toggleEl.style.setProperty('top',    t + 'px', 'important');
        toggleEl.style.setProperty('bottom', 'auto', 'important');
        toggleEl.style.setProperty('left',   side === 'left'  ? EDGE + 'px' : 'auto', 'important');
        toggleEl.style.setProperty('right',  side === 'right' ? EDGE + 'px' : 'auto', 'important');
        if (panelOpen) positionPanel();
    }

    try {
        const saved = JSON.parse(store.get('lt_pos', '{}'));
        snap(saved.side || 'right', saved.top || 280);
    } catch(e) { snap('right', 280); }

    let dragging = false, moved = false, sX, sY, sL, sT;

    function start(cx, cy) { moved = false; dragging = true; sX = cx; sY = cy; const r = toggleEl.getBoundingClientRect(); sL = r.left; sT = r.top; toggleEl.style.setProperty('opacity','0.7','important'); toggleEl.style.setProperty('transform','scale(1.1)','important'); }
    function move(cx, cy) {
        if (!dragging) return;
        const dx = cx - sX, dy = cy - sY;
        if (!moved && Math.hypot(dx, dy) < 5) return;
        moved = true;
        toggleEl.style.setProperty('left',   Math.max(EDGE, Math.min(sL + dx, window.innerWidth  - SZ - EDGE)) + 'px', 'important');
        toggleEl.style.setProperty('right',  'auto', 'important');
        toggleEl.style.setProperty('top',    Math.max(EDGE, Math.min(sT + dy, window.innerHeight - SZ - EDGE)) + 'px', 'important');
        toggleEl.style.setProperty('bottom', 'auto', 'important');
        if (panelOpen) positionPanel();
    }
    function end() {
        if (!dragging) return; dragging = false;
        toggleEl.style.setProperty('opacity','1','important'); toggleEl.style.setProperty('transform','none','important');
        if (!moved) return;
        const r    = toggleEl.getBoundingClientRect();
        const side = (r.left + SZ / 2) < window.innerWidth / 2 ? 'left' : 'right';
        snap(side, r.top);
        store.set('lt_pos', JSON.stringify({ side, top: r.top }));
    }

    toggleEl.addEventListener('mousedown',  e => { if (e.button !== 0) return; start(e.clientX, e.clientY); });
    document.addEventListener('mousemove',  e => move(e.clientX, e.clientY));
    document.addEventListener('mouseup',    end);
    toggleEl.addEventListener('touchstart', e => { const t = e.touches[0]; start(t.clientX, t.clientY); }, { passive: true });
    toggleEl.addEventListener('touchmove',  e => { if (!dragging) return; e.preventDefault(); const t = e.touches[0]; move(t.clientX, t.clientY); }, { passive: false });
    toggleEl.addEventListener('touchend',   end);

    toggleEl.addEventListener('click',       () => { if (moved) return; if (panelOpen) closePanel(); else openPanel(); });
    toggleEl.addEventListener('contextmenu', e => { e.preventDefault(); openSettings(); });

    /* ── Carousel Tooltip ── */
    (function buildCarousel() {
        const slides = [
            { icon: '✈️', title: 'Sets Tracker',       body: 'Your overseas sets companion. Track Plushie, Flower, Prehistoric & Special sets, live Xanax data, and plan supply runs — all in one panel.' },
            { icon: '🎒', title: 'Sets Tab',            body: 'Shows every item sorted by bottleneck. Own = extras after completing sets. Abroad = live overseas stock from YATA. When a run or priority is active, all groups collapse to show only Xanax.' },
            { icon: '🟢', title: 'Stock Colours',       body: 'Green = stock above threshold (Plushie ≥2000 / Flower ≥5000). Orange = below threshold. Red = zero stock abroad. 🏪 = Bits n\' Bobs live stock count.' },
            { icon: '🧪', title: 'Xanax Tab',           body: 'Personal count is auto-read when you visit your Items page — no manual entry needed. Shows carry limit, SA live stock & price, and faction Xanax supply.' },
            { icon: '✈',  title: 'Xanax Runs',          body: 'Start a supply run contract from the Xanax tab. Set a client name, total qty, and sell price. Log each SA trip with one tap — the tracker counts progress, calculates profit vs SA buy price, and tracks payment.' },
            { icon: '📊', title: 'Run Focus Mode',      body: 'When a run is active, Sets and Travel tabs collapse to show only South Africa and Xanax. The SA card shows trips needed (contract ÷ carry limit) with overage. Ends automatically when you tap ✓ End.' },
            { icon: '🎯', title: 'Xanax Priority',      body: 'Enable Xanax Priority in Settings to pin South Africa to the top of Travel and collapse Sets to Xanax only — until your personal count hits your set threshold.' },
            { icon: '✈',  title: 'Travel Tab',          body: 'Loot Run Planner ranks all destinations by items needed. In focus mode only SA shows, with a trips-needed badge. Best Items section shows the top loot per country for quick reference.' },
            { icon: '🔑', title: 'API Key',             body: 'Requires a Full Access key from Torn for faction drug data, and a Limited Access (Display) key for inventory. Both stay on your device.' },
            { icon: '🔄', title: 'Manual Refresh',      body: 'Tap ↺ in the panel header to re-fetch inventory, YATA overseas stock, BoB shop stock, faction Xanax, and points price all at once.' },
            { icon: '📱', title: 'PDA Compatible',      body: 'Built for Torn PDA on mobile. Drag the ✈ button anywhere on screen — it snaps to the nearest edge. Works in browser too.' },
            { icon: '⚙',  title: 'Settings',            body: 'Right-click the ✈ button (or tap ⚙ in the header). Control API key, User ID, section visibility, carry limit, Xanax priority threshold, and tooltip.' },
        ];

        let cur = 0;
        const tip = document.createElement('div');
        tip.id = 'lt-carousel';
        tip.setAttribute('style',
            'position:fixed !important;bottom:72px !important;right:12px !important;' +
            'width:234px !important;' +
            'background:linear-gradient(145deg,rgba(0,12,18,0.97),rgba(0,8,14,0.97)) !important;' +
            'border:1px solid rgba(0,180,210,0.45) !important;border-radius:10px !important;' +
            'z-index:999995 !important;padding:12px 14px 10px !important;' +
            'box-shadow:0 6px 28px rgba(0,0,0,0.85) !important;' +
            'font-family:Arial,sans-serif !important;color:#e8f0d0 !important;' +
            'opacity:0 !important;transition:opacity 0.35s ease !important;'
        );

        const iconEl   = document.createElement('div');
        const titleEl  = document.createElement('div');
        const bodyEl   = document.createElement('div');
        const dotsWrap = document.createElement('div');
        const navWrap  = document.createElement('div');

        iconEl.setAttribute('style',
            'font-size:24px !important;margin-bottom:5px !important;line-height:1 !important;');
        titleEl.setAttribute('style',
            'font-size:11px !important;font-weight:700 !important;color:#00c8e0 !important;' +
            'margin-bottom:5px !important;letter-spacing:.5px !important;text-transform:uppercase !important;');
        bodyEl.setAttribute('style',
            'font-size:10.5px !important;line-height:1.55 !important;' +
            'color:rgba(190,240,248,0.88) !important;min-height:48px !important;');
        dotsWrap.setAttribute('style',
            'display:flex !important;justify-content:center !important;gap:6px !important;' +
            'margin-top:2px !important;align-items:center !important;');
        navWrap.setAttribute('style',
            'display:flex !important;justify-content:space-between !important;' +
            'align-items:center !important;margin-top:9px !important;');

        function makeDot(i) {
            const d = document.createElement('div');
            d.setAttribute('style',
                'width:6px !important;height:6px !important;border-radius:50% !important;' +
                'cursor:pointer !important;flex-shrink:0 !important;transition:background 0.2s !important;' +
                'background:' + (i === cur ? 'rgba(0,200,224,0.9)' : 'rgba(120,120,120,0.3)') + ' !important;'
            );
            (function(idx) { d.addEventListener('click', function() { clearInterval(autoTimer); go(idx); }); })(i);
            return d;
        }

        function renderDots() {
            dotsWrap.innerHTML = '';
            for (let i = 0; i < slides.length; i++) dotsWrap.appendChild(makeDot(i));
        }

        function go(n) {
            cur = (n + slides.length) % slides.length;
            iconEl.textContent  = slides[cur].icon;
            titleEl.textContent = slides[cur].title;
            bodyEl.textContent  = slides[cur].body;
            renderDots();
        }

        const prevBtn = document.createElement('div');
        prevBtn.textContent = '◀';
        prevBtn.setAttribute('style',
            'cursor:pointer !important;font-size:12px !important;color:rgba(0,190,215,0.8) !important;' +
            'padding:3px 8px !important;user-select:none !important;border-radius:4px !important;' +
            'border:1px solid rgba(0,170,205,0.3) !important;'
        );
        prevBtn.addEventListener('click', function() { clearInterval(autoTimer); go(cur - 1); });

        const nextBtn = document.createElement('div');
        nextBtn.textContent = '▶';
        nextBtn.setAttribute('style',
            'cursor:pointer !important;font-size:12px !important;color:rgba(0,190,215,0.8) !important;' +
            'padding:3px 8px !important;user-select:none !important;border-radius:4px !important;' +
            'border:1px solid rgba(0,170,205,0.3) !important;'
        );
        nextBtn.addEventListener('click', function() { clearInterval(autoTimer); go(cur + 1); });

        const closeBtn = document.createElement('div');
        closeBtn.textContent = '✕';
        closeBtn.setAttribute('style',
            'position:absolute !important;top:8px !important;right:10px !important;' +
            'cursor:pointer !important;font-size:11px !important;' +
            'color:rgba(0,190,215,0.65) !important;line-height:1 !important;user-select:none !important;'
        );
        closeBtn.addEventListener('click', function() {
            clearInterval(autoTimer);
            tip.style.setProperty('opacity', '0', 'important');
            setTimeout(function() { if (tip.parentNode) tip.parentNode.removeChild(tip); }, 350);
        });

        navWrap.appendChild(prevBtn);
        navWrap.appendChild(dotsWrap);
        navWrap.appendChild(nextBtn);

        tip.appendChild(closeBtn);
        tip.appendChild(iconEl);
        tip.appendChild(titleEl);
        tip.appendChild(bodyEl);
        tip.appendChild(navWrap);

        go(0);

        let autoTimer = setInterval(function() { go(cur + 1); }, 5000);

        // Only show once — persisted in localStorage
        const SEEN_KEY = 'lt_tooltip_seen';
        let alreadySeen = false;
        try { alreadySeen = !!localStorage.getItem(SEEN_KEY); } catch(e) {}
        if (alreadySeen) return;

        document.body.appendChild(tip);
        setTimeout(function() { tip.style.setProperty('opacity', '1', 'important'); }, 1800);

        function markSeen() {
            try { localStorage.setItem(SEEN_KEY, '1'); } catch(e) {}
        }
        closeBtn.addEventListener('click', markSeen);

        // Also mark seen after cycling through all slides once
        let seenCount = 0;
        const origGo = go;
        go = function(n) {
            origGo(n);
            seenCount++;
            if (seenCount >= slides.length) markSeen();
        };
    })();
}

/* ─────────────────────────────────────────
   REFRESH
───────────────────────────────────────── */
async function refreshAll() {
    isLoading = true;
    renderStatusBar();

    await Promise.allSettled([
        fetchInventory()
            .then(d  => { invCache = d; })
            .catch(() => {}),
        // xanPersonal is scraped from items page via watchItemsPage()
        fetchYataData()
            .then(result => {
                if (result && result.map)  abroadCache = result.map;
                if (result && result.sa)   xanSACache  = result.sa;
                console.log('[SetsTracker] xanSACache set:', JSON.stringify(xanSACache));
            })
            .catch(e => { console.warn('[SetsTracker] YATA assign failed:', e); }),
        fetchBobStock()
            .then(d  => { bobCache = d; })
            .catch(() => {}),
        fetchXanaxFaction()
            .then(d  => { if (d !== null) xanFacCache = d; })
            .catch(() => {}),
        fetchPointsPrice()
            .then(p  => { pointsPrice = p; })
            .catch(() => {}),
    ]);

    isLoading = false;
    if (panelEl) renderPanel();
}

async function mainLoop() {
    // ToS: only poll while the user has this page actively open
    if (document.visibilityState === 'visible') {
        await refreshAll();
    }
    pollTimer = setTimeout(mainLoop, 45000);
}

/* ─────────────────────────────────────────
   INIT
───────────────────────────────────────── */

// Click a country row on the travel page — works in both TornPDA and Tampermonkey
function clickCountry(name) {
    var tLow = name.toLowerCase();
    var doc = (typeof unsafeWindow !== 'undefined' && unsafeWindow.document) ? unsafeWindow.document : document;
    var cells = doc.querySelectorAll('div[class*="flagAndName"]');
    for (var i = 0; i < cells.length; i++) {
        var txt = (cells[i].textContent || '').trim().toLowerCase();
        if (txt.indexOf(tLow) !== -1) {
            var parent = cells[i].parentElement;
            if (parent) { parent.click(); return true; }
        }
    }
    return false;
}

function init() {
    xanPersonal = cfg.getXanCount(); // restore last known count from previous scrape
    // Auto-click via page-context script injection (bypasses GM sandbox)
    (function() {
        var target = null;
        try {
            var m = (location.hash || '').match(/lt_travel=([^&]+)/);
            if (m) target = decodeURIComponent(m[1]);
        } catch(e) {}
        if (!target || (location.pathname + location.search).indexOf('sid=travel') === -1) return;


        var tName = target;
        // Try once after 3s with debug
        // Poll every 300ms for up to 15s
        var _attempts = 0;
        var _poll = setInterval(function() {
            _attempts++;
            if (_attempts > 50) { clearInterval(_poll); return; }
            if (clickCountry(tName)) { clearInterval(_poll); }
        }, 300);
    })();

    if (document.getElementById('lt-toggle')) return;

    try { injectCSS(); } catch(e) { console.warn('[SetsTracker] CSS inject failed:', e); }

    toggleEl = document.createElement('div'); toggleEl.id = 'lt-toggle';
    panelEl  = document.createElement('div'); panelEl.id  = 'lt-panel';

    document.body.appendChild(panelEl);
    document.body.appendChild(toggleEl);

    buildToggle();
    buildPanel();
    setupDrag();

    if (!cfg.apiKey) {
        setTimeout(openSettings, 600);
    } else {
        mainLoop();
    }
}

// ToS: pause all background polling when page is hidden, resume on return
document.addEventListener('visibilitychange', function() {
    if (document.visibilityState === 'visible' && cfg.apiKey) {
        // Page became visible — refresh immediately so data is fresh
        refreshAll();
    }
});

if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', () => setTimeout(init, 400));
} else {
    setTimeout(init, 400);
}

})();