☰

🎯 Target Tracker

War Roster Β· Chain Targets Β· Operator Dashboard β€” Torn

VocΓͺ precisarΓ‘ instalar uma extensΓ£o como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

VocΓͺ precisarΓ‘ instalar uma extensΓ£o como Tampermonkey para instalar este script.

VocΓͺ precisarΓ‘ instalar uma extensΓ£o como Tampermonkey ou Violentmonkey para instalar este script.

VocΓͺ precisarΓ‘ instalar uma extensΓ£o como Tampermonkey ou Userscripts para instalar este script.

VocΓͺ precisarΓ‘ instalar uma extensΓ£o como o Tampermonkey para instalar este script.

VocΓͺ precisarΓ‘ instalar um gerenciador de scripts de usuΓ‘rio para instalar este script.

(Eu jΓ‘ tenho um gerenciador de scripts de usuΓ‘rio, me deixe instalΓ‘-lo!)

VocΓͺ precisarΓ‘ instalar uma extensΓ£o como o Stylus para instalar este estilo.

VocΓͺ precisarΓ‘ instalar uma extensΓ£o como o Stylus para instalar este estilo.

VocΓͺ precisarΓ‘ instalar uma extensΓ£o como o Stylus para instalar este estilo.

VocΓͺ precisarΓ‘ instalar um gerenciador de estilos de usuΓ‘rio para instalar este estilo.

VocΓͺ precisarΓ‘ instalar um gerenciador de estilos de usuΓ‘rio para instalar este estilo.

VocΓͺ precisarΓ‘ instalar um gerenciador de estilos de usuΓ‘rio para instalar este estilo.

(Eu jΓ‘ possuo um gerenciador de estilos de usuΓ‘rio, me deixar fazer a instalaΓ§Γ£o!)

// ==UserScript==
// @name         🎯 Target Tracker
// @namespace    https://osdevscape.com
// @version      8.8.2
// @author       Phillip_J_Fry (OSMays8338) β€” OSDevscape
// @license      All Rights Reserved Β© 2026 OSDevscape
// @homepageURL  https://greasyfork.org/en/scripts/568658-war-target-tracker
// @supportURL   https://greasyfork.org/en/scripts/568658-war-target-tracker/feedback
// @description  War Roster Β· Chain Targets Β· Operator Dashboard β€” Torn
// @match        https://www.torn.com/*
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_xmlhttpRequest
// @grant        unsafeWindow
// @run-at       document-end
// ==/UserScript==

// ╔══════════════════════════════════════════════════════════════╗
// β•‘              🎯  TARGET TRACKER  v8.8.2                 β•‘
// β•‘                                                              β•‘
// β•‘  Author  :  Phillip_J_Fry  (Torn) Β· OSMays8338 (Greasyfork) β•‘
// β•‘  Company :  OSDevscape                                       β•‘
// β•‘  License :  All Rights Reserved Β© 2026 OSDevscape           β•‘
// β•‘  Script  :  https://greasyfork.org/en/scripts/568658        β•‘
// β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•

(function () {
'use strict';

/* ─────────────────────────────────────────
   STORAGE HELPERS
   Dual-layer: localStorage (PDA + all browsers)
   with GM_setValue/getValue fallback (Tampermonkey PC)
───────────────────────────────────────── */
const store = {
    get: function(k, d) {
        // Try localStorage first (works in PDA and browsers)
        try {
            var v = localStorage.getItem('wtt_' + k);
            if (v !== null) return v;
        } catch(e) {}
        // Fallback to GM_getValue (Tampermonkey)
        try { return GM_getValue(k, d); } catch(e) {}
        return d;
    },
    set: function(k, v) {
        // Write to both so either environment can read it
        try { localStorage.setItem('wtt_' + k, v); } catch(e) {}
        try { GM_setValue(k, v); } catch(e) {}
    },
    getJSON: function(k, d) {
        try {
            var v = localStorage.getItem('wtt_' + 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: function(k, v) {
        var s = JSON.stringify(v);
        try { localStorage.setItem('wtt_' + k, s); } catch(e) {}
        try { GM_setValue(k, s); } catch(e) {}
    }
};

/* ─────────────────────────────────────────
   CONFIG
───────────────────────────────────────── */
const cfg = {
    get apiKey()    { return store.get('wtt_api', ''); },
    set apiKey(v)   { store.set('wtt_api', v); },
    get factionId() { return store.get('wtt_fac', ''); },
    set factionId(v){ store.set('wtt_fac', v); },
    get userId()    { return store.get('wtt_uid', ''); },
    set userId(v)   { store.set('wtt_uid', v); },
    getChainIds()   { return store.getJSON('wtt_chain', []); },
    setChainIds(a)  { store.setJSON('wtt_chain', a); },
    get refillDest()  { return store.get('wtt_refill_dest', 'inventory'); },
    set refillDest(v) { store.set('wtt_refill_dest', v); },
    // Chain Watcher
    get cwEnabled()   { return store.get('wtt_cw_on', '0') === '1'; },
    set cwEnabled(v)  { store.set('wtt_cw_on', v ? '1' : '0'); },
    // threshold bitmask stored as JSON array of seconds: [240,180,120,60,30,10]
    getCwThresholds() { return store.getJSON('wtt_cw_thresh', [60, 30, 10]); },
    setCwThresholds(a){ store.setJSON('wtt_cw_thresh', a); }
};

/* ─────────────────────────────────────────
   STATE
───────────────────────────────────────── */
let toggleEl = null;
let panelEl  = null;
let panelOpen  = false;
let activeTab  = 'dash';   // dash | war | chain
let activeFilter = 'all';
let warRoster    = [];
let enemyRoster  = [];   // enemy faction members fetched via war data
let chainCache   = [];
let selfData     = null;
let factionChain = null;
let isLoading    = false;
let regenTimer      = null;
let nerveTimer      = null;
let pollTimer       = null;
let selfTimer       = null;   // fast 30s self/bars refresh
let chainTimer      = null;   // 90s chain targets refresh
let warsTimer       = null;   // 120s war score refresh
let lastWarsFetch   = 0;      // unix ms of last v2/wars call
let notifGranted    = false;
let prevHospStatus  = {};   // id -> bool, for hosp-out notifications
let warScore        = null; // { us, them, estRespect }
let enemyData       = null; // { name, respect, id }
let ownFactionData  = null; // { name, respect, rank, bestChain, memberCount, wins, losses }
let ownFactionId    = null; // derived from selfData.faction.faction_id

/* ── Chain Watcher state ── */
let cwEnabled       = false;   // master toggle (from settings)
let cwData          = null;    // { current, timeout, cooldown } from last API fetch
let cwSecsLeft      = 0;       // local countdown (decrements every 1s)
let cwTickTimer     = null;    // 1s setInterval handle
let cwPollTimer     = null;    // 60s resync setInterval handle
let cwAlerted       = {};      // { 240:true, 180:true, ... } β€” fired thresholds this chain
let cwCooldown      = false;   // true when in cooldown mode
let cwCount         = 0;       // current chain hit count

/* ─────────────────────────────────────────
   COLOUR PALETTE  (used inline everywhere)
───────────────────────────────────────── */
const C = {
    bg:       '#0e0303',
    bg2:      '#160505',
    border:   'rgba(160,20,20,0.45)',
    red:      '#dd4444',
    redDim:   'rgba(220,60,60,0.7)',
    text:     '#d4d4d4',
    textDim:  'rgba(180,180,180,0.55)',
    hosp:     '#ff5555',
    travel:   '#f0b030',
    jail:     '#78a8ff',
    okay:     '#66bb66',
    mono:     '"Share Tech Mono",Consolas,monospace',
    sans:     'Rajdhani,"Segoe UI",Arial,sans-serif'
};

/* ─────────────────────────────────────────
   INJECT STYLESHEET  (non-critical styles only)
   Settings + toast use ONLY inline styles
───────────────────────────────────────── */
function injectCSS() {
    if (document.getElementById('wtt-css')) return;
    const s = document.createElement('style');
    s.id = 'wtt-css';
    // Only safe, non-import CSS. No @import, no external resources.
    s.textContent = `
#wtt-toggle { position:fixed; z-index:999990; user-select:none; touch-action:none; cursor:grab; }
#wtt-toggle:active { cursor:grabbing; }
#wtt-panel  { position:fixed; z-index:999989; overflow:hidden; display:flex; flex-direction:column; }
#wtt-panel .wtt-body { overflow-y:auto; overflow-x:hidden; flex:1; scrollbar-width:thin; scrollbar-color:rgba(160,20,20,0.4) transparent; touch-action:pan-y; -webkit-overflow-scrolling:touch; }
#wtt-panel .wtt-body::-webkit-scrollbar { width:3px; }
#wtt-panel .wtt-body::-webkit-scrollbar-thumb { background:rgba(160,20,20,0.5); border-radius:2px; }
#wtt-panel a { text-decoration:none !important; color:inherit; }
#wtt-panel a:hover { text-decoration:none !important; }
@keyframes wtt-pulseRed { 0%,100%{box-shadow:0 0 0 0 rgba(220,50,50,0.55)} 50%{box-shadow:0 0 0 5px rgba(220,50,50,0)} }
@keyframes wtt-pulseAmb { 0%,100%{box-shadow:0 0 0 0 rgba(230,160,30,0.45)} 50%{box-shadow:0 0 0 5px rgba(230,160,30,0)} }
@keyframes wtt-spin     { to{transform:rotate(360deg)} }
@keyframes wtt-blink    { 0%,100%{opacity:1} 50%{opacity:0.35} }
.wtt-pulse-red { animation:wtt-pulseRed 2s ease-in-out infinite; }
.wtt-pulse-amb { animation:wtt-pulseAmb 2s ease-in-out infinite; }
.wtt-spin-anim { animation:wtt-spin 0.8s linear infinite; display:inline-block; }
.wtt-blink     { animation:wtt-blink 2.2s ease-in-out infinite; }
@keyframes wtt-urgentBlink { 0%,100%{opacity:1;background:rgba(200,30,30,0.18)} 50%{opacity:0.6;background:rgba(200,30,30,0.04)} }
.wtt-urgent    { animation:wtt-urgentBlink 0.9s ease-in-out infinite; }
`;
    (document.head || document.documentElement).appendChild(s);
}

/* ─────────────────────────────────────────
   TOAST  β€” 100% inline, cannot be hidden
───────────────────────────────────────── */
function toast(msg, dur) {
    dur = dur || 3000;
    const old = document.getElementById('wtt-toast');
    if (old) old.remove();
    const el = document.createElement('div');
    el.id = 'wtt-toast';
    el.textContent = msg;
    el.setAttribute('style',
        'position:fixed !important;' +
        'top:18px !important;' +
        'left:50% !important;' +
        'transform:translateX(-50%) !important;' +
        'background:#5a0808 !important;' +
        'border:1px solid #cc3333 !important;' +
        'border-radius:8px !important;' +
        'padding:10px 20px !important;' +
        'color:#ffaaaa !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(function() {
        el.style.setProperty('opacity', '0', 'important');
        el.style.setProperty('transition', 'opacity 0.3s', 'important');
        setTimeout(function() { el.remove(); }, 320);
    }, dur);
}

/* ─────────────────────────────────────────
   SETTINGS POPUP β€” 100% inline, CANNOT be
   hidden or overridden by page CSS
───────────────────────────────────────── */
function openSettings() {
    var existing = document.getElementById('wtt-settings-wrap');
    if (existing) { existing.remove(); return; }

    /* ── helpers ── */
    function el(tag, css, extra) {
        var e = document.createElement(tag);
        if (css) e.setAttribute('style', css);
        if (extra) Object.assign(e, extra);
        return e;
    }
    function IS(css) { return css + ' !important;'; }   // shorthand: wrap value in !important

    var ISTYLE = {
        wrap:  'position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.9);z-index:2147483647;display:flex;align-items:flex-start;justify-content:center;padding-top:5vh;box-sizing:border-box;overflow-y:auto;',
        box:   'background:#150303;border:2px solid #aa2222;border-radius:12px;width:360px;max-width:94vw;color:#d4d4d4;font-family:Arial,sans-serif;overflow:hidden;box-shadow:0 16px 60px rgba(0,0,0,0.95);margin-bottom:20px;',
        hdr:   'background:#2a0808;padding:13px 18px;font-size:12px;font-weight:700;letter-spacing:1.4px;color:#ee5555;border-bottom:1px solid #551111;',
        body:  'padding:16px;',
        note:  'background:#2a1500;border-left:3px solid #cc8800;padding:9px 12px;border-radius:4px;font-size:11px;color:#ddaa55;margin-bottom:16px;line-height:1.5;',
        secHdr:'font-size:9px;font-weight:700;letter-spacing:1.2px;text-transform:uppercase;color:rgba(200,80,80,0.7);padding-bottom:5px;margin:16px 0 8px;border-bottom:1px solid rgba(130,16,16,0.3);font-family:Consolas,monospace;',
        lbl:   'display:block;margin-bottom:5px;font-size:10px;font-weight:700;letter-spacing:1px;text-transform:uppercase;color:#cc8888;',
        inp:   'display:block;width:100%;box-sizing:border-box;padding:9px 11px;background:#0e0202;border:1px solid #661111;border-radius:5px;color:#dddddd;font-size:13px;font-family:Consolas,monospace;outline:none;',
        hint:  'font-size:9px;color:rgba(160,100,100,0.65);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 rgba(180,40,40,0.5);font-family:Arial,sans-serif;letter-spacing:0.5px;'
    };

    function addImportant(css) {
        return css.split(';').filter(Boolean).map(function(r){ return r.trim() + ' !important'; }).join(';') + ';';
    }

    function applyStyle(element, key) {
        element.setAttribute('style', addImportant(ISTYLE[key]));
    }

    /* ── structure ── */
    var wrap = el('div'); wrap.id = 'wtt-settings-wrap'; applyStyle(wrap, 'wrap');
    var box  = el('div'); applyStyle(box, 'box');
    var hdr  = el('div'); hdr.textContent = 'βš™  TARGET TRACKER β€” Settings'; applyStyle(hdr, 'hdr');
    var body = el('div'); applyStyle(body, 'body');

    var note = el('div'); note.innerHTML = '&#9888; Requires a <b>Full Access</b> API key for all features.'; applyStyle(note, 'note');

    /* ── field builder ── */
    function field(labelText, id, placeholder, value, hint, maxLen, isSecret) {
        var g = el('div'); g.setAttribute('style', 'margin-bottom:12px !important;');
        var lbl = el('label'); lbl.textContent = labelText; applyStyle(lbl, 'lbl');
        // Create input β€” set type via setAttribute so webviews honour it
        var inp = document.createElement('input');
        inp.setAttribute('type', isSecret ? 'password' : 'text');
        inp.id = id; inp.placeholder = placeholder; inp.value = value || '';
        applyStyle(inp, 'inp');
        if (maxLen) inp.maxLength = maxLen;
        inp.addEventListener('focus', function(){ inp.style.setProperty('border-color','#cc2222','important'); });
        inp.addEventListener('blur',  function(){ inp.style.setProperty('border-color','#661111','important'); });
        if (isSecret) {
            var row = document.createElement('div');
            row.setAttribute('style','display:flex !important;align-items:center !important;gap:6px !important;');
            var tog = document.createElement('div');
            tog.textContent = 'πŸ‘';
            tog.setAttribute('style','cursor:pointer !important;font-size:14px !important;flex-shrink:0 !important;opacity:0.45 !important;user-select:none !important;padding:2px !important;');
            tog.addEventListener('click', function(){
                var 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);
            var h = el('div'); h.textContent = hint; applyStyle(h, 'hint');
            g.appendChild(lbl); g.appendChild(row); g.appendChild(h);
        } else {
            var h = el('div'); h.textContent = hint; applyStyle(h, 'hint');
            g.appendChild(lbl); g.appendChild(inp); g.appendChild(h);
        }
        return g;
    }

    /* ── Section: Credentials ── */
    var credHdr = el('div'); credHdr.textContent = 'πŸ”‘ Credentials'; applyStyle(credHdr, 'secHdr');
    var fAPI = field('API Key', 'wtt-si-api', 'Enter your Torn API key', cfg.apiKey, 'Torn β†’ Settings β†’ API β†’ Full Access', null, true);

    // "Create API key" helper link
    var apiKeyHint = el('div');
    apiKeyHint.setAttribute('style', addImportant(
        'margin-top:-8px;margin-bottom:12px;font-size:10px;font-family:Arial,sans-serif;' +
        'color:rgba(180,130,60,0.85);line-height:1.5;'
    ));
    apiKeyHint.innerHTML = 'πŸ”‘ Don\'t have a key? ';
    var apiKeyLink = document.createElement('a');
    apiKeyLink.href = 'https://www.torn.com/preferences.php#tab=api?step=addNewKey&title=WarTargetTracker&access_level=4';
    apiKeyLink.textContent = 'Create Full Access key on Torn';
    apiKeyLink.setAttribute('style', addImportant(
        'color:rgba(220,160,60,0.9);font-weight:700;text-decoration:underline;cursor:pointer;'
    ));
    apiKeyHint.appendChild(apiKeyLink);
    var apiNameNote = el('div');
    apiNameNote.setAttribute('style', addImportant(
        'font-size:9px;font-family:Consolas,monospace;color:rgba(140,100,50,0.7);margin-top:2px;'
    ));
    apiNameNote.textContent = 'The key will be named "WarTargetTracker" automatically.';
    apiKeyHint.appendChild(document.createElement('br'));
    apiKeyHint.appendChild(apiNameNote);
    var fUID = field('Your User ID',        'wtt-si-uid', 'Enter your Torn player ID', cfg.userId,    'Used for dashboard health stats');
    var fFac = field('Your Faction ID',      'wtt-si-fac', 'e.g. 37155',          cfg.factionId, 'YOUR faction ID (not the enemy). Used to pull your roster, chain, and war data.');



    /* ── Save / Cancel ── */
    var btnRow = el('div'); applyStyle(btnRow, 'btnRow');
    var btnCancel = el('button', null, {type:'button', textContent:'Cancel'});
    var btnSave   = el('button', null, {type:'button', textContent:'Save & Refresh'});
    btnCancel.setAttribute('style', addImportant(ISTYLE.btn + 'background:#1a0505;color:#cc8888;'));
    btnSave.setAttribute('style',   addImportant(ISTYLE.btn + 'background:#660f0f;color:#ffaaaa;'));
    btnRow.appendChild(btnCancel); btnRow.appendChild(btnSave);

    /* ── Assemble ── */
    /* ── Chain Targets section ── */
    var chainActHdr = el('div'); chainActHdr.textContent = 'πŸ”— Chain Targets'; applyStyle(chainActHdr, 'secHdr');

    // Scrollable remove list
    var chainListBox = el('div');
    chainListBox.setAttribute('style', addImportant(
        'background:#0a0101;border:1px solid #3a0808;border-radius:6px;' +
        'max-height:140px;overflow-y:auto;margin-bottom:8px;'
    ));

    function rebuildSettingsChainList() {
        chainListBox.innerHTML = '';
        var ids = cfg.getChainIds();
        if (!ids.length) {
            var emp = el('div');
            emp.textContent = 'No targets tracked';
            emp.setAttribute('style', addImportant(
                'padding:10px;text-align:center;font-size:11px;' +
                'color:rgba(150,90,90,0.55);font-family:Arial,sans-serif;'
            ));
            chainListBox.appendChild(emp);
            return;
        }
        ids.forEach(function(id) {
            var cached = chainCache.find(function(p){ return p.id === id; });
            var label  = cached ? cached.name : 'ID ' + id;
            var status = cached ? ' Β· ' + cached.status.sub : '';
            var sRow = el('div');
            sRow.setAttribute('style', addImportant(
                'display:flex;align-items:center;justify-content:space-between;' +
                'padding:5px 10px;border-bottom:1px solid rgba(60,8,8,0.3);'
            ));
            var sName = el('div');
            sName.textContent = label + status;
            sName.setAttribute('style', addImportant(
                'font-size:11px;font-family:Consolas,monospace;color:#cccccc;' +
                'flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;'
            ));
            var sRm = el('div');
            sRm.textContent = 'βœ• Remove';
            sRm.setAttribute('style', addImportant(
                'font-size:10px;font-weight:700;font-family:Arial,sans-serif;' +
                'color:rgba(220,80,80,0.7);cursor:pointer;flex-shrink:0;padding-left:10px;'
            ));
            sRm.addEventListener('click', function() {
                var newIds = cfg.getChainIds().filter(function(x){ return x !== id; });
                cfg.setChainIds(newIds);
                chainCache = chainCache.filter(function(p){ return p.id !== id; });
                rebuildSettingsChainList();
                if (activeTab === 'chain' && panelEl) renderChainBody();
                toast('βœ“ Removed from chain list');
            });
            sRow.appendChild(sName); sRow.appendChild(sRm);
            chainListBox.appendChild(sRow);
        });
    }
    rebuildSettingsChainList();

    // Export + Clear buttons
    var chainActRow = el('div');
    chainActRow.setAttribute('style', addImportant('display:flex;gap:8px;'));
    function settingsActBtn(label, bg, col) {
        var b = el('button', null, {type:'button', textContent:label});
        b.setAttribute('style', addImportant('flex:1;padding:9px 0;border-radius:6px;font-size:11px;font-weight:700;cursor:pointer;font-family:Arial,sans-serif;background:' + bg + ';color:' + col + ';border:1px solid ' + col + ';'));
        return b;
    }
    var sExpBtn   = settingsActBtn('πŸ“‹ Export', 'rgba(10,30,100,0.3)', 'rgba(96,144,223,0.9)');
    var sClearBtn = settingsActBtn('βœ• Clear All', 'rgba(80,8,8,0.3)', 'rgba(200,80,80,0.85)');
    sExpBtn.addEventListener('click', function() { exportChain(); });
    sClearBtn.addEventListener('click', function() {
        var ids = cfg.getChainIds();
        if (!ids.length) { toast('Chain list is already empty'); return; }
        if (!confirm('Clear all ' + ids.length + ' chain targets?')) return;
        cfg.setChainIds([]); chainCache = [];
        rebuildSettingsChainList();
        if (activeTab === 'chain' && panelEl) renderChainBody();
        toast('βœ“ Chain list cleared');
    });
    chainActRow.appendChild(sExpBtn); chainActRow.appendChild(sClearBtn);

    /* ── Section: Preferences ── */
    var prefHdr = el('div'); prefHdr.textContent = 'πŸ’‘ Preferences'; applyStyle(prefHdr, 'secHdr');

    var tooltipRow = document.createElement('div');
    tooltipRow.setAttribute('style',
        'display:flex !important;align-items:center !important;justify-content:space-between !important;' +
        'padding:8px 10px !important;background:rgba(255,255,255,0.03) !important;' +
        'border:1px solid rgba(130,16,16,0.25) !important;border-radius:6px !important;margin-bottom:4px !important;'
    );

    var tooltipLabel = document.createElement('div');
    tooltipLabel.setAttribute('style','font-size:11px !important;color:rgba(210,210,210,0.85) !important;');
    tooltipLabel.textContent = '🎠 Show tips carousel on next load';

    // Read current state
    var SEEN_KEY = 'wtt_tooltip_seen';
    var isSeen = false;
    try { isSeen = !!localStorage.getItem(SEEN_KEY); } catch(e) {}

    // Toggle pill
    var togPill = document.createElement('div');
    var togKnob = document.createElement('div');

    function setPillState(active) {
        togPill.setAttribute('style',
            'width:36px !important;height:20px !important;border-radius:10px !important;' +
            'cursor:pointer !important;flex-shrink:0 !important;position:relative !important;' +
            'transition:background 0.2s !important;user-select:none !important;' +
            'background:' + (active ? 'rgba(60,180,60,0.7)' : 'rgba(100,30,30,0.5)') + ' !important;' +
            'border:1px solid ' + (active ? 'rgba(80,200,80,0.5)' : 'rgba(180,40,40,0.35)') + ' !important;'
        );
        togKnob.setAttribute('style',
            'position:absolute !important;top:2px !important;' +
            (active ? 'left:17px' : 'left:2px') + ' !important;' +
            'width:14px !important;height:14px !important;border-radius:50% !important;' +
            'background:#ffffff !important;transition:left 0.2s !important;'
        );
    }

    // active = tooltip will show (seen flag cleared)
    var pillActive = false; // default: don't show again
    setPillState(pillActive);
    togPill.appendChild(togKnob);

    togPill.addEventListener('click', function() {
        pillActive = !pillActive;
        setPillState(pillActive);
        if (pillActive) {
            try { localStorage.removeItem(SEEN_KEY); } catch(e) {}
        } else {
            try { localStorage.setItem(SEEN_KEY, '1'); } catch(e) {}
        }
    });

    tooltipRow.appendChild(tooltipLabel);
    tooltipRow.appendChild(togPill);

    // ── Refill destination ──
    var refillHdr = el('div'); refillHdr.textContent = '⚑ Energy Refill Destination'; applyStyle(refillHdr, 'secHdr');

    var curRefillDest = cfg.refillDest || 'inventory';
    var refillRow = document.createElement('div');
    refillRow.setAttribute('style', 'display:flex !important;gap:6px !important;');

    function makeRefillBtn(label, val) {
        var b = el('button', null, { type: 'button', textContent: label });
        var active = curRefillDest === val;
        b.setAttribute('style', addImportant(
            'flex:1;padding:9px 0;border-radius:6px;font-size:11px;font-weight:700;cursor:pointer;font-family:Arial,sans-serif;' +
            (active ? 'background:rgba(140,16,16,0.55);color:#ffaaaa;border:1px solid rgba(200,40,40,0.6);'
                     : 'background:#1a0505;color:#cc8888;border:1px solid rgba(130,16,16,0.3);')
        ));
        b.addEventListener('click', function() {
            curRefillDest = val; cfg.refillDest = val;
            refillRow.querySelectorAll('button').forEach(function(x) {
                var isActive = x === b;
                x.setAttribute('style', addImportant(
                    'flex:1;padding:9px 0;border-radius:6px;font-size:11px;font-weight:700;cursor:pointer;font-family:Arial,sans-serif;' +
                    (isActive ? 'background:rgba(140,16,16,0.55);color:#ffaaaa;border:1px solid rgba(200,40,40,0.6);'
                               : 'background:#1a0505;color:#cc8888;border:1px solid rgba(130,16,16,0.3);')
                ));
            });
            toast('βœ“ Refill: ' + label);
        });
        return b;
    }
    refillRow.appendChild(makeRefillBtn('πŸ“¦ Inventory', 'inventory'));
    refillRow.appendChild(makeRefillBtn('🏒 Points Building', 'points'));

    body.appendChild(note);
    body.appendChild(credHdr);
    body.appendChild(fAPI);
    body.appendChild(apiKeyHint);
    body.appendChild(fUID);
    body.appendChild(fFac);
    body.appendChild(chainActHdr);
    body.appendChild(chainListBox);
    body.appendChild(chainActRow);
    body.appendChild(prefHdr);
    body.appendChild(tooltipRow);
    body.appendChild(refillHdr);
    body.appendChild(refillRow);

    /* ── Section: Chain Watcher ── */
    var cwHdr = el('div'); cwHdr.textContent = 'β›“ Chain Watcher'; applyStyle(cwHdr, 'secHdr');

    // Master on/off toggle row
    var cwTogRow = document.createElement('div');
    cwTogRow.setAttribute('style',
        'display:flex !important;align-items:center !important;justify-content:space-between !important;' +
        'padding:8px 10px !important;background:rgba(255,255,255,0.03) !important;' +
        'border:1px solid rgba(130,16,16,0.25) !important;border-radius:6px !important;margin-bottom:8px !important;'
    );
    var cwTogLabel = document.createElement('div');
    cwTogLabel.setAttribute('style','font-size:11px !important;color:rgba(210,210,210,0.85) !important;line-height:1.4 !important;');
    cwTogLabel.innerHTML = 'β›“ Live chain countdown &amp; alerts<br><span style="font-size:9px;color:rgba(150,100,100,0.65);font-family:Consolas,monospace;">Faction API Β· auto-syncs every 60s</span>';

    var cwPill = document.createElement('div');
    var cwKnob = document.createElement('div');
    var cwActive = cfg.cwEnabled;
    function setCwPill(on) {
        cwPill.setAttribute('style',
            'width:36px !important;height:20px !important;border-radius:10px !important;' +
            'cursor:pointer !important;flex-shrink:0 !important;position:relative !important;' +
            'transition:background 0.2s !important;user-select:none !important;' +
            'background:' + (on ? 'rgba(60,180,60,0.7)' : 'rgba(100,30,30,0.5)') + ' !important;' +
            'border:1px solid ' + (on ? 'rgba(80,200,80,0.5)' : 'rgba(180,40,40,0.35)') + ' !important;'
        );
        cwKnob.setAttribute('style',
            'position:absolute !important;top:2px !important;' +
            (on ? 'left:17px' : 'left:2px') + ' !important;' +
            'width:14px !important;height:14px !important;border-radius:50% !important;' +
            'background:#ffffff !important;transition:left 0.2s !important;'
        );
    }
    setCwPill(cwActive);
    cwPill.appendChild(cwKnob);
    cwPill.addEventListener('click', function() {
        cwActive = !cwActive;
        setCwPill(cwActive);
        cfg.cwEnabled = cwActive;
        cwEnabled = cwActive;
        if (cwActive) { cwStart(); } else { cwStop(); }
        toast(cwActive ? 'β›“ Chain Watcher ON' : 'β›“ Chain Watcher OFF');
    });
    cwTogRow.appendChild(cwTogLabel); cwTogRow.appendChild(cwPill);

    // Audio alert thresholds β€” checkboxes
    var cwThreshHdr = document.createElement('div');
    cwThreshHdr.setAttribute('style', addImportant(
        'font-size:9px;font-weight:700;letter-spacing:1px;text-transform:uppercase;' +
        'color:rgba(180,100,100,0.65);margin:8px 0 6px;font-family:Consolas,monospace;'
    ));
    cwThreshHdr.textContent = 'Audio alerts at:';

    var cwThreshBox = document.createElement('div');
    cwThreshBox.setAttribute('style', addImportant(
        'display:flex;flex-wrap:wrap;gap:6px;padding:8px 10px;' +
        'background:rgba(255,255,255,0.02);border:1px solid rgba(100,16,16,0.25);border-radius:6px;'
    ));

    var savedThresh = cfg.getCwThresholds();
    var CW_T_LABELS = [{secs:240,label:'4:00'},{secs:180,label:'3:00'},{secs:120,label:'2:00'},{secs:60,label:'1:00'},{secs:30,label:'0:30'},{secs:10,label:'0:10'}];
    CW_T_LABELS.forEach(function(t) {
        var checked = savedThresh.indexOf(t.secs) !== -1;
        var item = document.createElement('div');
        item.setAttribute('style', addImportant(
            'display:flex;align-items:center;gap:5px;cursor:pointer;' +
            'padding:4px 8px;border-radius:4px;' +
            'border:1px solid ' + (checked ? 'rgba(200,80,80,0.5)' : 'rgba(80,30,30,0.3)') + ';' +
            'background:' + (checked ? 'rgba(140,16,16,0.25)' : 'rgba(20,5,5,0.3)') + ';' +
            'transition:all 0.15s;'
        ));
        var cb = document.createElement('input');
        cb.type = 'checkbox'; cb.checked = checked;
        cb.setAttribute('style', addImportant('cursor:pointer;accent-color:#cc3333;width:13px;height:13px;margin:0;'));
        var lbl = document.createElement('span');
        lbl.textContent = t.secs <= 30 ? '⏱ ' + t.label : t.label;
        lbl.setAttribute('style', addImportant(
            'font-size:11px;font-family:Consolas,monospace;font-weight:700;' +
            'color:' + (t.secs <= 30 ? '#ff7744' : t.secs <= 60 ? '#f0b030' : 'rgba(200,200,200,0.8)') + ';'
        ));
        function updateItem(isOn) {
            item.setAttribute('style', addImportant(
                'display:flex;align-items:center;gap:5px;cursor:pointer;' +
                'padding:4px 8px;border-radius:4px;' +
                'border:1px solid ' + (isOn ? 'rgba(200,80,80,0.5)' : 'rgba(80,30,30,0.3)') + ';' +
                'background:' + (isOn ? 'rgba(140,16,16,0.25)' : 'rgba(20,5,5,0.3)') + ';' +
                'transition:all 0.15s;'
            ));
        }
        item.addEventListener('click', function() {
            cb.checked = !cb.checked;
            updateItem(cb.checked);
            var cur = cfg.getCwThresholds();
            if (cb.checked) { if (cur.indexOf(t.secs) === -1) cur.push(t.secs); }
            else { cur = cur.filter(function(x){ return x !== t.secs; }); }
            cfg.setCwThresholds(cur);
        });
        item.appendChild(cb); item.appendChild(lbl);
        cwThreshBox.appendChild(item);
    });

    body.appendChild(cwHdr);
    body.appendChild(cwTogRow);
    body.appendChild(cwThreshHdr);
    body.appendChild(cwThreshBox);
    body.appendChild(btnRow);

    box.appendChild(hdr); box.appendChild(body);
    wrap.appendChild(box);
    document.body.appendChild(wrap);

    setTimeout(function(){ var i = document.getElementById('wtt-si-api'); if(i) i.focus(); }, 50);

    function doSave() {
        var key = (document.getElementById('wtt-si-api').value || '').trim();
        var uid = (document.getElementById('wtt-si-uid').value || '').trim();
        var fac = (document.getElementById('wtt-si-fac').value || '').trim();
        if (!key) { toast('⚠ API key is required'); return; }
        console.log('[TargetTracker] Saving: key=' + key.substring(0,4) + '*** uid=' + uid + ' fac=' + fac);
        cfg.apiKey = key; cfg.userId = uid; cfg.factionId = fac;
        // Verify it actually saved
        var saved = cfg.apiKey;
        console.log('[TargetTracker] Verified saved key starts with: ' + (saved ? saved.substring(0,4) : 'EMPTY'));
        if (!saved) { toast('⚠ Save failed β€” storage unavailable'); return; }
        wrap.remove();
        toast('βœ“ Saved! Key: ' + key.substring(0,4) + '***');
        selfData = null; warRoster = []; enemyRoster = []; factionChain = null; enemyData = null; ownFactionData = null; warScore = null;
        if (panelEl) renderPanel();
        stopPolling();
        // Restart chain watcher if enabled
        cwStop();
        cwEnabled = cfg.cwEnabled;
        refreshAll().then(function() { startPolling(); if (cwEnabled) cwStart(); });
    }

    btnSave.addEventListener('click', doSave);
    btnCancel.addEventListener('click', function(){ wrap.remove(); });
    wrap.addEventListener('click', function(e){ if (e.target === wrap) wrap.remove(); });
    box.addEventListener('click', function(e){ e.stopPropagation(); });
    wrap.addEventListener('keydown', function(e){ if (e.key === 'Escape') wrap.remove(); });
}


/* ─────────────────────────────────────────
   API
───────────────────────────────────────── */
function gmFetch(url) {
    // Try GM_xmlhttpRequest first (Tampermonkey β€” bypasses CORS)
    if (typeof GM_xmlhttpRequest !== 'undefined') {
        return new Promise(function(resolve) {
            GM_xmlhttpRequest({
                method: 'GET',
                url: url,
                onload: function(r) {
                    try { resolve(JSON.parse(r.responseText)); }
                    catch(e) { resolve({ _parseError: true }); }
                },
                onerror: function() { resolve({ _netError: true }); }
            });
        });
    }
    // Fallback: native fetch (PDA and modern browsers)
    return fetch(url)
        .then(function(r) { return r.json(); })
        .catch(function() { return { _netError: true }; });
}

function cleanId(raw) {
    var n = parseInt(String(raw || '').replace(/\D/g, ''));
    return isNaN(n) ? 0 : n;
}

function fmtSecs(sec) {
    if (sec <= 0) return 'soon';
    var h = Math.floor(sec / 3600);
    var m = Math.floor((sec % 3600) / 60);
    var s = sec % 60;
    if (h > 0) return h + 'h ' + m + 'm';
    if (m > 0) return m + 'm ' + s + 's';
    return s + 's';
}
function fmtUntil(unix) { return fmtSecs(unix - Math.floor(Date.now() / 1000)); }

var COUNTRY_ABBR = {
    'cayman islands':'Cayman', 'south africa':'S.Africa', 'united kingdom':'UK',
    'united states':'USA', 'argentina':'ARG', 'canada':'CAN', 'switzerland':'Swiss',
    'japan':'Japan', 'china':'China', 'mexico':'Mexico', 'hawaii':'Hawaii',
    'united arab emirates':'UAE', 'torn':'Torn'
};
function abbrCountry(desc, isAbroad) {
    if (!desc) return '?';
    var d = desc.toLowerCase();
    var m;
    if (isAbroad) {
        // "In South Africa" -> "Abroad in S.Africa"
        m = d.match(/^in (.+)/);
        var country = m ? (COUNTRY_ABBR[m[1].trim()] || cap(m[1].trim())) : (COUNTRY_ABBR[d.trim()] || cap(desc.trim()));
        return 'Abroad in ' + country;
    }
    // "Returning to Torn from South Africa" -> "From S.Africa"
    m = d.match(/returning to torn from (.+)/);
    if (m) return 'From ' + (COUNTRY_ABBR[m[1].trim()] || cap(m[1].trim()));
    // "Traveling to South Africa" -> "To S.Africa"
    m = d.match(/traveling to (.+)/);
    if (m) return 'To ' + (COUNTRY_ABBR[m[1].trim()] || cap(m[1].trim()));
    // fallback
    for (var k in COUNTRY_ABBR) { if (d.indexOf(k) !== -1) return COUNTRY_ABBR[k]; }
    return desc.split(' ').slice(0,2).join(' ');
}
function cap(s) { return s.charAt(0).toUpperCase() + s.slice(1); }

function parseStatus(player) {
    var s = player.status || {};
    var state = (s.state || '').toLowerCase();
    if (state === 'hospital' || state.indexOf('hospital') !== -1) {
        return { type:'hosp', sub: s.until ? 'Out ' + fmtUntil(s.until) : 'Hosp' };
    }
    if (state === 'traveling' || state.indexOf('traveling') !== -1) {
        return { type:'travel', sub: abbrCountry(s.description, false) };
    }
    if (state === 'abroad' || state.indexOf('abroad') !== -1) {
        return { type:'travel', sub: abbrCountry(s.description, true) };
    }
    if (state === 'jail' || state.indexOf('jail') !== -1) {
        return { type:'jail', sub: s.until ? 'Jail ' + fmtUntil(s.until) : 'Jail' };
    }
    if (state === 'federal' || state.indexOf('federal') !== -1) {
        return { type:'jail', sub: 'Fed jail' };
    }
    return { type:'okay', sub: 'Active' };
}

/* ─────────────────────────────────────────
   API CALLS
───────────────────────────────────────── */
async function fetchSelf() {
    var uid = cleanId(cfg.userId);
    if (!uid || !cfg.apiKey) return null;
    var d = await gmFetch('https://api.torn.com/user/' + uid + '?selections=profile,bars,perks&key=' + cfg.apiKey);
    if (d.error) {
        var msg = (d.error && d.error.error) ? d.error.error : JSON.stringify(d.error);
        console.warn('[TargetTracker] fetchSelf error (userId=' + uid + '):', msg);
        toast('⚠ User API: ' + msg);
        return null;
    }
    return d;
}

async function fetchFaction() {
    // YOUR faction β€” members, chain, basic info
    if (!cfg.factionId || !cfg.apiKey) return null;
    var d = await gmFetch('https://api.torn.com/faction/' + cfg.factionId + '?selections=basic,chain&key=' + cfg.apiKey);
    if (d.error) {
        var msg = (d.error && d.error.error) ? d.error.error : JSON.stringify(d.error);
        console.warn('[TargetTracker] fetchFaction error:', msg);
        toast('⚠ Faction API: ' + msg);
        return null;
    }
    return d;
}

async function fetchWars() {
    // Single call β€” returns wars data including both faction member lists
    if (!cfg.factionId || !cfg.apiKey) return null;
    var w = await gmFetch('https://api.torn.com/v2/faction/' + cfg.factionId + '/wars?key=' + cfg.apiKey);
    if (w.error) { console.warn('[TargetTracker] fetchWars error:', JSON.stringify(w.error)); return null; }
    if (!w.wars) { console.warn('[TargetTracker] fetchWars: no wars in response'); return null; }
    lastWarsFetch = Date.now();
    return w.wars;
}

function applyWarScore(wars) {
    if (!wars) return;
    var warIds = Object.keys(wars);
    if (!warIds.length) { console.warn('[TargetTracker] applyWarScore: wars object is empty'); return; }
    var w = wars[warIds[0]];

    // v2/wars factions is an ARRAY: [ { id, name, score, ... }, { id, name, score, ... } ]
    // NOT keyed by faction ID β€” must read .id from each entry
    var facs = w.factions || [];
    if (!Array.isArray(facs)) facs = Object.values(facs); // normalise just in case
    console.log('[TargetTracker] applyWarScore factions count:', facs.length, 'ids:', facs.map(function(f){ return f.id; }), 'cfg.factionId:', cfg.factionId);
    if (facs.length < 2) { console.warn('[TargetTracker] applyWarScore: less than 2 factions'); return; }

    var ourIdNum = parseInt(cfg.factionId);
    var usFac   = facs.find(function(f){ return f.id === ourIdNum; });
    var themFac = facs.find(function(f){ return f.id !== ourIdNum; });

    // Fallback: use selfData faction name if ID doesn't match
    if (!usFac && selfData && selfData.faction) {
        var selfName = (selfData.faction.faction_name || '').toLowerCase();
        usFac   = facs.find(function(f){ return (f.name||'').toLowerCase() === selfName; });
        themFac = facs.find(function(f){ return f !== usFac; });
    }

    // Final fallback: just pick first as us
    if (!usFac) { usFac = facs[0]; themFac = facs[1]; }

    warScore = {
        us:        usFac.score   || 0,
        them:      themFac.score || 0,
        usName:    usFac.name    || 'Us',
        themName:  themFac.name  || 'Them',
        enemyId:   String(themFac.id),  // actual faction ID from inside the object
        startTime: w.start || 0
    };
    console.log('[TargetTracker] warScore set β€” us:', warScore.usName, warScore.us, '| them:', warScore.themName, warScore.them, '| enemyId:', warScore.enemyId);
    // enemyId is now the real faction ID β€” fetchEnemyFactionDirect called from refreshFaction
}

async function fetchEnemyFactionDirect(enemyId) {
    if (!enemyId || !cfg.apiKey) return;
    var eid = String(enemyId).replace(/[^0-9]/g, '');
    if (!eid) return;
    console.log('[TargetTracker] fetchEnemyFactionDirect id=' + eid);

    // Two parallel v2 calls: basic info + members list
    var results = await Promise.allSettled([
        gmFetch('https://api.torn.com/v2/faction/' + eid + '?selections=basic&key=' + cfg.apiKey),
        gmFetch('https://api.torn.com/v2/faction/' + eid + '/members?key=' + cfg.apiKey)
    ]);

    var info    = (results[0].status === 'fulfilled') ? results[0].value : {};
    var membRes = (results[1].status === 'fulfilled') ? results[1].value : {};

    if (info.error) { console.warn('[TargetTracker] enemy basic error:', JSON.stringify(info.error)); }
    if (membRes.error) { console.warn('[TargetTracker] enemy members error:', JSON.stringify(membRes.error)); }

    // Build enemyData from basic info
    var rank = info.rank || {};
    enemyData = {
        id:          eid,
        name:        info.name        || ('Faction ' + eid),
        respect:     info.respect     || 0,
        bestChain:   info.best_chain  || 0,
        memberCount: 0,
        capacity:    info.capacity    || 0,
        rankName:    rank.name        || '',
        rankDiv:     rank.division    || '',
        wins:        rank.wins        || 0,
        losses:      rank.losses      || 0,
    };

    // v2/faction/{id}/members returns { members: { "userId": { name, level, position, status } } }
    // Keys are player IDs β€” must use Object.keys() not Object.values() to preserve them
    // Log full top-level keys so we can see what the API returned
    console.log('[TargetTracker] members response top-level keys:', JSON.stringify(Object.keys(membRes)));
    console.log('[TargetTracker] members response snippet:', JSON.stringify(membRes).substring(0, 400));

    // Handle every possible response shape from Torn v2 members endpoint:
    // { members: { "id": {...} } }  OR  { members: [ {...} ] }  OR  { "id": {...} } (flat)
    var scoreMap = {};
    enemyRoster.forEach(function(m) { if (m.warScore) scoreMap[m.id] = m.warScore; });
    var newRoster = [];

    var raw = membRes.members !== undefined ? membRes.members : membRes;
    if (Array.isArray(raw)) {
        // Array of member objects with id field
        newRoster = raw.map(function(m) {
            return { id: parseInt(m.id || m.player_id || 0), name: m.name || '?', level: m.level || 0, warScore: scoreMap[parseInt(m.id||0)] || 0, position: m.position || '', status: parseStatus(m) };
        });
    } else if (raw && typeof raw === 'object') {
        // Object keyed by player ID
        newRoster = Object.keys(raw).filter(function(k){ return !isNaN(parseInt(k)); }).map(function(uid) {
            var m = raw[uid];
            var pid = parseInt(uid);
            return { id: pid, name: m.name || '?', level: m.level || 0, warScore: scoreMap[pid] || 0, position: m.position || '', status: parseStatus(m) };
        });
    }

    newRoster = newRoster.filter(function(m) { return m.id > 0; });
    if (newRoster.length) {
        enemyData.memberCount = newRoster.length;
        enemyRoster = newRoster;
        console.log('[TargetTracker] enemy roster loaded:', newRoster.length, 'members');
    } else {
        console.warn('[TargetTracker] enemy roster still empty after parsing. Full response:', JSON.stringify(membRes).substring(0, 500));
    }
}

async function fetchPlayer(rawId) {
    var id = cleanId(rawId);
    if (!id)         { console.warn('[TargetTracker] fetchPlayer: bad id', rawId); return null; }
    if (!cfg.apiKey) { console.warn('[TargetTracker] fetchPlayer: no API key'); return null; }
    console.log('[TargetTracker] fetchPlayer id=' + id);
    var d = await gmFetch('https://api.torn.com/user/' + id + '?selections=profile&key=' + cfg.apiKey);
    if (d.error) {
        var msg = (d.error && d.error.error) ? d.error.error : JSON.stringify(d.error);
        console.warn('[TargetTracker] fetchPlayer error:', msg);
        toast('⚠ API: ' + msg);
        return null;
    }
    return { id: d.player_id || id, name: d.name || ('Player ' + id), level: d.level || 0, status: parseStatus(d) };
}

/* ─────────────────────────────────────────
   REFRESH β€” call budget:
   β€’ refreshSelf()    β€” every 60s  = 1 call/min
   β€’ refreshFaction() β€” every 45s  = ~2 calls/min (own faction + wars)
   β€’ fetchEnemyFactionDirect() β€” triggered by applyWarScore when at war
   β€’ refreshChain()   β€” every 90s  = N/1.5 calls/min (1 per chain target)
───────────────────────────────────────── */
async function refreshSelf() {
    var d = await fetchSelf();
    if (d) selfData = d;
    if (activeTab === 'dash') renderDashBody();
    renderStatusBar();
}

async function refreshFaction() {
    isLoading = true; renderStatusBar();

    // Call 1: our faction (members + chain)
    var d = await fetchFaction();
    if (d) {
        var rank = d.rank || {};
        ownFactionData = {
            name:        d.name        || '',
            respect:     d.respect     || 0,
            bestChain:   d.best_chain  || 0,
            memberCount: Object.keys(d.members || {}).length,
            capacity:    d.capacity    || 0,
            rankName:    rank.name     || '',
            rankDiv:     rank.division || '',
            wins:        rank.wins     || 0,
            losses:      rank.losses   || 0,
        };
        var raw = d.members || {};
        var newRoster = Object.keys(raw).map(function(id) {
            var m = raw[id];
            return { id: parseInt(id), name: m.name || '?', level: m.level || 0, position: m.position || '', status: parseStatus(m) };
        });
        newRoster.forEach(function(m) {
            var wasHosp = prevHospStatus[m.id];
            var isHosp  = m.status.type === 'hosp';
            if (wasHosp && !isHosp) sendHospOutNotif(m);
            prevHospStatus[m.id] = isHosp;
        });
        warRoster = newRoster;
        if (d.chain) factionChain = { current: d.chain.current || 0, timeout: d.chain.timeout || 0, cooldown: d.chain.cooldown || 0, modifier: d.chain.modifier || 0 };
    }

    // Call 2 (every 120s only): wars β€” derives enemyId
    if (cfg.factionId && (Date.now() - lastWarsFetch) > 120000) {
        applyWarScore(await fetchWars());
    }

    // Call 3: enemy faction β€” always fetch every cycle for live statuses
    if (warScore && warScore.enemyId) {
        await fetchEnemyFactionDirect(warScore.enemyId);
    }

    isLoading = false;
    if (activeTab === 'war' || activeTab === 'chain') renderPanel();
    else renderStatusBar();
    updateChainBadge();
}

async function refreshChain() {
    var ids = cfg.getChainIds();
    if (!ids.length || !cfg.apiKey) return;
    var res = await Promise.allSettled(ids.map(fetchPlayer));
    var newCache = res.filter(function(r) { return r.status === 'fulfilled' && r.value; }).map(function(r) { return r.value; });
    newCache.forEach(function(m) {
        var wasHosp = prevHospStatus['c_' + m.id];
        var isHosp  = m.status.type === 'hosp';
        if (wasHosp && !isHosp) sendHospOutNotif(m);
        prevHospStatus['c_' + m.id] = isHosp;
    });
    chainCache = newCache;
    if (activeTab === 'chain') renderChainBody();
    renderStatusBar();
}

// Full refresh β€” self first so selfData is set before applyWarScore cross-references it
async function refreshAll() {
    isLoading = true; renderStatusBar();
    await refreshSelf();
    await Promise.allSettled([refreshFaction(), refreshChain()]);
    isLoading = false;
    syncWarTabVisibility();
    renderPanel();
    updateChainBadge();
}

function sendHospOutNotif(m) {
    if (!notifGranted) return;
    try {
        // ToS: only fire desktop notifications while the user is actively on this page
        if (document.visibilityState !== 'visible') return;
        var n = new Notification('πŸ₯ ' + m.name + ' left hospital!', {
            body: 'Lv' + m.level + ' β€” available to attack now',
            icon: 'https://www.torn.com/favicon.ico',
            tag:  'wtt-hosp-' + m.id
        });
        n.onclick = function() { window.open('https://www.torn.com/loader.php?sid=attack&user2ID=' + m.id); };
        setTimeout(function() { n.close(); }, 8000);
    } catch(e) { console.warn('[TargetTracker] Notification failed:', e); }
}

/* ─────────────────────────────────────────
   RENDER HELPERS
───────────────────────────────────────── */
var STATUS_COLOR = { hosp: C.hosp, travel: C.travel, jail: C.jail, okay: C.okay };
var STATUS_BG    = { hosp: 'rgba(140,10,10,0.12)', travel: 'rgba(95,65,4,0.08)', jail: 'rgba(8,26,78,0.1)', okay: 'transparent' };
var STATUS_BDR   = { hosp: '#c03030', travel: '#c89820', jail: '#3a60c0', okay: 'transparent' };

function sortList(list) {
    var o = { okay:0, hosp:1, travel:2, jail:3 };
    return list.slice().sort(function(a,b) {
        var d = (o[a.status.type]||3) - (o[b.status.type]||3);
        return d !== 0 ? d : b.level - a.level;
    });
}

function filterList(list, f) {
    if (f === 'all') return list;
    return list.filter(function(m) { return m.status.type === f; });
}

function makeRow(m, idx, showRemove) {
    var st     = m.status;
    var col    = STATUS_COLOR[st.type] || C.text;
    var bg     = STATUS_BG[st.type]    || 'transparent';
    var bdr    = STATUS_BDR[st.type]   || 'transparent';
    var anim   = st.type === 'hosp' ? 'wtt-pulse-red' : st.type === 'travel' ? 'wtt-pulse-amb' : '';
    var canAtk = st.type === 'okay';
    var atkUrl  = 'https://www.torn.com/loader.php?sid=attack&user2ID=' + m.id;
    var dotCol  = { hosp:'#ff5555', travel:'#f0b030', jail:'#78a8ff', okay:'#66bb66' };

    // ── row: 3 cols β€” rank | name block | attack button ──
    var row = document.createElement('div');
    row.setAttribute('style',
        'display:grid !important;' +
        'grid-template-columns:14px 1fr auto !important;' +
        'align-items:center !important;gap:6px !important;' +
        'padding:5px 8px !important;border-bottom:1px solid rgba(40,10,10,0.35) !important;' +
        'border-left:3px solid ' + bdr + ' !important;background:' + bg + ' !important;' +
        'box-sizing:border-box !important;'
    );
    row.dataset.id = m.id;

    // col 1 β€” rank number
    var rank = document.createElement('div');
    rank.textContent = idx + 1;
    rank.setAttribute('style',
        'font-family:Consolas,monospace !important;font-size:8px !important;' +
        'color:rgba(120,120,120,0.6) !important;text-align:center !important;'
    );

    // col 2 β€” name (fixed 110px) + dot + sub below
    var nameBlock = document.createElement('div');
    nameBlock.setAttribute('style', 'overflow:hidden !important;');

    var nameRow = document.createElement('div');
    nameRow.setAttribute('style',
        'display:flex !important;align-items:center !important;gap:4px !important;'
    );

    var nameEl = document.createElement('div');
    nameEl.textContent = m.name;
    nameEl.setAttribute('style',
        'font-size:12px !important;font-weight:600 !important;color:' + C.text + ' !important;' +
        'white-space:nowrap !important;overflow:hidden !important;text-overflow:ellipsis !important;' +
        'max-width:110px !important;'
    );

    var dot = document.createElement('div');
    dot.className = anim;
    dot.setAttribute('style',
        'width:7px !important;height:7px !important;border-radius:50% !important;flex-shrink:0 !important;' +
        'background:' + (dotCol[st.type] || '#888') + ' !important;'
    );

    nameRow.appendChild(nameEl);
    nameRow.appendChild(dot);

    var sub = document.createElement('div');
    sub.textContent = st.sub;
    sub.setAttribute('style',
        'font-family:Consolas,monospace !important;font-size:9px !important;color:' + col + ' !important;' +
        'white-space:nowrap !important;overflow:hidden !important;text-overflow:ellipsis !important;' +
        'margin-top:1px !important;'
    );

    nameBlock.appendChild(nameRow);
    nameBlock.appendChild(sub);

    // col 3 β€” attack: plain <a> with official Torn attack URL, appended to body not panel
    var atkWrap = document.createElement('div');
    atkWrap.setAttribute('style', 'flex-shrink:0 !important;');

    if (canAtk) {
        var atkLink = document.createElement('a');
        atkLink.href = atkUrl;
        atkLink.textContent = 'βš” ATK';
        atkLink.setAttribute('style',
            'display:block !important;font-family:Consolas,monospace !important;' +
            'font-size:9px !important;font-weight:700 !important;' +
            'padding:4px 7px !important;border-radius:4px !important;' +
            'border:1px solid rgba(60,180,60,0.8) !important;' +
            'background:rgba(10,100,10,0.55) !important;' +
            'color:#88ff88 !important;white-space:nowrap !important;' +
            'text-decoration:none !important;-webkit-tap-highlight-color:rgba(60,200,60,0.4) !important;'
        );
        atkWrap.appendChild(atkLink);
    } else {
        var atkDim = document.createElement('div');
        atkDim.textContent = st.type === 'hosp' ? 'πŸ₯' : st.type === 'travel' ? '✈' : st.type === 'jail' ? 'πŸ”’' : 'β€”';
        atkDim.setAttribute('style',
            'font-size:16px !important;color:rgba(160,160,160,0.55) !important;' +
            'text-align:center !important;width:36px !important;line-height:1 !important;'
        );
        atkWrap.appendChild(atkDim);
    }

    row.appendChild(rank);
    row.appendChild(nameBlock);
    row.appendChild(atkWrap);

    return row;
}

function makeSectionLabel(text) {
    var el = document.createElement('div');
    el.textContent = text;
    el.style.cssText = 'padding:3px 10px;font-size:9px;font-weight:700;letter-spacing:1px;text-transform:uppercase;color:rgba(180,72,72,0.65);background:rgba(72,8,8,0.15);border-bottom:1px solid rgba(72,8,8,0.22);font-family:Consolas,monospace;';
    return el;
}

function makeEmpty(icon, msg) {
    var el = document.createElement('div');
    el.style.cssText = 'padding:28px 16px;text-align:center;color:rgba(150,90,90,0.65);font-size:11px;line-height:1.5;font-family:Arial,sans-serif;';
    el.innerHTML = '<div style="font-size:22px;margin-bottom:8px;">' + icon + '</div>' + msg;
    return el;
}

/* ─────────────────────────────────────────
   RENDER: WAR
───────────────────────────────────────── */
function renderWarBody() {
    var body = panelEl.querySelector('.wtt-body');
    body.innerHTML = '';
    if (!cfg.factionId || !cfg.apiKey) {
        body.appendChild(makeEmpty('βš”', 'Set your API key &amp; Your Faction ID in Settings')); return;
    }
    if (!warScore) {
        body.appendChild(makeEmpty('🏳', 'No active ranked war')); return;
    }
    // Show the war card even while enemy roster is still loading
    body.appendChild(makeWarCard(false));
    if (!enemyRoster.length) {
        body.appendChild(makeEmpty('β—Œ', 'Loading enemy roster…')); return;
    }

    // ── Enemy roster (primary β€” these are your targets) ──
    var enemyList = filterList(sortList(enemyRoster), activeFilter);
    if (enemyList.length) {
        var enemyName2 = (enemyData && enemyData.name) ? enemyData.name : (warScore ? warScore.themName : 'Enemy');
        var groups = {};
        enemyList.forEach(function(m) { if (!groups[m.status.type]) groups[m.status.type] = []; groups[m.status.type].push(m); });
        var SECS = [['okay','🟒 Active'],['hosp','πŸ₯ Hospitalized'],['travel','✈ Traveling'],['jail','πŸ”’ Jailed']];
        var idx = 0;

        if (activeFilter !== 'all') {
            var lbl = (SECS.find(function(s){ return s[0] === activeFilter; }) || ['',''])[1];
            body.appendChild(makeSectionLabel('🎯 ' + enemyName2.toUpperCase() + ' · ' + lbl + ' (' + enemyList.length + ')'));
            enemyList.forEach(function(m, i) { body.appendChild(makeRow(m, i, false)); });
        } else {
            SECS.forEach(function(sec) {
                var g = groups[sec[0]];
                if (!g || !g.length) return;
                body.appendChild(makeSectionLabel('🎯 ' + sec[1] + ' (' + g.length + ')'));
                g.forEach(function(m) { body.appendChild(makeRow(m, idx++, false)); });
            });
        }
    } else if (warScore) {
        body.appendChild(makeEmpty('β—Œ', 'Loading enemy roster…'));
    }
}

/* ─────────────────────────────────────────
   RENDER: CHAIN
───────────────────────────────────────── */
function renderChainBody() {
    var body = panelEl.querySelector('.wtt-body');
    body.innerHTML = '';

    // ── add-input row (always visible) ──
    var addWrap = document.createElement('div');
    addWrap.style.cssText = 'display:flex;gap:5px;padding:6px 8px;border-bottom:1px solid rgba(55,8,8,0.4);flex-shrink:0;';
    var cInp = document.createElement('input');
    cInp.type = 'text'; cInp.placeholder = 'Add player ID'; cInp.maxLength = 12;
    cInp.style.cssText = 'flex:1;padding:5px 8px;background:rgba(18,4,4,0.9);border:1px solid rgba(95,16,16,0.45);border-radius:4px;color:#d4d4d4;font-size:11px;font-family:Consolas,monospace;outline:none;';
    cInp.addEventListener('focus', function(){ cInp.style.borderColor = 'rgba(195,44,44,0.7)'; });
    cInp.addEventListener('blur',  function(){ cInp.style.borderColor = 'rgba(95,16,16,0.45)'; });
    var cAdd = document.createElement('button');
    cAdd.type = 'button'; cAdd.textContent = 'ADD';
    cAdd.style.cssText = 'padding:5px 11px;background:rgba(140,16,16,0.5);border:1px solid rgba(175,36,36,0.55);border-radius:4px;color:#df5555;font-size:10px;font-weight:700;cursor:pointer;font-family:Arial,sans-serif;';
    async function doAdd() {
        var id = cleanId(cInp.value); cInp.value = '';
        if (!id) { toast('⚠ Enter a numeric player ID'); return; }
        if (!cfg.apiKey) { toast('⚠ Add your API key in Settings'); return; }
        var ids = cfg.getChainIds();
        if (ids.indexOf(id) !== -1) { toast('Already tracking ' + id); return; }
        toast('Looking up ' + id + '…', 3000);
        var player = await fetchPlayer(id);
        if (!player) return;
        ids.push(id); cfg.setChainIds(ids);
        chainCache.push(player);
        toast('βœ“ Added ' + player.name);
        renderChainBody();
    }
    cAdd.addEventListener('click', doAdd);
    cInp.addEventListener('keydown', function(e){ if (e.key === 'Enter') doAdd(); });
    addWrap.appendChild(cInp); addWrap.appendChild(cAdd);
    body.appendChild(addWrap);

    if (!cfg.apiKey) { body.appendChild(makeEmpty('πŸ”‘', 'Add your API key in Settings')); return; }

    // ── Chain Watcher card β€” always shown when API key is set ──
    (function() {
        var card = document.createElement('div');
        card.id = 'wtt-cw-card';
        card.style.cssText = 'margin:8px 8px 0;border-radius:8px;padding:10px 12px;border:1px solid rgba(80,20,20,0.4);background:rgba(10,3,3,0.65);flex-shrink:0;transition:border-color 0.3s;';

        // Header row: label + state badge
        var hRow = document.createElement('div');
        hRow.style.cssText = 'display:flex;justify-content:space-between;align-items:center;margin-bottom:6px;';
        var hLbl = document.createElement('span');
        hLbl.textContent = 'β›“ CHAIN WATCHER';
        hLbl.style.cssText = 'font-size:9px;font-weight:700;font-family:Consolas,monospace;color:rgba(200,160,60,0.8);letter-spacing:.6px;';
        var stateEl = document.createElement('span');
        stateEl.id = 'wtt-cw-state';
        stateEl.style.cssText = 'font-size:8px;font-weight:700;font-family:Consolas,monospace;color:rgba(140,120,60,0.65);letter-spacing:.5px;';
        stateEl.textContent = cwEnabled ? (cwCount > 0 ? 'CHAINING' : 'Waiting…') : 'Off β€” enable in Settings';
        hRow.appendChild(hLbl); hRow.appendChild(stateEl);

        // Big timer + count row
        var midRow = document.createElement('div');
        midRow.style.cssText = 'display:flex;justify-content:space-between;align-items:flex-end;margin-bottom:7px;';

        var timerEl = document.createElement('div');
        timerEl.id = 'wtt-cw-timer';
        timerEl.style.cssText = 'font-size:34px;font-weight:700;font-family:Consolas,monospace;line-height:1;color:rgba(150,100,100,0.5);letter-spacing:2px;';
        timerEl.textContent = '--:--';

        var countWrap = document.createElement('div');
        countWrap.style.cssText = 'text-align:right;';
        var countLbl = document.createElement('div');
        countLbl.style.cssText = 'font-size:8px;font-family:Consolas,monospace;color:rgba(140,120,80,0.55);margin-bottom:1px;';
        countLbl.textContent = 'HITS';
        var countEl = document.createElement('div');
        countEl.id = 'wtt-cw-count';
        countEl.style.cssText = 'font-size:18px;font-weight:700;font-family:Consolas,monospace;color:' + C.travel + ';line-height:1;';
        countEl.textContent = 'β€”';
        countWrap.appendChild(countLbl); countWrap.appendChild(countEl);
        midRow.appendChild(timerEl); midRow.appendChild(countWrap);

        // Progress bar (5:00 = 100%)
        var barTrack = document.createElement('div');
        barTrack.style.cssText = 'height:4px;background:rgba(30,10,10,0.6);border-radius:2px;overflow:hidden;';
        var barFill = document.createElement('div');
        barFill.id = 'wtt-cw-bar';
        barFill.style.cssText = 'height:100%;width:0%;background:#66bb66;border-radius:2px;transition:width 0.9s linear,background 0.4s;';
        barTrack.appendChild(barFill);

        // "Enable in Settings" hint if off
        if (!cwEnabled) {
            var hint = document.createElement('div');
            hint.style.cssText = 'font-size:9px;color:rgba(140,100,60,0.55);font-family:Consolas,monospace;margin-top:5px;';
            hint.textContent = 'βš™ Enable Chain Watcher in Settings β†’ β›“ Chain Watcher';
            card.appendChild(hRow); card.appendChild(midRow); card.appendChild(barTrack); card.appendChild(hint);
        } else {
            card.appendChild(hRow); card.appendChild(midRow); card.appendChild(barTrack);
        }
        body.appendChild(card);

        // Initial render
        cwUpdateUI();
    })();

    // ── chain target guards ──
    var ids = cfg.getChainIds();
    if (!ids.length) { body.appendChild(makeEmpty('+', 'Enter a player ID above to start tracking')); return; }
    if (!chainCache.length) {
        body.appendChild(makeEmpty('β—Œ', 'Loading ' + ids.length + ' target' + (ids.length !== 1 ? 's' : '') + '…'));
        return;
    }

    // ── target rows ──
    var CHAIN_SECS = [['okay','🟒 Active'],['hosp','πŸ₯ Hospitalized'],['travel','✈ Traveling'],['jail','πŸ”’ Jailed']];
    var list = filterList(sortList(chainCache), activeFilter);
    if (!list.length) { body.appendChild(makeEmpty('β€”', 'No targets match this filter')); return; }

    if (activeFilter !== 'all') {
        var secLbl = (CHAIN_SECS.find(function(s){ return s[0] === activeFilter; }) || ['',''])[1];
        body.appendChild(makeSectionLabel(secLbl + ' (' + list.length + ')'));
        list.forEach(function(m, i) { body.appendChild(makeRow(m, i, true)); });
    } else {
        var groups = {};
        list.forEach(function(m) { if (!groups[m.status.type]) groups[m.status.type] = []; groups[m.status.type].push(m); });
        var idx = 0;
        CHAIN_SECS.forEach(function(sec) {
            var g = groups[sec[0]];
            if (!g || !g.length) return;
            body.appendChild(makeSectionLabel(sec[1] + ' (' + g.length + ')'));
            g.forEach(function(m) { body.appendChild(makeRow(m, idx++, true)); });
        });
    }
}

/* ─────────────────────────────────────────
   WAR CARD β€” used on both Dash and War tabs
───────────────────────────────────────── */
// showStats=true on war tab, false on dash
function makeWarCard(showStats) {
    var winning = warScore.us >= warScore.them;
    var usD  = ownFactionData || {};
    var thD  = enemyData     || {};

    var card = document.createElement('div');
    card.style.cssText = 'border-radius:8px;border:1px solid rgba(120,16,16,0.45);background:rgba(14,2,2,0.7);overflow:hidden;flex-shrink:0;';

    // Score bar top
    var total = warScore.us + warScore.them;
    var usPct = total > 0 ? Math.round(warScore.us / total * 100) : 50;
    var barOuter = document.createElement('div');
    barOuter.style.cssText = 'height:5px;background:rgba(140,20,20,0.3);';
    var barInner = document.createElement('div');
    barInner.style.cssText = 'height:100%;width:' + usPct + '%;background:' + (winning ? C.okay : C.hosp) + ';transition:width 0.6s ease;';
    barOuter.appendChild(barInner);
    card.appendChild(barOuter);

    // Two faction panels
    var panels = document.createElement('div');
    panels.style.cssText = 'display:grid;grid-template-columns:1fr 1fr;';

    function makeFacPanel(name, score, scoreColor, statusLabel, data, isLeft) {
        var p = document.createElement('div');
        p.style.cssText = 'padding:9px 10px;display:flex;flex-direction:column;gap:3px;' + (isLeft ? 'border-right:1px solid rgba(80,10,10,0.4);' : '');

        var nameEl = document.createElement('div');
        nameEl.textContent = name.toUpperCase();
        nameEl.style.cssText = 'font-size:9px;font-weight:700;letter-spacing:.7px;color:rgba(200,200,200,0.75);font-family:Consolas,monospace;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;';
        p.appendChild(nameEl);

        var scoreEl = document.createElement('div');
        scoreEl.textContent = score.toLocaleString();
        scoreEl.style.cssText = 'font-size:24px;font-weight:700;font-family:Consolas,monospace;line-height:1;color:' + scoreColor + ';';
        p.appendChild(scoreEl);

        var statusEl = document.createElement('div');
        statusEl.textContent = statusLabel;
        statusEl.style.cssText = 'font-size:8px;font-weight:700;color:' + scoreColor + ';font-family:Consolas,monospace;opacity:0.85;';
        p.appendChild(statusEl);

        if (showStats) {
            var divider = document.createElement('div');
            divider.style.cssText = 'border-top:1px solid rgba(80,10,10,0.3);margin:4px 0;';
            p.appendChild(divider);

            function stat(label, val, title) {
                if (val === null || val === undefined || val === '') return;
                var row = document.createElement('div');
                row.title = title || '';
                row.style.cssText = 'display:flex;justify-content:space-between;gap:4px;';
                var lbl = document.createElement('span');
                lbl.textContent = label;
                lbl.style.cssText = 'font-size:8px;color:rgba(140,120,120,0.7);font-family:Consolas,monospace;';
                var v = document.createElement('span');
                v.textContent = val;
                v.style.cssText = 'font-size:8px;color:rgba(200,180,180,0.85);font-family:Consolas,monospace;font-weight:600;text-align:right;';
                row.appendChild(lbl); row.appendChild(v);
                p.appendChild(row);
            }

            if (data.respect)              stat('Respect',    data.respect.toLocaleString(), 'Total faction respect');
            if (data.rankName)             stat('Rank',       data.rankName + (data.rankDiv ? ' ' + data.rankDiv : ''), 'War rank');
            if (data.wins !== undefined)   stat('W / L',      data.wins + ' / ' + data.losses, 'Ranked war wins / losses');
            if (data.memberCount)          stat('Members',    data.memberCount + ' / ' + (data.capacity || '?'), 'Members / capacity');
            if (data.bestChain)            stat('Best chain', data.bestChain, 'Best chain hit');
        }
        return p;
    }

    var usColor   = winning ? C.okay : C.hosp;
    var themColor = !winning ? C.okay : C.hosp;
    panels.appendChild(makeFacPanel(warScore.usName   || 'Us',    warScore.us,   usColor,   winning  ? 'β–² WINNING' : 'β–Ό LOSING', usD, true));
    panels.appendChild(makeFacPanel(warScore.themName || 'Enemy', warScore.them, themColor, !winning ? 'β–² WINNING' : 'β–Ό LOSING', thD, false));
    card.appendChild(panels);
    return card;
}

/* ─────────────────────────────────────────
   RENDER: DASHBOARD
───────────────────────────────────────── */
function renderDashBody() {
    var body = panelEl.querySelector('.wtt-body');
    body.innerHTML = '';

    if (!cfg.apiKey || !cfg.userId) {
        body.appendChild(makeEmpty('πŸ”‘', 'Set your API key &amp; User ID<br>in Settings to use the dashboard')); return;
    }
    if (!selfData) {
        body.appendChild(makeEmpty('β—Œ', 'Loading operator data…')); return;
    }

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

    /* ── health section ── */
    var hp   = selfData.life || {};
    var cur  = hp.current  || 0;
    var max  = hp.maximum  || 1;
    var pct  = Math.min(100, Math.round(cur / max * 100));
    var intv = hp.interval  || 300;
    var inc  = hp.increment || 0;

    var barColor = '#1a6ea8';
    var valColor = pct >= 100 ? '#5ab4f0' : pct >= 60 ? '#4a9cd4' : pct >= 30 ? '#3a7aaa' : '#2a5888';

    var st = parseStatus(selfData);
    var stCol = STATUS_COLOR[st.type] || C.okay;

    function sec(titleText) {
        var s = document.createElement('div');
        var t = document.createElement('div');
        t.textContent = titleText;
        t.style.cssText = 'font-size:9px;font-weight:700;letter-spacing:1.2px;text-transform:uppercase;color:rgba(200,72,72,0.75);margin-bottom:7px;padding-bottom:4px;border-bottom:1px solid rgba(130,16,16,0.3);font-family:Consolas,monospace;';
        s.appendChild(t);
        return s;
    }

    /* ── energy data (needed inside health card) ── */
    var energy = selfData.energy || {};
    var nCur  = energy.current  || 0;
    var nMax  = energy.maximum  || 0;
    var nPct  = nMax > 0 ? Math.min(100, Math.round(nCur / nMax * 100)) : 0;
    var nIntv = energy.ticktime || 300;

    /* health card */
    var hpSec  = sec('⚑ Operator Status');
    var card   = document.createElement('div');
    card.style.cssText = 'background:rgba(4,14,28,0.6);border:1px solid rgba(30,90,160,0.35);border-radius:7px;padding:10px 12px;';

    var topRow = document.createElement('div');
    topRow.style.cssText = 'display:flex;justify-content:space-between;align-items:center;margin-bottom:7px;';
    var hpLbl = document.createElement('span');
    hpLbl.textContent = 'HEALTH';
    hpLbl.style.cssText = 'font-size:10px;font-weight:700;color:rgba(100,170,230,0.85);letter-spacing:.4px;font-family:Arial,sans-serif;';
    var hpVal = document.createElement('span');
    hpVal.textContent = cur.toLocaleString() + ' / ' + max.toLocaleString();
    hpVal.style.cssText = 'font-size:14px;font-weight:700;color:' + valColor + ';font-family:Consolas,monospace;';
    topRow.appendChild(hpLbl); topRow.appendChild(hpVal);

    var barWrap = document.createElement('div');
    barWrap.style.cssText = 'height:7px;background:rgba(8,20,40,0.7);border-radius:4px;overflow:hidden;margin-bottom:8px;border:1px solid rgba(20,70,120,0.4);';
    var bar = document.createElement('div');
    bar.style.cssText = 'height:100%;border-radius:4px;width:' + pct + '%;background:' + barColor + ';transition:width 0.6s ease;';
    barWrap.appendChild(bar);

    // Regen sub-label shown under the HP value
    var regenRate = inc > 0 ? inc : Math.round(max * 0.05);
    var hpSubLbl = document.createElement('div');
    hpSubLbl.id = 'wtt-regen-val';
    hpSubLbl.style.cssText = 'font-size:9px;color:rgba(80,140,200,0.65);font-family:Consolas,monospace;text-align:right;margin-top:2px;';
    if (pct >= 100) { hpSubLbl.textContent = 'βœ“ Full'; }
    else { hpSubLbl.textContent = '+' + regenRate + ' HP / ' + fmtSecs(intv) + ' Β· full in ' + fmtSecs(Math.ceil((max - cur) / regenRate) * intv); }

    // ── Energy sub-section inside status card ──
    var divider = document.createElement('div');
    divider.style.cssText = 'border-top:1px solid rgba(20,80,20,0.3);margin:8px 0 8px;';

    var eLbl = document.createElement('span');
    eLbl.textContent = 'ENERGY';
    eLbl.style.cssText = 'font-size:10px;font-weight:700;color:rgba(100,200,100,0.85);letter-spacing:.4px;font-family:Arial,sans-serif;';
    var eVal = document.createElement('span');
    eVal.textContent = nCur + ' / ' + nMax;
    eVal.style.cssText = 'font-size:14px;font-weight:700;color:' + (nPct >= 75 ? C.okay : nPct >= 40 ? C.travel : C.hosp) + ';font-family:Consolas,monospace;';
    var eTopRow = document.createElement('div');
    eTopRow.style.cssText = 'display:flex;justify-content:space-between;align-items:center;margin-bottom:7px;';
    eTopRow.appendChild(eLbl); eTopRow.appendChild(eVal);

    var eBarWrap = document.createElement('div');
    eBarWrap.style.cssText = 'height:7px;background:rgba(4,24,8,0.7);border-radius:4px;overflow:hidden;margin-bottom:8px;border:1px solid rgba(20,100,40,0.4);';
    var eBar = document.createElement('div');
    eBar.style.cssText = 'height:100%;border-radius:4px;width:' + nPct + '%;background:#1e9e3a;transition:width 0.6s ease;';
    eBarWrap.appendChild(eBar);

    var eRegenRow = document.createElement('div');
    eRegenRow.style.cssText = 'display:flex;justify-content:space-between;align-items:center;margin-bottom:6px;';
    var eRegenVal = document.createElement('span');
    eRegenVal.id = 'wtt-nerve-val';
    eRegenVal.style.cssText = 'font-size:9.5px;font-weight:700;color:' + C.okay + ';font-family:Consolas,monospace;letter-spacing:.4px;flex:1;';
    var nTicksLeft = (nMax - nCur) * nIntv;
    eRegenVal.textContent = nPct >= 100 ? 'βœ“ Full' : '+1 / ' + fmtSecs(nIntv) + ' Β· full in ' + fmtSecs(nTicksLeft);
    var refillBtn = document.createElement('button');
    refillBtn.type = 'button';
    refillBtn.textContent = '⚑ Refill';
    refillBtn.style.cssText = 'padding:3px 9px;border-radius:4px;font-size:9px;font-weight:700;cursor:pointer;font-family:Arial,sans-serif;background:rgba(15,60,15,0.5);border:1px solid rgba(40,140,40,0.5);color:' + C.okay + ';flex-shrink:0;';
    refillBtn.addEventListener('mouseover', function(){ refillBtn.style.setProperty('background','rgba(20,90,20,0.7)','important'); });
    refillBtn.addEventListener('mouseout',  function(){ refillBtn.style.setProperty('background','rgba(15,60,15,0.5)','important'); });
    refillBtn.addEventListener('click', function() {
        var dest = cfg.refillDest;
        var url = dest === 'points'
            ? 'https://www.torn.com/points.php'
            : 'https://www.torn.com/item.php#energy-d-items';
        window.location.assign(url);
    });
    eRegenRow.appendChild(eRegenVal); eRegenRow.appendChild(refillBtn);

    // Hits available (25 energy per attack)
    var hitsAvail = Math.floor(nCur / 25);
    var hitsRow = document.createElement('div');
    hitsRow.style.cssText = 'display:flex;justify-content:space-between;align-items:center;margin-top:2px;';
    var hitsLbl = document.createElement('span');
    hitsLbl.textContent = 'βš” HITS AVAILABLE';
    hitsLbl.style.cssText = 'font-size:9px;color:rgba(100,150,100,0.65);font-family:Consolas,monospace;';
    var hitsVal = document.createElement('span');
    hitsVal.textContent = hitsAvail + ' hits  (25 energy each)';
    hitsVal.style.cssText = 'font-size:9.5px;font-weight:700;color:' + (hitsAvail >= 5 ? C.okay : hitsAvail >= 2 ? C.travel : C.hosp) + ';font-family:Consolas,monospace;';
    hitsRow.appendChild(hitsLbl); hitsRow.appendChild(hitsVal);

    card.appendChild(eTopRow); card.appendChild(eBarWrap); card.appendChild(eRegenRow); card.appendChild(hitsRow);
    card.appendChild(divider);
    card.appendChild(topRow); card.appendChild(barWrap); card.appendChild(hpSubLbl);
    hpSec.appendChild(card);

    /* start live regen clock */
    clearInterval(regenTimer);
    if (pct < 100) {
        var t0 = Date.now();
        var regenRateClock = inc > 0 ? inc : Math.round(max * 0.05);
        regenTimer = setInterval(function() {
            var el = document.getElementById('wtt-regen-val');
            if (!el) { clearInterval(regenTimer); return; }
            var elapsed = Math.floor((Date.now() - t0) / 1000);
            var gained  = Math.floor(elapsed / intv) * regenRateClock;
            var now2    = Math.min(cur + gained, max);
            if (now2 >= max) { el.textContent = 'βœ“ Full'; el.style.color = 'rgba(80,200,80,0.7)'; clearInterval(regenTimer); return; }
            var left = Math.ceil((max - now2) / regenRateClock) * intv - (elapsed % intv);
            el.textContent = '+' + regenRateClock + ' HP / ' + fmtSecs(intv) + ' Β· full in ' + fmtSecs(Math.max(0, left));
        }, 1000);
    }

    /* ── quick actions ── */
    var actSec = sec('πŸ’Š Quick Actions');
    function dashBtn(emoji, label, sublabel, href, cls) {
        var a = document.createElement('a');
        a.href = href;
        a.addEventListener('click', function(e) {
            e.preventDefault();
            var u = href;
            setTimeout(function(){ window.location.assign(u); }, 0);
        });
        a.style.cssText = 'display:flex;align-items:center;gap:8px;padding:9px 10px;border-radius:7px;text-decoration:none;border:1px solid;transition:opacity 0.2s;';
        if (cls === 'medical') { a.style.background = 'rgba(16,46,16,0.55)'; a.style.borderColor = 'rgba(55,135,55,0.45)'; a.style.color = C.okay; }
        if (cls === 'short')   { a.style.background = 'rgba(75,36,4,0.4)';  a.style.borderColor = 'rgba(175,115,18,0.42)'; a.style.color = C.travel; }
        if (cls === 'mid')     { a.style.background = 'rgba(55,18,58,0.4)'; a.style.borderColor = 'rgba(135,55,175,0.42)'; a.style.color = '#bb77dd'; }
        if (cls === 'long')    { a.style.background = 'rgba(4,26,66,0.4)';  a.style.borderColor = 'rgba(36,86,175,0.42)';  a.style.color = '#5888dd'; }
        a.addEventListener('mouseover', function() { a.style.opacity = '0.8'; });
        a.addEventListener('mouseout',  function() { a.style.opacity = '1'; });
        var icon = document.createElement('span');
        icon.textContent = emoji;
        icon.style.cssText = 'font-size:16px;flex-shrink:0;';
        var txt = document.createElement('span');
        txt.style.cssText = 'display:flex;flex-direction:column;';
        var lbl = document.createElement('span');
        lbl.textContent = label;
        lbl.style.cssText = 'font-size:11px;font-weight:700;letter-spacing:.5px;font-family:Arial,sans-serif;';
        var sub = document.createElement('span');
        sub.textContent = sublabel;
        sub.style.cssText = 'font-size:8.5px;opacity:0.65;font-family:Arial,sans-serif;margin-top:1px;';
        txt.appendChild(lbl); txt.appendChild(sub);
        a.appendChild(icon); a.appendChild(txt);
        return a;
    }

    actSec.appendChild(dashBtn('🩹', 'MEDICAL ITEMS', 'Open inventory β†’ Medical', 'https://www.torn.com/item.php#medical-items', 'medical'));

    /* ── escape section ── */
    var escSec = sec('✈ Escape');
    var escGrid = document.createElement('div');
    escGrid.style.cssText = 'display:grid;grid-template-columns:1fr 1fr;gap:7px;';
    // Travel links β€” navigate to travel page and auto-click the country dropdown
    function clickTravelCountry(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;
    }

    // On travel page load β€” check URL hash for target country and poll-click it
    (function() {
        var target = null;
        try {
            var m = (location.hash || '').match(/wtt_travel=([^&]+)/);
            if (m) target = decodeURIComponent(m[1]);
        } catch(e) {}
        if (!target || (location.pathname + location.search).indexOf('sid=travel') === -1) return;
        var _attempts = 0;
        var _poll = setInterval(function() {
            _attempts++;
            if (clickTravelCountry(target)) { clearInterval(_poll); return; }
            if (_attempts >= 40) clearInterval(_poll);
        }, 300);
    })();

    function travelBtn(emoji, label, sublabel, country, cls) {
        var b = dashBtn(emoji, label, sublabel, 'https://www.torn.com/page.php?sid=travel', cls);
        b.onclick = function() {
            var onTravelPage = (location.pathname + location.search).indexOf('sid=travel') !== -1;
            if (onTravelPage) {
                clickTravelCountry(country);
            } else {
                window.location.href = 'https://www.torn.com/page.php?sid=travel#wtt_travel=' + encodeURIComponent(country);
            }
        };
        return b;
    }

    escGrid.appendChild(travelBtn('πŸ‡²πŸ‡½', 'SHORT',  'Mexico Β· ~1.5h',                    'Mexico',       'short'));
    escGrid.appendChild(travelBtn('πŸ‡―πŸ‡΅', 'MEDIUM', 'Japan Β· ~12h',                      'Japan',        'mid'));
    var longBtn = travelBtn('πŸ‡ΏπŸ‡¦', 'LONG', 'South Africa Β· ~16h Β· max protection', 'South Africa', 'long');
    longBtn.style.gridColumn = '1 / -1';
    escGrid.appendChild(longBtn);
    escSec.appendChild(escGrid);

    // live energy tick clock
    clearInterval(nerveTimer);
    if (nPct < 100 && nIntv > 0) {
        var nt0 = Date.now();
        nerveTimer = setInterval(function() {
            var el = document.getElementById('wtt-nerve-val');
            if (!el) { clearInterval(nerveTimer); return; }
            var elapsed = Math.floor((Date.now() - nt0) / 1000);
            var gained  = Math.floor(elapsed / nIntv);
            var nowN    = Math.min(nCur + gained, nMax);
            if (nowN >= nMax) { el.textContent = 'βœ“ FULL'; clearInterval(nerveTimer); return; }
            var left = (nMax - nowN) * nIntv - (elapsed % nIntv);
            el.textContent = '+1 / ' + fmtSecs(nIntv) + ' Β· full in ' + fmtSecs(Math.max(0, left));
        }, 1000);
    }

    // ── War card on dashboard (only if confirmed at war) ──
    if (warScore) { wrap.appendChild(makeWarCard(true)); }
    wrap.appendChild(hpSec);
    wrap.appendChild(actSec);
    wrap.appendChild(escSec);
    body.appendChild(wrap);
}

/* ─────────────────────────────────────────
   STATUS BAR
───────────────────────────────────────── */
function renderStatusBar() {
    var bar = panelEl ? panelEl.querySelector('#wtt-sbar') : null;
    if (!bar) return;
    bar.innerHTML = '';
    if (activeTab === 'dash') {
        var n = document.createElement('span');
        n.textContent = selfData ? selfData.name : '…';
        n.style.cssText = 'color:' + C.redDim + ';font-weight:700;';
        bar.appendChild(n);
    } else {
        var list = activeTab === 'war' ? enemyRoster : chainCache;
        var cnt  = document.createElement('span'); cnt.style.color = C.redDim; cnt.style.fontWeight = '700'; cnt.textContent = list.length;
        bar.appendChild(cnt);
        bar.appendChild(document.createTextNode(' targets'));
        var h = list.filter(function(m) { return m.status.type === 'hosp'; }).length;
        var j = list.filter(function(m) { return m.status.type === 'jail'; }).length;
        var v = list.filter(function(m) { return m.status.type === 'travel'; }).length;
        if (h) { var s = document.createElement('span'); s.style.color = C.hosp;   s.textContent = ' πŸ₯' + h; bar.appendChild(s); }
        if (j) { var s = document.createElement('span'); s.style.color = C.jail;   s.textContent = ' πŸ”’' + j; bar.appendChild(s); }
        if (v) { var s = document.createElement('span'); s.style.color = C.travel; s.textContent = ' ✈' + v;  bar.appendChild(s); }
    }
    if (isLoading) {
        var sp = document.createElement('span');
        sp.className = 'wtt-spin-anim';
        sp.textContent = 'β—Œ';
        sp.style.cssText = 'margin-left:auto;color:rgba(200,80,80,0.6);';
        bar.appendChild(sp);
    }
}

/* ─────────────────────────────────────────
   CHAIN BADGE on toggle
───────────────────────────────────────── */
function updateChainBadge() {
    var old = document.getElementById('wtt-chain-badge');
    if (old) old.remove();
    if (!factionChain || factionChain.current < 10) return;
    var badge = document.createElement('div');
    badge.id = 'wtt-chain-badge';
    badge.className = 'wtt-blink';
    badge.setAttribute('style',
        'position:absolute !important;top:-6px !important;right:-8px !important;' +
        'background:linear-gradient(135deg,#7a0000,#c01818) !important;' +
        'border:1px solid rgba(255,70,70,0.55) !important;border-radius:9px !important;' +
        'padding:1px 6px !important;font-size:8.5px !important;font-weight:700 !important;' +
        'color:#ffe0e0 !important;font-family:Consolas,monospace !important;' +
        'white-space:nowrap !important;line-height:1.5 !important;' +
        'pointer-events:none !important;z-index:999992 !important;' +
        'box-shadow:0 0 6px rgba(200,30,30,0.55) !important;'
    );
    var timeout = factionChain.timeout > 0 ? fmtUntil(factionChain.timeout) : '';
    badge.textContent = 'β›“' + factionChain.current + (timeout ? ' Β· ' + timeout : '');
    toggleEl.style.position = 'fixed'; // ensure relative context
    toggleEl.style.overflow = 'visible';
    toggleEl.appendChild(badge);


}

/* ─────────────────────────────────────────
   CHAIN EXPORT
───────────────────────────────────────── */
function exportChain() {
    if (!chainCache.length) { toast('⚠ No chain targets to export'); return; }
    var lines = chainCache.map(function(m, i) {
        return (i + 1) + '. ' + m.name + ' [' + m.id + '] | Lv' + m.level + ' | ' + m.status.label + ' | https://www.torn.com/profiles.php?XID=' + m.id;
    });
    var text = '=== WTT Chain Targets β€” ' + new Date().toLocaleString() + ' ===\n' + lines.join('\n');
    if (navigator.clipboard && navigator.clipboard.writeText) {
        navigator.clipboard.writeText(text).then(function() { toast('βœ“ Copied to clipboard!'); }).catch(function() { downloadText(text); });
    } else { downloadText(text); }
}
function downloadText(text) {
    var a = document.createElement('a');
    a.href = 'data:text/plain;charset=utf-8,' + encodeURIComponent(text);
    a.download = 'wtt-chain-' + Date.now() + '.txt';
    document.body.appendChild(a); a.click(); setTimeout(function() { a.remove(); }, 500);
    toast('βœ“ Chain list downloaded!');
}

/* ─────────────────────────────────────────
   ADD CHAIN TARGET
───────────────────────────────────────── */
async function addChainTarget(rawId) {
    var id = cleanId(rawId);
    console.log('[TargetTracker] addChainTarget id=' + id + ' raw=' + rawId);
    if (!id) { toast('⚠ Enter a numeric player ID'); return; }
    if (!cfg.apiKey) { toast('⚠ Add your API key in Settings first'); return; }
    var ids = cfg.getChainIds();
    if (ids.indexOf(id) !== -1) { toast('Already tracking player ' + id); return; }
    toast('Looking up player ' + id + '…', 3000);
    var player = await fetchPlayer(id);
    if (!player) return;
    ids.push(id);
    cfg.setChainIds(ids);
    chainCache.push(player);
    toast('βœ“ Added ' + player.name + ' to chain list');
    renderChainBody();
    renderStatusBar();
}

/* ─────────────────────────────────────────
   MAIN PANEL RENDER
───────────────────────────────────────── */
function syncWarTabVisibility() {
    if (!panelEl) return;
    var tabWar = panelEl.querySelector('[data-tab="war"]');
    if (!tabWar) return;
    var inWar = !!warScore;
    tabWar.style.display = inWar ? '' : 'none';
    // If we're on the war tab but war ended, switch to dash
    if (!inWar && activeTab === 'war') {
        activeTab = 'dash';
    }
}

function renderPanel() {
    if (!panelEl) return;
    renderStatusBar();

    // show/hide filter bar
    var filters = document.getElementById('wtt-filters');
    if (filters) filters.style.display = activeTab !== 'dash' ? 'flex' : 'none';

    if (activeTab === 'dash')  renderDashBody();
    if (activeTab === 'war')   renderWarBody();
    if (activeTab === 'chain') renderChainBody();
}



/* ─────────────────────────────────────────
   BUILD PANEL
───────────────────────────────────────── */
function buildPanel() {
    // Inline style the entire panel shell
    panelEl.setAttribute('style',
        'position:fixed !important;' +
        'width:0 !important;' +
        'opacity:0 !important;' +
        'max-height:84vh !important;' +
        'background:linear-gradient(158deg,rgba(16,4,4,0.98),rgba(9,9,11,0.97)) !important;' +
        'border:1px solid rgba(155,18,18,0.4) !important;' +
        'border-radius:11px !important;' +
        'z-index:999989 !important;' +
        'display:flex !important;' +
        'flex-direction:column !important;' +
        'box-shadow:0 10px 44px 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:#d4d4d4 !important;'
    );

    // ── HEADER ──
    var hdr = document.createElement('div');
    hdr.setAttribute('style', 'padding:8px 12px;background:linear-gradient(90deg,rgba(155,18,18,0.3),transparent);border-bottom:1px solid rgba(155,18,18,0.28);display:flex;align-items:center;gap:7px;flex-shrink:0;');

    var titleEl = document.createElement('span');
    titleEl.textContent = '🎯 Target Tracker';
    titleEl.setAttribute('style', 'font-size:10.5px;font-weight:700;letter-spacing:1.5px;text-transform:uppercase;color:#dd4444;flex:1;white-space:nowrap;font-family:Arial,sans-serif;');

    // chain timer now lives inside chain tab, not header

    function iconBtn(svg, title) {
        var 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:rgba(175,175,175,0.55);transition:color 0.18s;');
        b.addEventListener('mouseover', function() { b.style.color = '#dd5555'; });
        b.addEventListener('mouseout',  function() { b.style.color = 'rgba(175,175,175,0.55)'; });
        return b;
    }

    var 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>';
    var 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>';

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

    btnRefresh.addEventListener('click', function() {
        stopPolling();
        selfData = null; warRoster = []; chainCache = []; factionChain = null; ownFactionData = null;
        lastWarsFetch = 0; // force wars re-fetch immediately
        refreshAll().then(startPolling);
        toast('Refreshing…', 1500);
    });
    btnSettings.addEventListener('click', openSettings);

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

    // ── TABS ──
    var tabs = document.createElement('div');
    tabs.setAttribute('style', 'display:flex;flex-shrink:0;border-bottom:1px solid rgba(75,8,8,0.5);');

    function makeTab(label, key) {
        var 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:rgba(175,175,175,0.48);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', function() { if (!t.classList.contains('wtt-tab-active')) t.style.color = 'rgba(215,90,90,0.85)'; });
        t.addEventListener('mouseout',  function() { if (!t.classList.contains('wtt-tab-active')) t.style.color = 'rgba(175,175,175,0.48)'; });
        t.addEventListener('click', function() {
            tabs.querySelectorAll('[data-tab]').forEach(function(x) {
                x.classList.remove('wtt-tab-active');
                x.style.color = 'rgba(175,175,175,0.48)';
                x.style.borderBottomColor = 'transparent';
                x.style.background = 'transparent';
            });
            t.classList.add('wtt-tab-active');
            t.style.color = '#dd4444';
            t.style.borderBottomColor = '#b82828';
            t.style.background = 'rgba(130,8,8,0.1)';
            activeTab = key; activeFilter = 'all';
            setFilterActive('all');
            renderPanel();
        });
        return t;
    }

    var tabDash  = makeTab('πŸ“Ÿ Dash',  'dash');
    var tabWar   = makeTab('βš” War',   'war');
    var tabChain = makeTab('πŸ”— Chain', 'chain');
    // activate dash by default (or war if already on war tab)
    var startTab = (activeTab === 'war' && warScore) ? tabWar : tabDash;
    startTab.classList.add('wtt-tab-active');
    startTab.style.color = '#dd4444'; startTab.style.borderBottomColor = '#b82828'; startTab.style.background = 'rgba(130,8,8,0.1)';
    // War tab only visible when at war
    tabWar.style.display = warScore ? '' : 'none';
    tabs.appendChild(tabDash); tabs.appendChild(tabWar); tabs.appendChild(tabChain);



    // ── FILTERS ──
    var filterBar = document.createElement('div');
    filterBar.id = 'wtt-filters';
    filterBar.setAttribute('style', 'display:none;gap:4px;padding:5px 8px;flex-shrink:0;flex-wrap:wrap;border-bottom:1px solid rgba(55,8,8,0.4);');

    var FILTERS = [['all','ALL',''],['okay','🟒 OKAY',C.okay],['hosp','πŸ₯ HOSP',C.hosp],['travel','✈ TRAVEL',C.travel],['jail','πŸ”’ JAIL',C.jail]];
    function setFilterActive(key) {
        filterBar.querySelectorAll('[data-filter]').forEach(function(f) {
            var col = FILTERS.find(function(x) { return x[0] === f.dataset.filter; });
            var active = f.dataset.filter === key;
            f.style.color  = active ? (col[2] || C.redDim) : 'rgba(155,155,155,0.72)';
            f.style.borderColor = active ? (col[2] || C.redDim) : 'rgba(75,75,75,0.28)';
            f.style.background  = active ? 'rgba(170,16,16,0.18)' : 'transparent';
        });
        activeFilter = key;
    }

    FILTERS.forEach(function(f) {
        var btn = document.createElement('span');
        btn.dataset.filter = f[0];
        btn.textContent = f[1];
        btn.setAttribute('style', 'padding:2px 7px;font-size:9px;font-weight:600;border-radius:3px;border:1px solid rgba(75,75,75,0.28);color:rgba(155,155,155,0.72);cursor:pointer;user-select:none;font-family:Consolas,monospace;');
        if (f[0] === 'all') { btn.style.color = C.redDim; btn.style.borderColor = C.redDim; btn.style.background = 'rgba(170,16,16,0.18)'; }
        btn.addEventListener('click', function() { setFilterActive(f[0]); renderPanel(); });
        filterBar.appendChild(btn);
    });

    // ── STATUS BAR ──
    var sbar = document.createElement('div');
    sbar.id = 'wtt-sbar';
    sbar.setAttribute('style', 'padding:3px 10px;font-size:9px;letter-spacing:.4px;color:rgba(175,175,175,0.5);border-bottom:1px solid rgba(45,8,8,0.4);display:flex;gap:8px;align-items:center;flex-shrink:0;font-family:Consolas,monospace;');
    sbar.textContent = 'Loading…';

    // ── BODY ──
    var body = document.createElement('div');
    body.className = 'wtt-body';

    panelEl.appendChild(hdr);
    panelEl.appendChild(tabs);
    panelEl.appendChild(filterBar);
    panelEl.appendChild(sbar);
    panelEl.appendChild(body);
}

/* ─────────────────────────────────────────
   TOGGLE BUTTON
───────────────────────────────────────── */
function buildToggle() {
    var SVG = '<svg width="28" height="28" viewBox="0 0 44 44" fill="none">' +
        '<circle cx="22" cy="28" r="13.5" stroke="rgba(215,44,44,.88)" stroke-width="1.5" fill="none"/>' +
        '<circle cx="22" cy="28" r="9"    stroke="rgba(215,44,44,.7)"  stroke-width="1.2" fill="none"/>' +
        '<circle cx="22" cy="28" r="4.5"  stroke="rgba(215,44,44,.9)"  stroke-width="1.2" fill="none"/>' +
        '<circle cx="22" cy="28" r="1.9"  fill="rgba(215,55,55,1)"/>' +
        '<line x1="22" y1="13.5" x2="22" y2="18"   stroke="rgba(215,44,44,.72)" stroke-width="1.3" stroke-linecap="round"/>' +
        '<line x1="22" y1="38"   x2="22" y2="42.5" stroke="rgba(215,44,44,.72)" stroke-width="1.3" stroke-linecap="round"/>' +
        '<line x1="7.5" y1="28"  x2="12" y2="28"   stroke="rgba(215,44,44,.72)" stroke-width="1.3" stroke-linecap="round"/>' +
        '<line x1="32"  y1="28"  x2="36.5" y2="28" stroke="rgba(215,44,44,.72)" stroke-width="1.3" stroke-linecap="round"/>' +
        '<circle cx="22" cy="7.5" r="3.4" fill="rgba(195,44,44,.88)"/>' +
        '<path d="M14.8 15.2 Q16.5 11.5 22 11.5 Q27.5 11.5 29.2 15.2" fill="rgba(195,44,44,.88)" stroke="none"/>' +
        '</svg>';

    toggleEl.innerHTML = SVG;
    toggleEl.title = 'Target Tracker Β· Right-click = Settings';
    toggleEl.setAttribute('style',
        'position:fixed !important;' +
        'width:44px !important;height:44px !important;' +
        'background:radial-gradient(circle at 38% 34%,#3d0b0b,#180202) !important;' +
        'border:2px solid rgba(185,28,28,0.75) !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 rgba(180,28,28,0.55),inset 0 0 12px rgba(0,0,0,0.55) !important;' +
        'user-select:none !important;touch-action:none !important;overflow:visible !important;'
    );
    toggleEl.addEventListener('mouseover', function() {
        toggleEl.style.setProperty('box-shadow', '0 0 24px rgba(220,55,55,0.75),inset 0 0 12px rgba(0,0,0,0.55)', 'important');
        toggleEl.style.setProperty('border-color', 'rgba(220,55,55,0.95)', 'important');
    });
    toggleEl.addEventListener('mouseout', function() {
        toggleEl.style.setProperty('box-shadow', '0 0 16px rgba(180,28,28,0.55),inset 0 0 12px rgba(0,0,0,0.55)', 'important');
        toggleEl.style.setProperty('border-color', 'rgba(185,28,28,0.75)', 'important');
    });
}

/* ─────────────────────────────────────────
   PANEL OPEN/CLOSE + POSITION
───────────────────────────────────────── */
function positionPanel() {
    var 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',   '308px', '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() {
    var SZ = 44, EDGE = 6;

    function snap(side, top) {
        toggleEl._side = side;
        var 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 {
        var saved = JSON.parse(store.get('wtt_pos', '{}'));
        snap(saved.side || 'right', saved.top || 340);
    } catch(e) { snap('right', 340); }

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

    function start(cx, cy) {
        moved = false; dragging = true; sX = cx; sY = cy;
        var 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;
        var 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;
        var r    = toggleEl.getBoundingClientRect();
        var side = (r.left + SZ / 2) < window.innerWidth / 2 ? 'left' : 'right';
        snap(side, r.top);
        store.set('wtt_pos', JSON.stringify({ side: side, top: r.top }));
    }

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

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

    /* ── Carousel Tooltip ── */
    (function buildCarousel() {
        var slides = [
            { icon:'🎯', title:'Target Tracker',    body:'Track your faction war roster in real-time β€” statuses, filters, and one-tap attack buttons.' },
            { icon:'βš”',  title:'Attack Button',         body:'Green βš” ATK buttons appear next to Active targets. Tap to go straight to the attack page.' },
            { icon:'πŸ”—', title:'Chain Target List',      body:'Add players by ID to your chain list. Statuses update automatically with your war roster data.' },
            { icon:'πŸ“Ÿ', title:'Dashboard',              body:'Monitor your Health & Energy bars and Quick Actions. Use Escape buttons to fast-travel to Mexico, Japan, or South Africa.' },
            { icon:'πŸ”‘', title:'API Key Security',       body:'Your Full Access API key is masked by default. Tap πŸ‘ in Settings to reveal it. Never share your key with anyone.' },
            { icon:'πŸ”„', title:'Manual Refresh',         body:'Tap the β†Ί icon in the panel header to instantly re-fetch all statuses from the Torn API.' },
            { icon:'πŸ“±', title:'PDA Compatible',         body:'Built for Torn PDA on mobile. All navigation uses native links for full app compatibility.' },
            { icon:'βš™',  title:'First Time Setup',       body:'Open Settings to enter your Full Access API Key, User ID, and Faction ID. Data is stored locally on your device only.' },
        ];
        var cur = 0;
        var tip = document.createElement('div');
        tip.id = 'wtt-carousel';
        tip.setAttribute('style',
            'position:fixed !important;bottom:72px !important;right:12px !important;' +
            'width:230px !important;background:linear-gradient(145deg,rgba(18,4,4,0.97),rgba(10,10,13,0.97)) !important;' +
            'border:1px solid rgba(155,18,18,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:#d4d4d4 !important;' +
            'opacity:0 !important;transition:opacity 0.35s ease !important;'
        );

        var iconEl   = document.createElement('div');
        var titleEl  = document.createElement('div');
        var bodyEl   = document.createElement('div');
        var dotsWrap = document.createElement('div');
        var 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:#ff6060 !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(210,210,210,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) {
            var 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(220,60,60,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 (var 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();
        }

        var prevBtn = document.createElement('div');
        prevBtn.textContent = 'β—€';
        prevBtn.setAttribute('style',
            'cursor:pointer !important;font-size:12px !important;color:rgba(200,80,80,0.8) !important;' +
            'padding:3px 8px !important;user-select:none !important;border-radius:4px !important;' +
            'border:1px solid rgba(180,40,40,0.3) !important;'
        );
        prevBtn.addEventListener('click', function(){ clearInterval(autoTimer); go(cur - 1); });

        var nextBtn = document.createElement('div');
        nextBtn.textContent = 'β–Ά';
        nextBtn.setAttribute('style',
            'cursor:pointer !important;font-size:12px !important;color:rgba(200,80,80,0.8) !important;' +
            'padding:3px 8px !important;user-select:none !important;border-radius:4px !important;' +
            'border:1px solid rgba(180,40,40,0.3) !important;'
        );
        nextBtn.addEventListener('click', function(){ clearInterval(autoTimer); go(cur + 1); });

        var 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(180,80,80,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);

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

        // Only show once β€” dismissed state persisted in localStorage
        var SEEN_KEY = 'wtt_tooltip_seen';
        var 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);

        // Mark seen when user closes OR after last slide auto-advances past end
        function markSeen() {
            try { localStorage.setItem(SEEN_KEY, '1'); } catch(e) {}
        }
        closeBtn.addEventListener('click', markSeen);
        // Also mark seen after they've cycled through all slides once
        var seenCount = 0;
        var origGo = go;
        go = function(n) {
            origGo(n);
            seenCount++;
            if (seenCount >= slides.length) markSeen();
        };
    })();
}

/* ─────────────────────────────────────────
   CHAIN WATCHER ENGINE
   β€’ cwStart()      β€” begin 1s tick + 60s API resync
   β€’ cwStop()       β€” clear both timers
   β€’ cwTick()       β€” runs every second: counts down, fires audio alerts
   β€’ cwResync()     β€” fetches faction/?selections=chain, resets if new hit detected
   β€’ cwPlayBeep()   β€” Web Audio API tone (no files needed)
   β€’ cwUpdateUI()   β€” updates the floating chain watcher card if visible
───────────────────────────────────────── */

// Threshold definitions (seconds -> label)
var CW_THRESHOLDS = [
    { secs: 240, label: '4:00' },
    { secs: 180, label: '3:00' },
    { secs: 120, label: '2:00' },
    { secs:  60, label: '1:00' },
    { secs:  30, label: '0:30' },
    { secs:  10, label: '0:10' }
];

function cwPlayBeep(secsLeft) {
    try {
        var ctx = new (window.AudioContext || window.webkitAudioContext)();
        var osc = ctx.createOscillator();
        var gain = ctx.createGain();
        osc.connect(gain);
        gain.connect(ctx.destination);
        // Pitch gets higher as timer gets lower (more urgent)
        var freq = secsLeft <= 10 ? 880 : secsLeft <= 30 ? 660 : secsLeft <= 60 ? 520 : 440;
        osc.frequency.setValueAtTime(freq, ctx.currentTime);
        osc.type = 'sine';
        gain.gain.setValueAtTime(0.25, ctx.currentTime);
        gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.35);
        osc.start(ctx.currentTime);
        osc.stop(ctx.currentTime + 0.35);
        // Double-beep for <=10s
        if (secsLeft <= 10) {
            var osc2 = ctx.createOscillator();
            var gain2 = ctx.createGain();
            osc2.connect(gain2);
            gain2.connect(ctx.destination);
            osc2.frequency.setValueAtTime(freq, ctx.currentTime + 0.45);
            osc2.type = 'sine';
            gain2.gain.setValueAtTime(0.3, ctx.currentTime + 0.45);
            gain2.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.8);
            osc2.start(ctx.currentTime + 0.45);
            osc2.stop(ctx.currentTime + 0.8);
        }
        setTimeout(function(){ try { ctx.close(); } catch(e){} }, 1000);
    } catch(e) { console.warn('[TargetTracker] Audio failed:', e); }
}

function cwTick() {
    if (!cwEnabled) return;
    if (cwCooldown) {
        // Count down cooldown
        cwSecsLeft = Math.max(0, cwSecsLeft - 1);
        cwUpdateUI();
        return;
    }
    cwSecsLeft = Math.max(0, cwSecsLeft - 1);

    // Check audio thresholds
    var enabled = cfg.getCwThresholds();
    CW_THRESHOLDS.forEach(function(t) {
        if (enabled.indexOf(t.secs) !== -1 && cwSecsLeft === t.secs && !cwAlerted[t.secs]) {
            cwAlerted[t.secs] = true;
            // ToS: only play audio if the user is actively viewing this page
            if (document.visibilityState === 'visible') {
                cwPlayBeep(t.secs);
            }
        }
    });

    if (cwSecsLeft === 0) {
        // Chain expired β€” enter cooldown
        cwCooldown = true;
    }
    cwUpdateUI();
}

async function cwResync() {
    // ToS: only poll while user is actively viewing this page
    if (!cwEnabled || !cfg.apiKey || !cfg.factionId) return;
    if (document.visibilityState !== 'visible') return;
    try {
        var d = await gmFetch('https://api.torn.com/faction/' + cfg.factionId + '?selections=chain&key=' + cfg.apiKey);
        if (!d || d.error || !d.chain) return;
        var chain = d.chain;
        var newCount   = chain.current  || 0;
        var newTimeout = chain.timeout  || 0;
        var newCooldown= chain.cooldown || 0;

        // Detect a new hit: chain count went up β†’ reset 5:00 timer + clear alerts
        if (newCount > cwCount) {
            cwCount     = newCount;
            cwAlerted   = {};    // reset so thresholds fire again on next countdown
            cwCooldown  = false;
            var serverLeft = newTimeout > 0 ? newTimeout - Math.floor(Date.now() / 1000) : 300;
            cwSecsLeft  = Math.max(0, serverLeft);
            // Also update factionChain for the badge
            factionChain = { current: newCount, timeout: newTimeout, cooldown: newCooldown, modifier: chain.modifier || 0 };
            updateChainBadge();
        } else if (newCooldown > 0) {
            cwCooldown = true;
            cwSecsLeft = newCooldown;
        } else if (newTimeout > 0) {
            // Drift correction: sync to server time
            var serverLeft = newTimeout - Math.floor(Date.now() / 1000);
            if (Math.abs(serverLeft - cwSecsLeft) > 5) cwSecsLeft = Math.max(0, serverLeft);
            cwCount = newCount;
        } else {
            // No active chain
            cwCount    = 0;
            cwSecsLeft = 0;
            cwCooldown = false;
        }
        cwUpdateUI();
    } catch(e) { console.warn('[TargetTracker] resync error:', e); }
}

function cwStart() {
    if (cwTickTimer) clearInterval(cwTickTimer);
    if (cwPollTimer) clearInterval(cwPollTimer);
    cwAlerted = {};
    // Initial pull
    cwResync();
    cwTickTimer = setInterval(cwTick, 1000);
    cwPollTimer = setInterval(cwResync, 60000);
    console.log('[TargetTracker] Chain Watcher started');
}

function cwStop() {
    if (cwTickTimer) { clearInterval(cwTickTimer); cwTickTimer = null; }
    if (cwPollTimer) { clearInterval(cwPollTimer); cwPollTimer = null; }
    cwSecsLeft = 0; cwCount = 0; cwCooldown = false; cwAlerted = {};
    cwUpdateUI();
    console.log('[TargetTracker] Chain Watcher stopped');
}

function cwFmtTime(secs) {
    if (secs <= 0) return '0:00';
    var m = Math.floor(secs / 60);
    var s = secs % 60;
    return m + ':' + (s < 10 ? '0' : '') + s;
}

function cwUpdateUI() {
    var card = document.getElementById('wtt-cw-card');
    if (!card) return;

    var timerEl   = document.getElementById('wtt-cw-timer');
    var countEl   = document.getElementById('wtt-cw-count');
    var stateEl   = document.getElementById('wtt-cw-state');
    var barEl     = document.getElementById('wtt-cw-bar');

    if (!timerEl) return;

    if (!cwEnabled || (cwCount === 0 && !cwCooldown)) {
        timerEl.textContent = '--:--';
        timerEl.style.color = 'rgba(150,100,100,0.6)';
        if (countEl) countEl.textContent = 'β€”';
        if (stateEl) stateEl.textContent = cwEnabled ? 'Waiting for chain…' : 'Chain Watcher off';
        if (barEl)   { barEl.style.width = '0%'; barEl.style.background = 'rgba(100,100,100,0.3)'; }
        card.style.borderColor = 'rgba(80,20,20,0.4)';
        card.className = card.className.replace(/\s*wtt-urgent/g,'');
        return;
    }

    if (cwCooldown) {
        timerEl.textContent = cwFmtTime(cwSecsLeft);
        timerEl.style.color = '#78a8ff';
        if (countEl) countEl.textContent = cwCount.toLocaleString();
        if (stateEl) stateEl.textContent = 'COOLDOWN';
        if (barEl)   { barEl.style.width = '100%'; barEl.style.background = '#78a8ff'; }
        card.style.borderColor = 'rgba(60,80,180,0.5)';
        card.className = card.className.replace(/\s*wtt-urgent/g,'');
        return;
    }

    var pct   = Math.min(100, Math.round(cwSecsLeft / 300 * 100));
    // Colour scale: green (>2:00) β†’ orange (2:00–0:30) β†’ red (<0:30)
    var color = cwSecsLeft <= 30 ? '#ff4444'
              : cwSecsLeft <= 120 ? '#f07020'
              : '#55cc55';

    timerEl.textContent = cwFmtTime(cwSecsLeft);
    timerEl.style.color = color;
    if (countEl) countEl.textContent = cwCount.toLocaleString();
    if (stateEl) stateEl.textContent = 'CHAINING';
    if (barEl)   { barEl.style.width = pct + '%'; barEl.style.background = color; }
    card.style.borderColor = cwSecsLeft <= 120 ? color : 'rgba(80,20,20,0.4)';

    if (cwSecsLeft <= 30 && !card.classList.contains('wtt-urgent')) {
        card.classList.add('wtt-urgent');
    } else if (cwSecsLeft > 30) {
        card.className = card.className.replace(/\s*wtt-urgent/g,'');
    }
}

/* ─────────────────────────────────────────
   POLL LOOPS β€” independent timers per tier
───────────────────────────────────────── */
function startPolling() {
    // Self/bars: every 60s β€” 1 call/min
    async function selfLoop() {
        // ToS: skip API calls when page is not actively being viewed
        if (document.visibilityState === 'visible') await refreshSelf();
        selfTimer = setTimeout(selfLoop, 60000);
    }
    // Faction (own + enemy roster): every 45s β€” 2 calls per cycle
    async function factionLoop() {
        if (document.visibilityState === 'visible') await refreshFaction();
        pollTimer = setTimeout(factionLoop, 45000);
    }
    // Chain targets: every 90s β€” N calls (1 per tracked player)
    async function chainLoop() {
        if (document.visibilityState === 'visible') await refreshChain();
        chainTimer = setTimeout(chainLoop, 90000);
    }

    selfTimer  = setTimeout(selfLoop,    60000);
    pollTimer  = setTimeout(factionLoop, 45000);
    chainTimer = setTimeout(chainLoop,   90000);
}

function stopPolling() {
    clearTimeout(selfTimer);
    clearTimeout(pollTimer);
    clearTimeout(chainTimer);
    clearTimeout(warsTimer);
}

/* ─────────────────────────────────────────
   INIT
───────────────────────────────────────── */
function init() {
    if (document.getElementById('wtt-toggle')) return;

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

    toggleEl = document.createElement('div');
    toggleEl.id = 'wtt-toggle';

    panelEl = document.createElement('div');
    panelEl.id = 'wtt-panel';

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

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

    // Always show settings on first load if no API key
    // Request notification permission for hosp-out alerts
    if ('Notification' in window && Notification.permission === 'default') {
        Notification.requestPermission().then(function(p) { notifGranted = p === 'granted'; });
    } else if ('Notification' in window) {
        notifGranted = Notification.permission === 'granted';
    }

    if (!cfg.apiKey) {
        setTimeout(openSettings, 500);
    } else {
        refreshAll().then(function() {
            startPolling();
            cwEnabled = cfg.cwEnabled;
            if (cwEnabled) cwStart();
        });
    }
}

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

})();