☰

🎯 Target Tracker

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

TendrΓ‘s que instalar una extensiΓ³n para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

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

TendrΓ‘s que instalar una extensiΓ³n como Tampermonkey o Violentmonkey para instalar este script.

NecesitarΓ‘s instalar una extensiΓ³n como Tampermonkey o Userscripts para instalar este script.

TendrΓ‘s que instalar una extensiΓ³n como Tampermonkey antes de poder instalar este script.

NecesitarΓ‘s instalar una extensiΓ³n para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, dΓ©jame instalarlo)

TendrΓ‘s que instalar una extensiΓ³n como Stylus antes de poder instalar este script.

TendrΓ‘s que instalar una extensiΓ³n como Stylus antes de poder instalar este script.

TendrΓ‘s que instalar una extensiΓ³n como Stylus antes de poder instalar este script.

Para poder instalar esto tendrΓ‘s que instalar primero una extensiΓ³n de estilos de usuario.

Para poder instalar esto tendrΓ‘s que instalar primero una extensiΓ³n de estilos de usuario.

Para poder instalar esto tendrΓ‘s que instalar primero una extensiΓ³n de estilos de usuario.

(Ya tengo un administrador de estilos de usuario, dΓ©jame instalarlo)

// ==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);
}

})();