Torn — Pickpocket HUD

HUD de pickpocket autónomo — aparece apenas na página de crimes/pickpocketing. Seleção inteligente de alvos, logs em tempo real, nerve display, anti-padrão.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Torn — Pickpocket HUD
// @namespace    https://torn.com/
// @version      1.0
// @description  HUD de pickpocket autónomo — aparece apenas na página de crimes/pickpocketing. Seleção inteligente de alvos, logs em tempo real, nerve display, anti-padrão.
// @author       TeuNome
// @match        https://www.torn.com/*
// @icon         https://www.torn.com/favicon.ico
// @grant        none
// @license MIT
// ==/UserScript==

(function () {
    'use strict';

    /* ══════════════════════════════════════════════════════
       SÓ ACTIVA NA PÁGINA DE PICKPOCKET
    ══════════════════════════════════════════════════════ */
    function isPickpocketPage() {
        return location.href.includes("sid=crimes") && location.hash.includes("pickpocketing");
    }
    function isCrimesPage() {
        return location.href.includes("sid=crimes");
    }

    // Aguarda hash change caso o utilizador navegue directamente para crimes
    if (!isPickpocketPage() && !isCrimesPage()) return;

    /* ══════════════════════════════════════════════════════
       CONFIGURAÇÃO
    ══════════════════════════════════════════════════════ */
    const API_KEY            = "merda"; // ← substitui pelo teu
    const PICKPOCKET_URL     = "https://www.torn.com/page.php?sid=crimes#/pickpocketing";
    const WAIT_POLL_MS       = 3000;
    const SCAN_LOG_INTERVAL  = 2;
    const REFRESH_MIN_SEC    = 3 * 60;
    const REFRESH_MAX_SEC    = 5 * 60;
    const STATE_TTL_MS       = 30000;

    /* ── localStorage keys ── */
    const LOGS_KEY          = "tph_logs";
    const STATS_KEY         = "tph_stats";
    const DROP_STATS_KEY    = "tph_dropStats";
    const PAUSED_KEY        = "tph_paused";
    const SESSION_MODE_KEY  = "tph_sessionMode";
    const NERVE_COUNTER_KEY = "tph_nerveCounter";
    const TARGET_FILTERS_KEY= "tph_targetFilters";
    const VICTIM_CFG_KEY    = "tph_victimCfg";
    const SCORE_MODE_KEY    = "tph_scoreMode";

    /* ══════════════════════════════════════════════════════
       BASE DE DADOS DE ALVOS
    ══════════════════════════════════════════════════════ */
const VICTIM_DB = [
    {
        id: "drunk_woman",
        label: "Drunk Woman",
        emoji: "🍹",
        priority: 1,
        difficulty: 1,
        cashMin: 274,
        cashMax: 3380,
        timerSec: 42,
        statuses: ["stumbling", "distracted"],
        goodStatus: ["stumbling", "distracted"],
        requireBuild: "heavyset",
        requireRazor: true,
        unique: "2x Bottle of Pumpkin Brew",
        uniqueVal: 800,
        riskHosp: true
    },
    {
        id: "drunk_man",
        label: "Drunk Man",
        emoji: "🍺",
        priority: 1,
        difficulty: 1,
        cashMin: 216,
        cashMax: 3769,
        timerSec: 42,
        statuses: ["stumbling", "distracted"],
        goodStatus: ["stumbling", "distracted"],
        requireRazor: true,
        unique: "6x Bottle of Beer",
        uniqueVal: 900
    },
    {
        id: "businessman",
        label: "Businessman",
        emoji: "💼",
        priority: 2,
        difficulty: 2,
        cashMin: 1380,
        cashMax: 9600,
        timerSec: 25,
        statuses: ["walking", "on phone"],
        goodStatus: ["on phone"],
        unique: "2x Speed",
        uniqueVal: 1200,
        riskJail: true
    },
    {
        id: "businesswoman",
        label: "Businesswoman",
        emoji: "👠",
        priority: 2,
        difficulty: 2,
        cashMin: 1100,
        cashMax: 6000,
        timerSec: 28,
        statuses: ["walking", "on phone"],
        goodStatus: ["on phone"],
        unique: "Glasses",
        uniqueVal: 500,
        riskJail: true
    },
    {
        id: "classy_lady",
        label: "Classy Lady",
        emoji: "💎",
        priority: 2,
        difficulty: 3,
        cashMin: 1020,
        cashMax: 79400,
        timerSec: 25,
        statuses: ["walking", "on phone"],
        goodStatus: ["on phone"],
        unique: "High Heels",
        uniqueVal: 2000,
        riskJail: true
    },
    {
        id: "cyclist",
        label: "Cyclist",
        emoji: "🚴",
        priority: 2,
        difficulty: 3,
        cashMin: 720,
        cashMax: 10400,
        timerSec: 10,
        statuses: ["cycling"],
        goodStatus: [],
        unique: "Mountain Bike",
        uniqueVal: 5000,
        riskHosp: true,
        riskJail: true
    },
    {
        id: "elderly_man",
        label: "Elderly Man",
        emoji: "👴",
        priority: 1,
        difficulty: 1,
        cashMin: 460,
        cashMax: 5000,
        timerSec: 55,
        statuses: ["walking"],
        goodStatus: [],
        unique: "Bag of Tootsie Rolls",
        uniqueVal: 300
    },
    {
        id: "elderly_woman",
        label: "Elderly Woman",
        emoji: "👵",
        priority: 1,
        difficulty: 1,
        cashMin: 460,
        cashMax: 4900,
        timerSec: 55,
        statuses: ["walking"],
        goodStatus: [],
        unique: "Opium",
        uniqueVal: 800,
        riskJail: true
    },
    {
        id: "gang_member",
        label: "Gang Member",
        emoji: "🔫",
        priority: 2,
        difficulty: 3,
        cashMin: 1660,
        cashMax: 7000,
        timerSec: 70,
        statuses: ["loitering"],
        goodStatus: ["loitering"],
        requireRazor: true,
        unique: "Spray Cans / Wire Cutters",
        uniqueVal: 1500,
        riskJail: true
    },
    {
        id: "homeless",
        label: "Homeless Person",
        emoji: "🧍",
        priority: 1,
        difficulty: 1,
        cashMin: 70,
        cashMax: 2921,
        timerSec: 120,
        statuses: ["loitering"],
        goodStatus: ["loitering"],
        unique: "4x Morphine",
        uniqueVal: 1000,
        riskJail: true
    },
    {
        id: "jogger",
        label: "Jogger",
        emoji: "🏃",
        priority: 2,
        difficulty: 2,
        cashMin: 560,
        cashMax: 4550,
        timerSec: 20,
        statuses: ["walking", "jogging"],
        goodStatus: ["walking"],
        requireRazor: true,
        unique: "Crocozade",
        uniqueVal: 400,
        riskJail: true
    },
    {
        id: "junkie",
        label: "Junkie",
        emoji: "💉",
        priority: 1,
        difficulty: 1,
        cashMin: 202,
        cashMax: 4800,
        timerSec: 60,
        statuses: ["stumbling", "loitering"],
        goodStatus: ["stumbling"],
        unique: "Drug bundle",
        uniqueVal: 1500,
        riskJail: true
    },
    {
        id: "laborer",
        label: "Laborer",
        emoji: "🪓",
        priority: 2,
        difficulty: 2,
        cashMin: 560,
        cashMax: 4700,
        timerSec: 40,
        statuses: ["walking", "distracted", "on phone"],
        goodStatus: ["distracted", "on phone"],
        unique: "Megaphone",
        uniqueVal: 700,
        riskJail: true
    },
    {
        id: "mobster",
        label: "Mobster",
        emoji: "🕴️",
        priority: 3,
        difficulty: 4,
        cashMin: 10000,
        cashMax: 700000,
        timerSec: 30,
        statuses: ["walking"],
        goodStatus: [],
        requireRazor: true,
        unique: "Huge Cash Drop",
        uniqueVal: 600000,
        riskJound: true
    },
    {
        id: "police",
        label: "Police Officer",
        emoji: "👮",
        priority: 3,
        difficulty: 4,
        cashMin: 0,
        cashMax: 2000,
        timerSec: 25,
        statuses: ["walking", "running"],
        goodStatus: ["walking"],
        unique: "Police Badge",
        uniqueVal: 2000,
        riskJail: true
    },
    {
        id: "postal_worker",
        label: "Postal Worker",
        emoji: "📦",
        priority: 2,
        difficulty: 2,
        cashMin: 746,
        cashMax: 5792,
        timerSec: 25,
        statuses: ["walking", "distracted"],
        goodStatus: ["distracted"],
        unique: "Lottery Voucher",
        uniqueVal: 1200,
        riskJail: true
    },
    {
        id: "rich_kid",
        label: "Rich Kid",
        emoji: "🧑‍🎧",
        priority: 2,
        difficulty: 3,
        cashMin: 1200,
        cashMax: 8000,
        timerSec: 32,
        statuses: ["walking", "on phone", "listening to music"],
        goodStatus: ["listening to music"],
        requireRazor: true,
        unique: "Six Pack Alcohol",
        uniqueVal: 900,
        riskJail: true
    },
    {
        id: "sex_worker",
        label: "Sex Worker",
        emoji: "💋",
        priority: 2,
        difficulty: 3,
        cashMin: 530,
        cashMax: 5600,
        timerSec: 80,
        statuses: ["distracted", "on phone", "soliciting"],
        goodStatus: ["soliciting"],
        unique: "Thong",
        uniqueVal: 400,
        riskJail: true
    },
    {
        id: "student",
        label: "Student",
        emoji: "🎓",
        priority: 2,
        difficulty: 2,
        cashMin: 312,
        cashMax: 3631,
        timerSec: 25,
        statuses: ["walking", "running", "on phone", "listening to music"],
        goodStatus: ["on phone", "listening to music"],
        requireRazor: true,
        unique: "Tech bundle",
        uniqueVal: 1500,
        riskJail: true
    },
    {
        id: "thug",
        label: "Thug",
        emoji: "👊",
        priority: 2,
        difficulty: 2,
        cashMin: 800,
        cashMax: 6000,
        timerSec: 20,
        statuses: ["walking", "running"],
        goodStatus: ["walking"],
        unique: "Weapon bundle",
        uniqueVal: 1200,
        riskJail: true
    },
    {
        id: "young_man",
        label: "Young Man",
        emoji: "🧑",
        priority: 1,
        difficulty: 1,
        cashMin: 550,
        cashMax: 3970,
        timerSec: 25,
        statuses: ["walking", "on phone", "listening to music"],
        goodStatus: ["on phone", "listening to music"],
        unique: "Ketamine",
        uniqueVal: 600,
        riskJail: true
    },
    {
        id: "young_woman",
        label: "Young Woman",
        emoji: "👩",
        priority: 1,
        difficulty: 1,
        cashMin: 830,
        cashMax: 4960,
        timerSec: 25,
        statuses: ["walking", "on phone", "listening to music", "distracted"],
        goodStatus: ["distracted", "on phone"],
        unique: "Ecstasy",
        uniqueVal: 700,
        riskJail: true
    }
];

    const ALL_STATUSES = [
        { id: "stumbling",           label: "Stumbling",           emoji: "🥴", mult: 1.5 },
        { id: "distracted",          label: "Distracted",          emoji: "😵", mult: 1.3 },
        { id: "on phone",            label: "On Phone",            emoji: "📱", mult: 1.2 },
        { id: "listening to music",  label: "Listening to Music",  emoji: "🎧", mult: 1.2 },
        { id: "talking",             label: "Talking",             emoji: "💬", mult: 1.2 },
        { id: "loitering",           label: "Loitering",           emoji: "🧍", mult: 1.2 },
        { id: "soliciting",          label: "Soliciting",          emoji: "🤑", mult: 1.3 },
        { id: "walking",             label: "Walking",             emoji: "🚶", mult: 1.0 },
        { id: "jogging",             label: "Jogging",             emoji: "🏃", mult: 0.7 },
        { id: "running",             label: "Running",             emoji: "💨", mult: 0.4 },
        { id: "cycling",             label: "Cycling",             emoji: "🚴", mult: 0.5 },
        { id: "patrolling",          label: "Patrolling",          emoji: "👮", mult: 0.9 },
    ];

    const ALL_BUILDS = [
        { id: "skinny",     label: "Skinny",     emoji: "🦴" },
        { id: "average",    label: "Average",    emoji: "🧍" },
        { id: "heavyset",   label: "Heavyset",   emoji: "🪨" },
        { id: "muscular",   label: "Muscular",   emoji: "💪" },
        { id: "overweight", label: "Overweight", emoji: "🫃" },
    ];

    const VICTIM_DROPS = {
    drunk_woman: [
        { id: "candy_kisses", name: "Bag of Candy Kisses", emoji: "🍬", heavysetOnly: true },
        { id: "tootsie_rolls", name: "Bag of Tootsie Rolls", emoji: "🍫", heavysetOnly: true },
        { id: "lollipop", name: "Lollipop", emoji: "🍭", heavysetOnly: true },
        { id: "pumpkin_brew", name: "Bottle of Pumpkin Brew", emoji: "🎃", unique: true },
        { id: "cash", name: "Cash", emoji: "💵" },
    ],

    drunk_man: [
        { id: "beer", name: "Bottle of Beer", emoji: "🍺" },
        { id: "cash", name: "Cash", emoji: "💵" },
    ],

    businessman: [
        { id: "speed", name: "Speed", emoji: "💊" },
        { id: "cash", name: "Cash", emoji: "💵" },
    ],

    businesswoman: [
        { id: "glasses", name: "Glasses", emoji: "👓" },
        { id: "cash", name: "Cash", emoji: "💵" },
    ],

    classy_lady: [
        { id: "heels", name: "High Heels", emoji: "👠" },
        { id: "cash", name: "Cash", emoji: "💵" },
    ],

    cyclist: [
        { id: "bike", name: "Mountain Bike", emoji: "🚴" },
        { id: "cash", name: "Cash", emoji: "💵" },
    ],

    homeless: [
        { id: "morphine", name: "Morphine", emoji: "💉" },
        { id: "cash", name: "Cash", emoji: "💵" },
    ],

    student: [
        { id: "tech", name: "Tech bundle", emoji: "📱" },
        { id: "cash", name: "Cash", emoji: "💵" },
    ],
};

    const STATUS_MULT = {
        "stumbling": 1.5, "distracted": 1.3, "soliciting": 1.3,
        "loitering": 1.2, "on phone": 1.2, "listening to music": 1.2, "talking": 1.2,
        "walking": 1.0, "patrolling": 0.9, "jogging": 0.7, "cycling": 0.5, "running": 0.4,
    };

    /* ══════════════════════════════════════════════════════
       HELPERS
    ══════════════════════════════════════════════════════ */
    function randBetween(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; }
    function formatTime(sec) {
        sec = Math.max(0, Math.round(sec));
        const h = Math.floor(sec / 3600), m = Math.floor((sec % 3600) / 60), s = sec % 60;
        return h > 0 ? `${h}:${pad(m)}:${pad(s)}` : `${m}:${pad(s)}`;
    }
    function pad(n) { return String(n).padStart(2, "0"); }
    function delay(ms) { return new Promise(r => setTimeout(r, ms)); }
    function waitFor(fn, maxMs = 12000) {
        return new Promise((res, rej) => {
            const t0 = Date.now();
            const iv = setInterval(() => {
                const el = fn();
                if (el) { clearInterval(iv); res(el); }
                else if (Date.now() - t0 >= maxMs) { clearInterval(iv); rej(`Timeout ${maxMs}ms`); }
            }, 300);
        });
    }

    /* ══════════════════════════════════════════════════════
       ESTADO: PAUSA, MODO SESSÃO, MODO SCORE
    ══════════════════════════════════════════════════════ */
    function isPaused()         { return localStorage.getItem(PAUSED_KEY) === "1"; }
    function setPaused(v)       { localStorage.setItem(PAUSED_KEY, v ? "1" : "0"); }
    function getSessionMode()   { return localStorage.getItem(SESSION_MODE_KEY) || "wait"; }
    function setSessionMode(m)  { localStorage.setItem(SESSION_MODE_KEY, m); }
    function getScoreMode()     { return localStorage.getItem(SCORE_MODE_KEY) || "auto"; }
    function setScoreMode(m)    { localStorage.setItem(SCORE_MODE_KEY, m); }

    /* ══════════════════════════════════════════════════════
       FILTROS DE ALVO (status + build)
    ══════════════════════════════════════════════════════ */
    function buildDefaultFilters() {
        const filters = {};
        VICTIM_DB.forEach(v => {
            const statuses = {};
            ALL_STATUSES.forEach(s => {
                statuses[s.id] = v.statuses.includes(s.id) && (v.goodStatus.length === 0 || v.goodStatus.includes(s.id));
            });
            const builds = {};
            ALL_BUILDS.forEach(b => { builds[b.id] = v.requireBuild ? b.id === v.requireBuild : true; });
            filters[v.id] = { statuses, builds };
        });
        return filters;
    }
    function getTargetFilters() {
        try {
            const s = JSON.parse(localStorage.getItem(TARGET_FILTERS_KEY));
            if (s && typeof s === "object") return s;
        } catch {}
        return buildDefaultFilters();
    }
    function saveTargetFilters(f) { localStorage.setItem(TARGET_FILTERS_KEY, JSON.stringify(f)); }
    function isStatusAllowed(victimId, status) {
        return getTargetFilters()[victimId]?.statuses?.[status.toLowerCase()] === true;
    }
    function isBuildAllowed(victimId, build) {
        return getTargetFilters()[victimId]?.builds?.[build.toLowerCase()] === true;
    }
    function toggleStatusFilter(victimId, statusId) {
        const f = getTargetFilters();
        if (!f[victimId]) f[victimId] = buildDefaultFilters()[victimId];
        f[victimId].statuses[statusId] = !f[victimId].statuses[statusId];
        saveTargetFilters(f);
    }
    function toggleBuildFilter(victimId, buildId) {
        const f = getTargetFilters();
        if (!f[victimId]) f[victimId] = buildDefaultFilters()[victimId];
        f[victimId].builds[buildId] = !f[victimId].builds[buildId];
        saveTargetFilters(f);
    }

    /* ══════════════════════════════════════════════════════
       LOGS
    ══════════════════════════════════════════════════════ */
    const CAT_CLR = {
        crime: "#5c2db0", scan: "#1a4a3a", drop: "#1a5a2a",
        system: "#2a2a4a", nerve: "#3a1a6a", warn: "#6a3a00"
    };
    function getLogs() { try { return JSON.parse(localStorage.getItem(LOGS_KEY)) || []; } catch { return []; } }
    function addLog(category, msg) {
        const logs = getLogs();
        const ts   = new Date().toLocaleTimeString("pt-PT", { hour:"2-digit", minute:"2-digit", second:"2-digit" });
        logs.unshift({ ts, category, msg });
        if (logs.length > 300) logs.length = 300;
        localStorage.setItem(LOGS_KEY, JSON.stringify(logs));
        renderLogs();
    }
    function clearLogs() { localStorage.removeItem(LOGS_KEY); renderLogs(); }

    /* ══════════════════════════════════════════════════════
       ESTATÍSTICAS
    ══════════════════════════════════════════════════════ */
    function getStats() {
        try { return JSON.parse(localStorage.getItem(STATS_KEY)) || defaultStats(); } catch { return defaultStats(); }
    }
    function defaultStats() {
        return { pickpockets: { count: 0, nerveSpent: 0, successCount: 0, byVictim: {} } };
    }
    function saveStats(s) { localStorage.setItem(STATS_KEY, JSON.stringify(s)); }
    function recordPickpocket(victimId, nerveSpent, success) {
        const s = getStats();
        s.pickpockets.count++;
        s.pickpockets.nerveSpent += nerveSpent;
        if (success) s.pickpockets.successCount++;
        if (!s.pickpockets.byVictim[victimId]) s.pickpockets.byVictim[victimId] = { count: 0, success: 0 };
        s.pickpockets.byVictim[victimId].count++;
        if (success) s.pickpockets.byVictim[victimId].success++;
        saveStats(s);
        if (s.pickpockets.count % 5 === 0) {
            addLog("crime", `🦹 #${s.pickpockets.count} crimes · ${s.pickpockets.nerveSpent} nerve gasto`);
        }
        updateStatsDisplay();
    }

    /* ══════════════════════════════════════════════════════
       DROPS
    ══════════════════════════════════════════════════════ */
    function getDropStats()    { try { return JSON.parse(localStorage.getItem(DROP_STATS_KEY)) || {}; } catch { return {}; } }
    function saveDropStats(d)  { localStorage.setItem(DROP_STATS_KEY, JSON.stringify(d)); }
    function recordDrop(victimId, dropId) {
    const d = getDropStats();

    if (!d[victimId]) d[victimId] = {};
    d[victimId][dropId] = (d[victimId][dropId] || 0) + 1;

    saveDropStats(d);

    const drop = VICTIM_DROPS[victimId]?.find(x => x.id === dropId);

    if (drop) {
        addLog("drop", `${drop.emoji} ${victimId} → ${drop.name} (x${d[victimId][dropId]})`);
    }
}
function parseOutcomeForDrop(modalEl, victimId) {
    if (!modalEl || !victimId) return null;

    const text = modalEl.textContent.toLowerCase();
    const drops = VICTIM_DROPS[victimId] || [];

    for (const drop of drops) {
        if (text.includes(drop.name.toLowerCase())) {
            return drop.id;
        }
    }

    if (/\$[\d,]+/.test(modalEl.textContent) && !text.includes("failed")) {
        return "cash";
    }

    return null;
}

    /* ══════════════════════════════════════════════════════
       NERVE — LEITURA DO DOM + API
    ══════════════════════════════════════════════════════ */
    let currentNerve    = 0;
    let currentNerveMax = 30;
    let apiLoaded       = false;

    function readNerveFromDOM() {
        const stats = document.querySelectorAll('[class*="bar-stats"]');
        for (const stat of stats) {
            const nameEl = stat.querySelector('[class*="bar-name"]');
            const valEl  = stat.querySelector('[class*="bar-value"]');
            if (nameEl && valEl && nameEl.textContent.trim().toLowerCase().startsWith("nerve")) {
                const m = valEl.textContent.trim().match(/(\d+)\/(\d+)/);
                if (m) { currentNerve = parseInt(m[1], 10); currentNerveMax = parseInt(m[2], 10); return true; }
            }
        }
        const names = document.querySelectorAll('[class*="bar-name"]');
        for (const nameEl of names) {
            if (nameEl.textContent.trim().toLowerCase().startsWith("nerve")) {
                const parent = nameEl.parentElement;
                const valEl  = parent && parent.querySelector('[class*="bar-value"]');
                if (valEl) {
                    const m = valEl.textContent.trim().match(/(\d+)\/(\d+)/);
                    if (m) { currentNerve = parseInt(m[1], 10); currentNerveMax = parseInt(m[2], 10); return true; }
                }
            }
        }
        return false;
    }

    async function fetchNerveFromAPI() {
        try {
            const res  = await fetch(`https://api.torn.com/user/?selections=bars&key=${API_KEY}`);
            const data = await res.json();
            if (data?.nerve) {
                currentNerve    = data.nerve.current;
                currentNerveMax = data.nerve.maximum;
                apiLoaded = true;
            }
        } catch (e) { console.error("[TPH] API:", e); }
    }

    /* ══════════════════════════════════════════════════════
       PARSE DE CARDS DE VÍTIMAS
    ══════════════════════════════════════════════════════ */
    function parseVictimCards() {
        const wrappers = document.querySelectorAll('[class*="crimeOptionWrapper"]');
        const parsed   = [];
        for (const wrap of wrappers) {
            const titleDiv = wrap.querySelector('[class*="titleAndProps"] [style*="color"]');
            const rawTitle = (titleDiv ? titleDiv.textContent.trim() : "").replace(/\s*\(.*?\)\s*$/, "").trim();

            const activityEl = wrap.querySelector('[class*="activity___"]');
            let rawStatus = "";
            if (activityEl) {
                for (const node of activityEl.childNodes) {
                    if (node.nodeType === Node.TEXT_NODE) { const t = node.textContent.trim(); if (t) { rawStatus = t; break; } }
                }
            }

            const buildEl  = wrap.querySelector('[class*="physicalProps___"] [aria-hidden="true"]');
            const rawBuild = buildEl ? buildEl.textContent.trim() : "";
            const clockEl  = wrap.querySelector('[class*="clock___"]');
            const clockSec = clockEl ? (parseInt(clockEl.textContent) || 99) : 99;
            const btn      = wrap.querySelector('button[class*="commitButton"]');
            if (!btn) continue;

            const matchedVictim =
                VICTIM_DB.find(v => rawTitle.toLowerCase() === v.label.toLowerCase()) ||
                VICTIM_DB.find(v => rawTitle.toLowerCase().includes(v.label.toLowerCase()) || v.label.toLowerCase().includes(rawTitle.toLowerCase()));

            parsed.push({ btn, rawTitle, rawStatus: rawStatus.toLowerCase(), rawBuild, clockSec, victimId: matchedVictim?.id || null, victim: matchedVictim || null });
        }
        return parsed;
    }

    function cardMeetsRequirements(card) {
        const v = card.victim;
        if (!v) return false;
        if (!isStatusAllowed(v.id, card.rawStatus)) return false;
        const buildWord = card.rawBuild.split(/\s+/)[0].toLowerCase();
        if (buildWord && !isBuildAllowed(v.id, buildWord)) return false;
        return true;
    }

    /* ══════════════════════════════════════════════════════
       SCAN LOG DE VÍTIMAS VISÍVEIS
    ══════════════════════════════════════════════════════ */
    function logVisibleVictims(cards) {
        if (!cards.length) { addLog("scan", "👁 Nenhum alvo visível no ecrã"); return; }
        cards.forEach(c => {
            const meetsReq = c.victim ? cardMeetsRequirements(c) : false;
            const icon     = c.victim ? c.victim.emoji : "❓";
            let reason;
            if (!c.victim)       reason = "⬜ fora da lista";
            else if (!meetsReq) {
                const needStatus = !isStatusAllowed(c.victimId, c.rawStatus);
                const buildWord  = c.rawBuild.split(/\s+/)[0].toLowerCase();
                const needBuild  = buildWord && !isBuildAllowed(c.victimId, buildWord);
                reason = needStatus ? `⏳ status "${c.rawStatus}" não permitido`
                       : needBuild  ? `⏳ build "${buildWord}" não permitido`
                       : `⏳ req. não cumprido`;
            } else reason = "✅ CANDIDATO";
            const mult     = STATUS_MULT[c.rawStatus] || 1.0;
            const multClr  = mult >= 1.2 ? "#00e676" : mult >= 1.0 ? "#ffd966" : "#ff6666";
            addLog("scan", `${icon} ${c.rawTitle} · <span style="color:${multClr};">${c.rawStatus || "?"}</span> · ${c.rawBuild.split(" ")[0] || "?"} · ⏱${c.clockSec}s → ${reason}`);
        });
    }

    /* ══════════════════════════════════════════════════════
       SELECÇÃO DE MELHOR ALVO
    ══════════════════════════════════════════════════════ */
    let nervePickCounter = parseInt(localStorage.getItem(NERVE_COUNTER_KEY) || "0", 10);
    function incNerveCounter()   { nervePickCounter++; localStorage.setItem(NERVE_COUNTER_KEY, nervePickCounter); return nervePickCounter; }
    function resetNerveCounter() { nervePickCounter = 0; localStorage.setItem(NERVE_COUNTER_KEY, "0"); }

    function selectBestVictim(doLogScan = false) {
        const cards = parseVictimCards();
        if (doLogScan) logVisibleVictims(cards);
        if (!cards.length) return null;

        // A cada 4 crimes → crime aleatório anti-padrão
        if (nervePickCounter > 0 && nervePickCounter % 4 === 0) {
            const pool = cards.filter(c => c.victim);
            if (!pool.length) return null;
            const pick = pool[Math.floor(Math.random() * pool.length)];
            addLog("crime", `🎲 Aleatório (turno ${nervePickCounter}) → ${pick.rawTitle} · ${pick.rawStatus}`);
            return pick;
        }

        // Selecção por prioridade + filtros
        const sorted = VICTIM_DB.slice().sort((a, b) => (a.priority || 99) - (b.priority || 99));
        for (const dbEntry of sorted) {
            const candidates = cards.filter(c => c.victimId === dbEntry.id && cardMeetsRequirements(c));
            if (candidates.length) {
                candidates.sort((a, b) => b.clockSec - a.clockSec);
                const pick = candidates[0];
                addLog("crime", `🎯 Atacar: ${pick.rawTitle} · ${pick.rawStatus} · ⏱${pick.clockSec}s`);
                return pick;
            }
        }
        return null;
    }

    /* ══════════════════════════════════════════════════════
       FECHAR MODAL DE RESULTADO + DETECTAR DROP
    ══════════════════════════════════════════════════════ */
    async function closeOutcomeModal(victimId) {
        const modalSel = '[class*="outcomeWrapper___"]';
        const t0 = Date.now();
        let modal = null;
        while (Date.now() - t0 < 4000) {
            const m = document.querySelector(modalSel);
            if (m && m.children.length > 0) { modal = m; break; }
            await delay(200);
        }
        await delay(300);
        if (modal) {
            const dropId = parseOutcomeForDrop(modal, victimId);
            if (dropId) recordDrop(victimId, dropId);
        }
        const closeSelectors = [
            '[class*="outcomeWrapper"] button', '[class*="outcome"] button',
            '[class*="modal"] button[class*="close"]', '[class*="closeButton"]',
            '[aria-label="Close"]', '[class*="continue"]',
        ];
        for (const sel of closeSelectors) {
            const btn = document.querySelector(sel);
            if (btn) { btn.click(); await delay(300); return; }
        }
        document.dispatchEvent(new KeyboardEvent("keydown", { key: "Escape", keyCode: 27, bubbles: true }));
        await delay(300);
        const overlay = document.querySelector('[class*="overlay"], [class*="Overlay"]');
        if (overlay) overlay.click();
        await delay(300);
    }

    /* ══════════════════════════════════════════════════════
       LOOP DE PICKPOCKET
    ══════════════════════════════════════════════════════ */
    let pickpocketRunning = false;

    async function runPickpocketLoop() {
        if (pickpocketRunning) return;
        if (!isPickpocketPage()) return;
        pickpocketRunning = true;

        const refreshAt    = Date.now() + randBetween(REFRESH_MIN_SEC, REFRESH_MAX_SEC) * 1000;
        const sessionStart = Date.now();
        let sessionCrimes  = 0;
        let scanIter       = 0;
        let consecutiveErr = 0;

        addLog("crime", `▶ Sessão iniciada — refresh em ~${Math.round((refreshAt - Date.now()) / 60000)}m · modo: ${getSessionMode()}`);
        updateStatusBadge("▶ A correr", "#00e676");

        try {
            await delay(2000);

            while (true) {
                /* ── Pausa ── */
                if (isPaused()) { updateStatusBadge("⏸ Pausa", "#ffd966"); await delay(1000); continue; }

                /* ── Refresh periódico ── */
                if (Date.now() >= refreshAt) {
                    const elapsed = Math.round((Date.now() - sessionStart) / 1000);
                    addLog("crime", `🔄 Refresh após ${formatTime(elapsed)} (${sessionCrimes} crimes) — a recarregar`);
                    pickpocketRunning = false;
                    location.href = PICKPOCKET_URL;
                    return;
                }

                /* ── Lê nerve ── */
                readNerveFromDOM();
                updateNerveDisplay();

                const hasNerve = currentNerve >= 5;

                /* ── Sem nerve ── */
                if (!hasNerve) {
                    if (getSessionMode() === "leave") {
                        addLog("crime", `⚡ Nerve ${currentNerve}/5 — modo "sair" — sessão terminada`);
                        updateStatusBadge("💤 Nerve baixo", "#ff6666");
                        break;
                    }
                    updateStatusBadge(`⏳ Nerve ${currentNerve}/${currentNerveMax}`, "#ffd966");
                    if (scanIter % SCAN_LOG_INTERVAL === 0) {
                        const cards = parseVictimCards();
                        logVisibleVictims(cards);
                    }
                    scanIter++;
                    await delay(WAIT_POLL_MS);
                    continue;
                }

                /* ── Selecciona alvo ── */
                const doLog = (scanIter % SCAN_LOG_INTERVAL === 0);
                scanIter++;
                const pick = selectBestVictim(doLog);

                if (!pick) {
                    if (doLog) addLog("scan", `⏳ Sem alvos prioritários (nerve: ${currentNerve})`);
                    updateStatusBadge("🔍 A procurar alvo…", "#6c5ce7");
                    await delay(WAIT_POLL_MS);
                    continue;
                }

                /* ── Timer mínimo ── */
                if (pick.clockSec < 3) {
                    addLog("scan", `⚠️ ${pick.rawTitle} a desaparecer (${pick.clockSec}s) — saltar`);
                    await delay(500);
                    continue;
                }

                /* ── Executa crime ── */
                updateStatusBadge(`🦹 ${pick.rawTitle}`, "#5c2db0");
                try {
                    incNerveCounter();
                    pick.btn.click();
                    sessionCrimes++;
                    consecutiveErr = 0;
                    recordPickpocket(pick.victimId || "unknown", 5, true);

                    await closeOutcomeModal(pick.victimId);

                    let waited = 0;
                    while (waited < 6000) {
                        await delay(500); waited += 500;
                        if (document.querySelector('button[class*="commitButton"]')) break;
                    }
                } catch (crimeErr) {
                    consecutiveErr++;
                    addLog("warn", `⚠️ Erro no crime #${sessionCrimes}: ${crimeErr}`);
                    if (consecutiveErr >= 5) {
                        addLog("warn", "❌ 5 erros consecutivos — a recarregar");
                        pickpocketRunning = false;
                        location.href = PICKPOCKET_URL;
                        return;
                    }
                    await delay(3000);
                }
            }
        } catch (err) {
            addLog("warn", `❌ Erro fatal: ${err}`);
        }

        addLog("crime", `✅ Sessão terminada: ${sessionCrimes} crimes · ${sessionCrimes * 5} nerve gasto`);
        updateStatusBadge("⏹ Parado", "#3a3a6a");
        pickpocketRunning = false;
        resetNerveCounter();
    }

    /* ══════════════════════════════════════════════════════
       CONSTRUÇÃO DO HUD
    ══════════════════════════════════════════════════════ */
    const box = document.createElement("div");
    box.id = "tph-box";
    box.style.cssText = [
        "position:fixed;top:0;right:0;bottom:0;width:360px",
        "background:rgba(6,6,12,0.98);color:#d0d0e8",
        "z-index:999999;font-family:'Segoe UI',Arial,sans-serif",
        "box-shadow:-6px 0 40px rgba(0,0,0,0.95)",
        "display:flex;flex-direction:column;overflow:hidden",
        "border-left:1px solid #1a1a2e;user-select:none",
        "transition:transform .3s ease",
    ].join(";");

    box.innerHTML = `
<!-- ── HEADER ── -->
<div style="background:#08081a;padding:8px 12px;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid #1a1a2e;flex-shrink:0;">
    <div>
        <div style="font-size:11px;font-weight:800;letter-spacing:2.5px;color:#6c5ce7;">🦹 PICKPOCKET HUD</div>
        <div id="tph-status-badge" style="font-size:10px;color:#3a3a6a;margin-top:2px;">iniciando…</div>
    </div>
    <div style="display:flex;gap:5px;align-items:center;">
        <button id="tph-pause" style="background:#3a1a00;border:1px solid #7a3a00;color:#ffd966;padding:3px 10px;border-radius:4px;cursor:pointer;font-size:10px;font-weight:800;">⏸</button>
        <button id="tph-start" style="background:#0e3a0e;border:1px solid #1a7a1a;color:#00e676;padding:3px 10px;border-radius:4px;cursor:pointer;font-size:10px;font-weight:800;">▶ START</button>
        <button id="tph-collapse" style="background:none;border:1px solid #2a2a3e;color:#444;padding:2px 8px;border-radius:4px;cursor:pointer;font-size:10px;">◀</button>
    </div>
</div>

<!-- ── PAUSA BANNER ── -->
<div id="tph-pause-banner" style="display:none;background:#2a1000;border-bottom:2px solid #ffd966;padding:5px 12px;text-align:center;font-size:10px;font-weight:800;color:#ffd966;flex-shrink:0;">
    ⏸ EM PAUSA — podes navegar livremente
</div>

<!-- ── TABS ── -->
<div style="display:flex;background:#08081a;border-bottom:1px solid #1a1a2e;flex-shrink:0;">
    <button class="tph-tab active" data-tab="nerve"   style="flex:1;">NERVE</button>
    <button class="tph-tab"        data-tab="alvos"   style="flex:1;">ALVOS</button>
    <button class="tph-tab"        data-tab="drops"   style="flex:1;">DROPS</button>
    <button class="tph-tab"        data-tab="stats"   style="flex:1;">STATS</button>
    <button class="tph-tab"        data-tab="logs"    style="flex:1;">LOGS</button>
</div>

<!-- ── SCROLL AREA ── -->
<div id="tph-scroll" style="flex:1;overflow-y:auto;padding:12px;scrollbar-width:thin;scrollbar-color:#252538 #08081a;">

    <!-- NERVE TAB -->
    <div id="tph-tab-nerve">
        <div class="tph-card">
            <div class="tph-label">⚡ NERVE ATUAL</div>
            <div style="display:flex;align-items:baseline;gap:8px;margin-bottom:8px;">
                <div id="tph-nerve-val" style="font-size:36px;font-weight:800;letter-spacing:1px;color:#e0e0e0;line-height:1;">--/--</div>
                <div id="tph-nerve-phase" style="font-size:11px;color:#3a3a6a;"></div>
            </div>
            <div style="background:#101020;border-radius:5px;height:10px;overflow:hidden;margin-bottom:5px;">
                <div id="tph-nerve-bar" style="height:100%;width:0%;background:#5c2db0;border-radius:5px;transition:width .9s linear;"></div>
            </div>
            <div id="tph-nerve-sub" style="font-size:10px;color:#3a3a6a;"></div>
        </div>

        <div class="tph-card" style="margin-top:10px;">
            <div class="tph-label" style="margin-bottom:8px;">⚙ CONFIGURAÇÃO DA SESSÃO</div>
            <div style="font-size:10px;color:#3a3a5a;margin-bottom:6px;">Modo quando sem nerve:</div>
            <div style="display:flex;gap:6px;margin-bottom:10px;">
                <button id="tph-mode-wait"  class="tph-btn" style="flex:1;font-size:10px;background:#1a3a1a;">🟢 Aguardar</button>
                <button id="tph-mode-leave" class="tph-btn" style="flex:1;font-size:10px;background:#1a1a2e;">🔴 Sair</button>
            </div>
            <div id="tph-session-desc" style="font-size:10px;color:#2a2a4a;line-height:1.6;"></div>
        </div>

        <div class="tph-card" style="margin-top:10px;">
            <div class="tph-label" style="margin-bottom:6px;">🎲 ANTI-PADRÃO</div>
            <div style="font-size:10px;color:#2a2a4a;line-height:1.6;">
                A cada <strong style="color:#ffd966;">4 crimes</strong>, o 4.º é sempre <strong style="color:#ffd966;">completamente aleatório</strong>.<br>
                Refresh automático a cada <strong style="color:#6c5ce7;">3–5 min</strong> (URL sempre limpo).
            </div>
            <div style="margin-top:8px;font-size:10px;color:#3a3a5a;">Crimes até próximo aleatório:</div>
            <div id="tph-counter-display" style="font-size:20px;font-weight:800;color:#6c5ce7;line-height:1.2;">—</div>
        </div>
    </div>

    <!-- ALVOS TAB -->
    <div id="tph-tab-alvos" style="display:none;">
        <div id="tph-victims-wrap"></div>
    </div>

    <!-- DROPS TAB -->
    <div id="tph-tab-drops" style="display:none;">
        <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;">
            <div class="tph-label" style="margin-bottom:0;">🍹 DROPS DRUNK WOMAN</div>
            <button id="tph-clear-drops" style="padding:2px 8px;background:#160808;border:1px solid #3a1010;color:#993333;border-radius:4px;cursor:pointer;font-size:9px;">Repor</button>
        </div>
        <div id="tph-drops-list"></div>
    </div>

    <!-- STATS TAB -->
    <div id="tph-tab-stats" style="display:none;">
        <div class="tph-card" style="margin-bottom:10px;">
            <div class="tph-label" style="margin-bottom:10px;">🦹 PICKPOCKETS</div>
            <div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:8px;">
                <div class="tph-stat-box"><div class="tph-stat-val" id="st-count">0</div><div class="tph-stat-lbl">crimes</div></div>
                <div class="tph-stat-box"><div class="tph-stat-val" id="st-nerve">0</div><div class="tph-stat-lbl">nerve gasto</div></div>
                <div class="tph-stat-box"><div class="tph-stat-val" id="st-succ">—</div><div class="tph-stat-lbl">sucesso%</div></div>
            </div>
        </div>
        <div class="tph-card" style="margin-bottom:10px;">
            <div class="tph-label" style="margin-bottom:10px;">👤 POR ALVO</div>
            <div id="st-by-victim" style="font-size:11px;display:flex;flex-direction:column;gap:5px;"></div>
        </div>
        <button id="tph-reset-stats" style="width:100%;padding:8px;background:#160808;border:1px solid #3a1010;color:#993333;border-radius:6px;cursor:pointer;font-size:11px;">⚠ Repor estatísticas</button>
    </div>

    <!-- LOGS TAB -->
    <div id="tph-tab-logs" style="display:none;">
        <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;">
            <div style="font-size:10px;color:#2a2a4a;">Últimas 300 entradas</div>
            <button id="tph-clear-logs" style="padding:3px 10px;background:#160808;border:1px solid #3a1010;color:#993333;border-radius:4px;cursor:pointer;font-size:10px;">Limpar</button>
        </div>
        <div id="tph-logs-list" style="display:flex;flex-direction:column;gap:3px;font-size:11px;"></div>
    </div>

</div><!-- /scroll -->

<style>
#tph-box * { box-sizing:border-box; }
#tph-scroll::-webkit-scrollbar { width:4px; }
#tph-scroll::-webkit-scrollbar-track { background:#08081a; }
#tph-scroll::-webkit-scrollbar-thumb { background:#202030; border-radius:3px; }
.tph-card  { background:#0c0c1c;border:1px solid #1a1a2e;border-radius:8px;padding:12px; }
.tph-label { font-size:9px;letter-spacing:1.5px;color:#2a2a5a;text-transform:uppercase;margin-bottom:5px; }
.tph-btn   { width:100%;padding:8px 0;border:none;border-radius:6px;color:#d0d0e8;cursor:pointer;font-size:12px;font-weight:700;transition:filter .15s; }
.tph-btn:hover { filter:brightness(1.35); }
.tph-tab   { padding:7px 0;border:none;background:none;color:#2a2a4a;font-size:9px;font-weight:800;cursor:pointer;border-bottom:2px solid transparent;letter-spacing:.6px;text-transform:uppercase; }
.tph-tab.active { background:#0c0c1c !important;color:#6c5ce7 !important;border-bottom:2px solid #6c5ce7 !important; }
.tph-stat-box  { background:#08081a;border:1px solid #1a1a2e;border-radius:6px;padding:8px;text-align:center; }
.tph-stat-val  { font-size:20px;font-weight:800;color:#6c5ce7;line-height:1; }
.tph-stat-lbl  { font-size:9px;color:#2a2a4a;margin-top:3px;text-transform:uppercase;letter-spacing:.4px; }
.tph-log       { background:#0c0c1c;border-radius:4px;padding:4px 9px;border-left:2px solid #1a1a2e;line-height:1.5;font-size:10px; }
.tph-log-ts    { color:#1e1e3a;font-size:9px;margin-right:5px; }
.tph-tinput    { width:100%;background:#0c0c1a;border:1px solid #20203a;color:#d0d0e8;padding:5px;border-radius:5px;font-size:11px; }
</style>`;

    document.body.appendChild(box);

    /* ── Colapsar ── */
    let collapsed = false;
    document.getElementById("tph-collapse").onclick = () => {
        collapsed = !collapsed;
        box.style.transform = collapsed ? "translateX(360px)" : "translateX(0)";
        document.getElementById("tph-collapse").textContent = collapsed ? "▶" : "◀";
    };

    /* ── Pausa ── */
    function updatePauseUI() {
        const paused  = isPaused();
        const btn     = document.getElementById("tph-pause");
        const banner  = document.getElementById("tph-pause-banner");
        if (paused) {
            btn.textContent      = "▶";
            btn.style.background = "#0e3a0e"; btn.style.borderColor = "#1a7a1a"; btn.style.color = "#00e676";
            if (banner) banner.style.display = "block";
            addLog("crime", "⏸ Pausado");
        } else {
            btn.textContent      = "⏸";
            btn.style.background = "#3a1a00"; btn.style.borderColor = "#7a3a00"; btn.style.color = "#ffd966";
            if (banner) banner.style.display = "none";
            addLog("crime", "▶ Retomado");
        }
    }
    document.getElementById("tph-pause").onclick = () => { setPaused(!isPaused()); updatePauseUI(); };
    updatePauseUI();

    /* ── Start manual ── */
    document.getElementById("tph-start").onclick = () => {
        if (isPickpocketPage()) { runPickpocketLoop(); }
        else { addLog("warn", "⚠️ Navega para a página de pickpocket primeiro!"); }
    };

    /* ── Tabs ── */
    const TAB_NAMES = ["nerve","alvos","drops","stats","logs"];
    function switchTab(name) {
        TAB_NAMES.forEach(t => {
            document.getElementById(`tph-tab-${t}`).style.display = t === name ? "block" : "none";
        });
        document.querySelectorAll(".tph-tab").forEach(b => b.classList.toggle("active", b.dataset.tab === name));
    }
    document.querySelectorAll(".tph-tab").forEach(b => b.addEventListener("click", () => switchTab(b.dataset.tab)));
    switchTab("nerve");

    /* ── Modo sessão ── */
    function updateSessionModeUI() {
        const m = getSessionMode();
        const bw = document.getElementById("tph-mode-wait"), bl = document.getElementById("tph-mode-leave");
        if (bw) bw.style.background = m === "wait"  ? "#0e4a0e" : "#1a3a1a";
        if (bl) bl.style.background = m === "leave" ? "#4a0e0e" : "#1a1a2e";
        const desc = document.getElementById("tph-session-desc");
        if (desc) desc.textContent = m === "wait"
            ? "🟢 Fica na página mesmo sem nerve. Ataca assim que aparecer alvo prioritário com ≥5 nerve."
            : "🔴 Sai da sessão quando nerve < 5.";
    }
    document.getElementById("tph-mode-wait").onclick  = () => { setSessionMode("wait");  updateSessionModeUI(); };
    document.getElementById("tph-mode-leave").onclick = () => { setSessionMode("leave"); updateSessionModeUI(); };
    updateSessionModeUI();

    /* ── Botões ── */
    document.getElementById("tph-clear-logs").onclick  = () => clearLogs();
    document.getElementById("tph-clear-drops").onclick = () => { if (confirm("Repor drops?")) { localStorage.removeItem(DROP_STATS_KEY); renderDropStats(); } };
    document.getElementById("tph-reset-stats").onclick = () => { if (confirm("Repor estatísticas?")) { localStorage.removeItem(STATS_KEY); updateStatsDisplay(); } };

    /* ══════════════════════════════════════════════════════
       STATUS BADGE (header)
    ══════════════════════════════════════════════════════ */
    function updateStatusBadge(text, color = "#3a3a6a") {
        const el = document.getElementById("tph-status-badge");
        if (el) { el.textContent = text; el.style.color = color; }
    }

    /* ══════════════════════════════════════════════════════
       NERVE DISPLAY
    ══════════════════════════════════════════════════════ */
    function updateNerveDisplay() {
        const valEl   = document.getElementById("tph-nerve-val");
        const phaseEl = document.getElementById("tph-nerve-phase");
        const barEl   = document.getElementById("tph-nerve-bar");
        const subEl   = document.getElementById("tph-nerve-sub");
        const cntEl   = document.getElementById("tph-counter-display");
        if (!valEl) return;

        valEl.textContent = `${currentNerve}/${currentNerveMax}`;
        const pct = currentNerveMax > 0 ? (currentNerve / currentNerveMax) * 100 : 0;

        if (currentNerve >= currentNerveMax && currentNerveMax > 0) {
            valEl.style.color   = "#00e676";
            phaseEl.textContent = "nerve cheio!"; phaseEl.style.color = "#00e676";
            barEl.style.width   = "100%"; barEl.style.background = "#00e676";
            subEl.textContent   = "Pronto para atacar";
        } else if (currentNerve >= 5) {
            valEl.style.color   = "#d0d0e8";
            phaseEl.textContent = `${currentNerveMax - currentNerve} em falta`; phaseEl.style.color = "#3a3a6a";
            barEl.style.width   = pct + "%"; barEl.style.background = pct > 70 ? "#7c3dd6" : "#4a1a90";
            subEl.textContent   = `${Math.round(pct)}% — nerve suficiente para atacar`;
        } else {
            valEl.style.color   = "#ff6666";
            phaseEl.textContent = "nerve baixo"; phaseEl.style.color = "#ff4444";
            barEl.style.width   = pct + "%"; barEl.style.background = "#4a1a1a";
            subEl.textContent   = `Precisa de ≥5 nerve para atacar`;
        }

        // Contador anti-padrão
        if (cntEl) {
            const nextRandom = 4 - (nervePickCounter % 4);
            cntEl.textContent = nextRandom === 4 ? "4 (próximo é aleatório!)" : nextRandom;
            cntEl.style.color = nextRandom === 1 ? "#ffd966" : "#6c5ce7";
        }
    }

    /* ══════════════════════════════════════════════════════
       TAB ALVOS — FILTROS DE STATUS E BUILD
    ══════════════════════════════════════════════════════ */
    function renderVictimFilters() {
        const wrap = document.getElementById("tph-victims-wrap");
        if (!wrap) return;
        wrap.innerHTML = "";
        const filters = getTargetFilters();

        VICTIM_DB.forEach(v => {
            const vf = filters[v.id] || buildDefaultFilters()[v.id];

            const card = document.createElement("div");
            card.className = "tph-card";
            card.style.marginBottom = "10px";

            /* Header do alvo */
            card.innerHTML = `
                <div style="display:flex;align-items:center;gap:8px;margin-bottom:10px;">
                    <span style="font-size:24px;">${v.emoji}</span>
                    <div style="flex:1;">
                        <div style="font-weight:800;font-size:14px;color:#c0c0e0;">${v.label}</div>
                        <div style="font-size:9px;color:#3a3a6a;margin-top:2px;">$${v.cashMin.toLocaleString()}–$${v.cashMax.toLocaleString()} · ⏱~${v.timerSec}s · ${v.riskHosp ? "🏥 risco hosp" : ""}</div>
                    </div>
                </div>
                ${v.requireRazor ? `<div style="background:#0c0c1a;border-radius:5px;padding:6px 8px;margin-bottom:10px;border-left:2px solid #ffd966;font-size:10px;color:#ffd966;">⭐ ${v.unique} &nbsp;<span style="color:#6c5ce7;">✂️ Razor no inv.</span></div>` : ""}
                ${v.notes ? `<div style="font-size:9px;color:#2a2a4a;margin-bottom:8px;line-height:1.5;">${v.notes}</div>` : ""}`;

            /* Secção de status */
            const statusSec = document.createElement("div");
            statusSec.innerHTML = `<div class="tph-label" style="margin-bottom:6px;">🎭 Atacar quando status:</div>`;
            const statusGrid = document.createElement("div");
            statusGrid.style.cssText = "display:grid;grid-template-columns:1fr 1fr;gap:3px;margin-bottom:6px;";

            ALL_STATUSES.forEach(s => {
                const inVictim = v.statuses.includes(s.id);
                const checked  = vf.statuses?.[s.id] === true;
                const multColor = s.mult >= 1.2 ? "#00e676" : s.mult >= 1.0 ? "#ffd966" : "#ff6666";

                const label = document.createElement("label");
                label.style.cssText = `display:flex;align-items:center;gap:4px;padding:4px 6px;border-radius:4px;cursor:${inVictim ? "pointer" : "default"};background:${inVictim && checked ? "#0e1e0e" : "#080812"};border:1px solid ${inVictim ? (checked ? "#2a4a2a" : "#1a1a2e") : "#0e0e18"};opacity:${inVictim ? "1" : "0.3"};font-size:10px;`;
                label.innerHTML = `<input type="checkbox" ${checked ? "checked" : ""} ${!inVictim ? "disabled" : ""} data-vid="${v.id}" data-sid="${s.id}" class="tph-status-cb" style="cursor:${inVictim ? "pointer" : "default"};"><span>${s.emoji} ${s.label}</span><span style="margin-left:auto;font-size:9px;color:${multColor};">×${s.mult.toFixed(1)}</span>`;
                statusGrid.appendChild(label);
            });
            statusSec.appendChild(statusGrid);

            /* Botões atalho */
            const sBtns = document.createElement("div");
            sBtns.style.cssText = "display:flex;gap:4px;margin-bottom:10px;";
            sBtns.innerHTML = `
                <button class="tph-btn tph-s-all" data-vid="${v.id}"  style="flex:1;padding:4px;font-size:9px;background:#0e2a0e;border:1px solid #1a4a1a;">✓ Todos</button>
                <button class="tph-btn tph-s-none" data-vid="${v.id}" style="flex:1;padding:4px;font-size:9px;background:#2a0e0e;border:1px solid #4a1a1a;">✗ Nenhum</button>
                <button class="tph-btn tph-s-good" data-vid="${v.id}" style="flex:1;padding:4px;font-size:9px;background:#0e0e2e;border:1px solid #2a2a5a;">★ Bons</button>`;
            statusSec.appendChild(sBtns);
            card.appendChild(statusSec);

            /* Secção de build */
            const buildSec = document.createElement("div");
            buildSec.innerHTML = `<div class="tph-label" style="margin-bottom:6px;">🧍 Atacar quando físico:</div>`;
            const buildGrid = document.createElement("div");
            buildGrid.style.cssText = "display:flex;flex-wrap:wrap;gap:4px;margin-bottom:4px;";

            ALL_BUILDS.forEach(b => {
                const checked     = vf.builds?.[b.id] === true;
                const isExclusive = v.requireBuild === b.id;
                const label = document.createElement("label");
                label.style.cssText = `display:flex;align-items:center;gap:4px;padding:4px 8px;border-radius:4px;cursor:pointer;background:${checked ? "#0e1e0e" : "#080812"};border:1px solid ${checked ? (isExclusive ? "#6c5ce7" : "#2a4a2a") : "#1a1a2e"};font-size:10px;`;
                label.innerHTML = `<input type="checkbox" ${checked ? "checked" : ""} data-vid="${v.id}" data-bid="${b.id}" class="tph-build-cb" style="cursor:pointer;"><span>${b.emoji} ${b.label}${isExclusive ? ` <span style="color:#6c5ce7;font-size:9px;">★</span>` : ""}</span>`;
                buildGrid.appendChild(label);
            });
            buildSec.appendChild(buildGrid);
            buildSec.innerHTML += `<div style="font-size:9px;color:#1e1e3a;margin-bottom:6px;">★ Build com drops exclusivos</div>`;
            card.appendChild(buildSec);

            /* Resumo activo */
            const summary = document.createElement("div");
            summary.className = `tph-sum-${v.id}`;
            summary.style.cssText = "background:#060610;border-radius:4px;padding:5px 8px;font-size:9px;color:#3a3a6a;line-height:1.6;";
            card.appendChild(summary);

            wrap.appendChild(card);

            /* Eventos */
            card.querySelectorAll(".tph-status-cb").forEach(cb => {
                cb.addEventListener("change", () => { toggleStatusFilter(cb.dataset.vid, cb.dataset.sid); refreshVictimSummary(cb.dataset.vid); });
            });
            card.querySelectorAll(".tph-build-cb").forEach(cb => {
                cb.addEventListener("change", () => { toggleBuildFilter(cb.dataset.vid, cb.dataset.bid); refreshVictimSummary(cb.dataset.vid); });
            });
            card.querySelector(".tph-s-all").addEventListener("click", () => {
                const f = getTargetFilters();
                v.statuses.forEach(s => { f[v.id].statuses[s] = true; }); saveTargetFilters(f); renderVictimFilters();
            });
            card.querySelector(".tph-s-none").addEventListener("click", () => {
                const f = getTargetFilters();
                ALL_STATUSES.forEach(s => { f[v.id].statuses[s.id] = false; }); saveTargetFilters(f); renderVictimFilters();
            });
            card.querySelector(".tph-s-good").addEventListener("click", () => {
                const f = getTargetFilters();
                ALL_STATUSES.forEach(s => { f[v.id].statuses[s.id] = v.goodStatus.includes(s.id); }); saveTargetFilters(f); renderVictimFilters();
            });

            refreshVictimSummary(v.id);
        });
    }

    function refreshVictimSummary(victimId) {
        const v   = VICTIM_DB.find(x => x.id === victimId);
        const f   = getTargetFilters();
        const vf  = f[victimId] || buildDefaultFilters()[victimId];
        const el  = document.querySelector(`.tph-sum-${victimId}`);
        if (!v || !el) return;
        const activeS = ALL_STATUSES.filter(s => v.statuses.includes(s.id) && vf.statuses?.[s.id]);
        const activeB = ALL_BUILDS.filter(b => vf.builds?.[b.id]);
        el.innerHTML = (activeS.length && activeB.length)
            ? `🎯 Status aceites: <span style="color:#00e676;">${activeS.map(s => s.emoji + " " + s.label).join(", ")}</span><br>🧍 Builds aceites: <span style="color:#6c5ce7;">${activeB.map(b => b.emoji + " " + b.label).join(", ")}</span>`
            : `<span style="color:#ff4444;">⚠️ Nenhum estado/build seleccionado — não vai atacar!</span>`;
    }

    renderVictimFilters();

    /* ══════════════════════════════════════════════════════
       DROPS DISPLAY
    ══════════════════════════════════════════════════════ */
    function renderDropStats() {
    const wrap = document.getElementById("tph-drops-list");
    if (!wrap) return;

    const data = getDropStats();

    if (!Object.keys(data).length) {
        wrap.innerHTML = `<div style="color:#1e1e3a;text-align:center;padding:20px;">Sem drops ainda.</div>`;
        return;
    }

    wrap.innerHTML = Object.entries(data).map(([victimId, drops]) => {
        const victim = VICTIM_DB.find(v => v.id === victimId);
        const total = Object.values(drops).reduce((a,b)=>a+b,0);

        return `
        <div style="margin-bottom:10px;">
            <div style="font-weight:800;color:#6c5ce7;margin-bottom:4px;">
                ${victim ? victim.emoji + " " + victim.label : victimId}
                <span style="font-size:9px;color:#2a2a4a;">(${total})</span>
            </div>
            ${
                Object.entries(drops).map(([dropId,count])=>{
                    const drop = VICTIM_DROPS[victimId]?.find(d=>d.id===dropId);
                    if (!drop) return "";
                    return `<div style="font-size:11px;padding-left:10px;">
                        ${drop.emoji} ${drop.name}
                        <span style="color:#6c5ce7;">x${count}</span>
                    </div>`;
                }).join("")
            }
        </div>`;
    }).join("");
}

    /* ══════════════════════════════════════════════════════
       STATS DISPLAY
    ══════════════════════════════════════════════════════ */
    function updateStatsDisplay() {
        const s   = getStats();
        const pp  = s.pickpockets;
        const set = (id, v) => { const el = document.getElementById(id); if (el) el.textContent = v; };
        set("st-count", pp.count);
        set("st-nerve", pp.nerveSpent);
        set("st-succ",  pp.count > 0 ? Math.round((pp.successCount / pp.count) * 100) + "%" : "—");

        const bvEl = document.getElementById("st-by-victim");
        if (bvEl) {
            bvEl.innerHTML = Object.entries(pp.byVictim).map(([id, data]) => {
                const v = VICTIM_DB.find(x => x.id === id);
                const pct = data.count > 0 ? Math.round((data.success / data.count) * 100) : 0;
                return `<div style="display:flex;justify-content:space-between;background:#080812;padding:5px 8px;border-radius:4px;border-left:2px solid #1a1a2e;">
                    <span>${v ? v.emoji + " " + v.label : id}</span>
                    <span style="color:#6c5ce7;">${data.count}× <span style="color:#2a2a5a;font-size:9px;">${pct}%</span></span>
                </div>`;
            }).join("") || `<div style="color:#1e1e3a;text-align:center;padding:10px;">Sem dados ainda.</div>`;
        }
    }
    updateStatsDisplay();

    /* ══════════════════════════════════════════════════════
       LOGS DISPLAY
    ══════════════════════════════════════════════════════ */
    function renderLogs() {
        const c = document.getElementById("tph-logs-list");
        if (!c) return;
        const logs = getLogs();
        c.innerHTML = "";
        if (!logs.length) { c.innerHTML = `<div style="color:#1e1e3a;text-align:center;padding:20px;font-size:11px;">Sem registos.</div>`; return; }
        logs.forEach(e => {
            const el = document.createElement("div");
            el.className = "tph-log";
            el.style.borderLeftColor = CAT_CLR[e.category] || "#1a1a2e";
            el.innerHTML = `<span class="tph-log-ts">${e.ts}</span>${e.msg}`;
            c.appendChild(el);
        });
    }
    renderLogs();

    /* ══════════════════════════════════════════════════════
       TICK — ACTUALIZA NERVE DISPLAY 1×/s
    ══════════════════════════════════════════════════════ */
    setInterval(() => {
        readNerveFromDOM();
        updateNerveDisplay();
    }, 1000);

    /* Fetch nerve da API a cada 30s */
    setInterval(fetchNerveFromAPI, 30000);

    /* ══════════════════════════════════════════════════════
       ARRANQUE AUTOMÁTICO
       Se já estamos na página de pickpocket → começa sozinho
    ══════════════════════════════════════════════════════ */
    async function init() {
        await fetchNerveFromAPI();
        updateNerveDisplay();
        updateStatsDisplay();

        if (isPickpocketPage()) {
            addLog("system", "🟢 HUD iniciado — página de pickpocket detectada — a arrancar sessão");
            updateStatusBadge("🔄 A iniciar…", "#6c5ce7");
            await delay(1500);
            runPickpocketLoop();
        } else if (isCrimesPage()) {
            addLog("system", "⚠️ Página de crimes detectada — navega para #/pickpocketing para iniciar");
            updateStatusBadge("⚠️ Navega para pickpocket", "#ffd966");
            // Aguarda hash change para pickpocket
            window.addEventListener("hashchange", () => {
                if (isPickpocketPage() && !pickpocketRunning) {
                    addLog("system", "✅ Pickpocket detectado via hashchange — a arrancar");
                    runPickpocketLoop();
                }
            });
        }
    }

    init();
    addLog("system", "🟣 Pickpocket HUD v1.0 carregado — apenas crimes/pickpocketing");

})();