TWAT Test

Identify, track, and evaluate war enemies with local notes, tags, Matchmaker guidance, and page highlighting.

Você precisará instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Você precisará instalar uma extensão como o Tampermonkey para instalar este script.

Você precisará instalar um gerenciador de scripts de usuário para instalar este script.

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

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

(Eu já possuo um gerenciador de estilos de usuário, me deixar fazer a instalação!)

// ==UserScript==
// @name         TWAT Test
// @namespace    https://torn.com/
// @version      1.5.76
// @description  Identify, track, and evaluate war enemies with local notes, tags, Matchmaker guidance, and page highlighting.
// @author       Maximate [3428839]
// @match        https://www.torn.com/*
// @grant        GM_setClipboard
// @grant        GM_addStyle
// @grant        GM_xmlhttpRequest
// @connect      api.torn.com
// @noframes
// @run-at       document-start
// ==/UserScript==

/*
Copyright (c) 2026 Maximate [3428839]. All rights reserved.

This software and its source code are the exclusive intellectual property of
Maximate [3428839]. Permission is granted for private, personal use only.

No person or entity may copy, reproduce, modify, distribute, publish,
sublicense, sell, share, reverse engineer for redistribution, or claim
authorship of this script, in whole or in part, without the prior explicit
written permission of Maximate [3428839].

Any unauthorized use, reproduction, or distribution is strictly prohibited.
*/
// Internal ownership reference: 5111976

(function () {
    "use strict";

    if (window.top !== window.self) {
        return;
    }

    const STORAGE_KEY = "torn-war-enemy-tracker:v1";
    const DEFAULT_TAGS = ["priority", "watch", "inactive", "chain", "easy", "dangerous", "med", "mugger"];
    const SCRIPT_OWNER = "Maximate [3428839]";
    const PAGE_SCAN_INTERVAL_MS = 1800;
    const MAX_CONTEXTS = 8;
    const API_REFRESH_MS = 5 * 60 * 1000;
    const API_BATCH_LIMIT = 8;
    const MATCHMAKER_VERSION = "1.5.76";
    const PDA_APIKEY_PLACEHOLDER = "###PDA-APIKEY###";
    const FTAB_OTHER_COLOR = "#ff6b1a";
    const FTAB_OTHER_COLOR_ID = "14";
    const FTAB_DEFAULT_ALLIANCES = [
        { name: "Nameless", tag: "NA", color: "#83E51B", factionIds: [6895,7227,7935,17991,18597,39788,10542,8500,13789,8766,13726,40774,35739,24106,48184,47042] }
    ];
    const TRANSLATIONS = {
        en: {
            languageLabel: "Language",
            sizeLabel: "Size",
            btnPin: "Pin",
            btnUnpin: "Unpin",
            btnMobile: "Mobile",
            btnDesktop: "Desktop",
            btnAttack: "Attack",
            btnCopy: "Copy",
            attackAssist: "Attack Assist",
            attackAssistCopy: "Quick manual tools for the best live targets, plus reminders for chain pressure and release timing.",
            topActionable: "Top Actionable Targets",
            chainTimer: "Chain Timer",
            chainTimerCopy: "Set a simple reminder countdown for the current chain.",
            chainMinutes: "Chain minutes",
            chainStart: "Start Chain",
            chainClear: "Clear Chain",
            chainInactive: "No active chain timer.",
            chainEndsIn: "Chain ends in {time}",
            alertSettings: "Alerts",
            alertSettingsCopy: "Visual and audible reminders when targets become attack-ready or hospital releases are close.",
            enableReadyAlerts: "Ready alerts",
            enableSoonAlerts: "Soon alerts",
            enableSound: "Sound",
            copiedProfile: "Profile link copied.",
            copiedReport: "Report copied.",
            copiedAttack: "Attack link copied.",
            readyNow: "Ready now",
            readySoon: "Ready soon",
            notificationReadyTitle: "Target ready",
            notificationSoonTitle: "Hospital release soon",
            notificationChainTitle: "Chain reminder",
            notificationTravelTitle: "Travel arrival",
            notificationTravelSoonTitle: "Travel landing soon",
            notificationReadyBody: "{name} is attack-ready now.",
            notificationSoonBody: "{name} is nearly out of hospital.",
            notificationChainBody: "Chain timer ends in {time}.",
            notificationTravelBody: "{name} has landed in {place}.",
            notificationTravelSoonBody: "{name} lands in {time} at {place}.",
            ownerLine: "Owner: {owner}",
            tabWar: "War",
            tabMatchmaker: "M/M",
            tabRevenge: "Revenge",
            tabFTAB: "FTAB",
            tabInstructions: "Instructions",
            btnSync: "Sync",
            btnRefresh: "Refresh",
            btnLinks: "Links",
            btnReset: "Reset",
            btnHide: "Hide",
            btnOpen: "Open",
            btnExport: "Export",
            btnImport: "Import",
            btnActions: "Actions",
            btnHideActions: "Hide actions",
            btnTools: "Tools",
            btnHideTools: "Hide tools",
            btnToolsUnavailable: "Tools unavailable",
            searchPlaceholder: "Search enemies or tags",
            sortScore: "Sort: score",
            sortRecent: "Sort: recent",
            sortName: "Sort: name",
            sortLevel: "Sort: level",
            filterAll: "All",
            filterPriority: "Priority",
            filterDangerous: "Dangerous",
            filterEasy: "Easy",
            filterInactive: "Inactive",
            hideAvoids: "Hide avoids / blocked",
            hideCautions: "Hide cautions",
            hideGreenlights: "Hide greenlights / playable",
            resetFilters: "Reset filters",
            noTracked: "No tracked enemies yet.",
            noTrackedWar: "Add your YATA API key and enemy faction ID above, then press Sync to load the current war roster.",
            noTrackedRevenge: "Add revenge player IDs above, then click Load revenge to pull them into the revenge board.",
            warDashboard: "War Dashboard",
            warCopy: "Set your YATA API key and the enemy faction ID here, then sync to build a live war board with hospital timers, availability, and ranked targets.",
            revengeDashboard: "Revenge Dashboard",
            revengeCopy: "Load revenge hits by player ID. Revenge targets stay visible in the tracker so they are easy to find whenever you want them.",
            revengeInputLabel: "Revenge Player ID Input",
            apiPlaceholder: "YATA API key",
            apiSavedPlaceholder: "Saved YATA API key (hidden)",
            yataKey: "YATA API",
            yataPlaceholder: "YATA API key",
            yataSavedPlaceholder: "Saved YATA API key (hidden)",
            factionPlaceholder: "Enemy faction ID",
            revengeIdsPlaceholder: "Enter revenge player ID(s)",
            idHelper: "Enter one or more Torn player IDs separated by commas or spaces.",
            apiRequired: "required",
            apiRedacted: "redacted",
            apiNotSet: "not set",
            apiSourceStored: "saved",
            apiSourcePda: "PDA-injected",
            apiKey: "YATA API",
            revengeIds: "Revenge IDs",
            factionId: "Faction ID",
            syncFaction: "Sync faction",
            refreshPlayerData: "Refresh player data",
            refreshPageLinks: "Refresh page links",
            resetTracker: "Reset tracker",
            loadRevenge: "Load revenge",
            refreshRevengeData: "Refresh revenge data",
            availableNow: "Available Now",
            hospitalized: "Hospitalized",
            blocked: "Blocked",
            availableSoon: "Available Soon",
            nextHospitalRelease: "Next hospital release",
            nextTargets: "Next Targets",
            matchmakerDeck: "Matchmaker Deck",
            matchmakerRecommended: "Recommended Right Now",
            matchmakerWatchlist: "Watchlist",
            matchmakerProtected: "Protected / Blocked",
            matchmakerGreenlights: "Greenlights",
            matchmakerPlayable: "Playable",
            matchmakerCaution: "Caution",
            matchmakerAvoid: "Avoid / Blocked",
            matchmakerCopy: "This view ranks every tracked target by Matchmaker recommendation so you can judge that layer separately from the normal war and revenge dashboards.",
            nextRevengeTargets: "Next Revenge Targets",
            allTargets: "All Targets",
            allRevengeTargets: "All Revenge Targets",
            noImmediateTargets: "No immediate targets identified yet.",
            instructionsTitle: "Instructions",
            instructionsCopy: "This tab explains how the tracker score is built so you can quickly understand why one target is ranked above another.",
            scoreOverview: "Score Overview",
            scoreOverview1: "Each target gets a score from 0 to 99. Higher scores are meant to suggest a more actionable or more important target.",
            scoreOverview2: "The tracker combines level, recent fight history, life, availability, status, last action, and some tags.",
            baseScoreInputs: "Base Score Inputs",
            base1: "Level adds up to 45 points. Higher-level targets score higher.",
            base2: "Recorded losses against that target add up to 30 points.",
            base3: "Recorded wins against that target subtract up to 24 points.",
            base4: "Recorded escapes add up to 15 points.",
            base5: "Current life adds up to 8 points.",
            availabilityEffects: "Availability Effects",
            avail1: "Available targets get a positive boost.",
            avail2: "Targets coming out of hospital soon get a smaller boost.",
            avail3: "Hospitalized, jailed, travelling, or federal targets lose score because they are less actionable.",
            avail4: "Revenge targets always get a strong availability boost so they stay prominent.",
            statusEffects: "Status And Activity Effects",
            status1: "Status text like okay or online adds points.",
            status2: "Status text like hospital, travel, abroad, offline, or federal reduces points.",
            status3: "Recent last-action activity such as online or active adds points, while offline removes points.",
            tagEffects: "Tag Effects",
            tag1: "priority and dangerous add a large boost.",
            tag2: "watch adds a small boost.",
            tag3: "med adds a slight boost.",
            tag4: "inactive and easy lower the score.",
            tag5: "Revenge targets also carry the revenge tag when added.",
            finalNumber: "Reading The Final Number",
            final1: "80 to 99 usually means high priority and is shown with the hottest badge style.",
            final2: "45 to 79 is mid priority.",
            final3: "Below 45 usually means lower urgency or lower actionability right now.",
            final4: "The score is a guide only. Your notes, tags, and your faction's current strategy should still drive the final choice.",
            warTarget: "War target",
            revengeTarget: "Revenge target",
            factionUnknown: "Faction unknown",
            levelUnknown: "Level unknown",
            lifeUnknown: "Life unknown",
            statusLabel: "Status",
            locationLabel: "Location",
            travelEtaLabel: "Travel ETA",
            travelAlarmLabel: "Landing alarm",
            travelAlarmSilence: "Silence",
            travelAlarmUnsilence: "Restore alarm",
            travelAlarmIdle: "Arms when target is traveling",
            hospLabel: "Hosp",
            lastActionLabel: "Last action",
            fightLog: "Your record",
            battleMemory: "Battle memory",
            seenLabel: "Seen",
            updatedLabel: "Updated",
            apiRefreshed: "API refreshed",
            selfIntelTitle: "Your Torn Intel",
            selfStatus: "Status",
            selfLocation: "Location",
            selfLastRefreshed: "Last refreshed",
            selfBattleStatsUnavailable: "Battle stats not available from current API response.",
            selfPersonalStatsUnavailable: "Personal stats not available from current API response.",
            runtimeLabel: "Runtime",
            runtimeBrowser: "Browser mode",
            runtimePda: "PDA mode",
            contexts: "Contexts",
            noSourcePages: "No source pages saved",
            tagsPlaceholder: "tags: priority, easy",
            manualStatusPlaceholder: "manual status override",
            notesPlaceholder: "notes",
            win: "Win",
            loss: "Loss",
            escape: "Escape",
            repairRecord: "Repair",
            remove: "Remove",
            lifeFormat: "Life {current}/{maximum}",
            levelFormat: "Level {level}",
            scoreWord: "score",
            recentFights: "Recent fights"
        },
        fr: {
            languageLabel: "Langue", sizeLabel: "Taille", btnPin: "Epingler", btnUnpin: "Liberer", btnMobile: "Mobile", btnDesktop: "Bureau", ownerLine: "Proprietaire : {owner}", tabWar: "Guerre", tabMatchmaker: "M/M", tabRevenge: "Revanche", tabInstructions: "Instructions",
            btnSync: "Sync", btnRefresh: "Rafraichir", btnLinks: "Liens", btnReset: "Reinit.", btnHide: "Masquer", btnOpen: "Ouvrir", btnExport: "Exporter", btnImport: "Importer",
            searchPlaceholder: "Rechercher ennemis ou tags", sortScore: "Tri : score", sortRecent: "Tri : recent", sortName: "Tri : nom", sortLevel: "Tri : niveau",
            filterAll: "Tous", filterPriority: "Priorite", filterDangerous: "Dangereux", filterEasy: "Facile", filterInactive: "Inactif",
            noTracked: "Aucune cible suivie.", noTrackedWar: "Ajoutez votre cle API limitee et l'ID de faction ennemie ci-dessus, puis appuyez sur Sync pour charger la liste de guerre.", noTrackedRevenge: "Ajoutez des ID de revanche ci-dessus, puis cliquez sur Charger revanche.",
            warDashboard: "Tableau de guerre", warCopy: "Saisissez votre cle API limitee et l'ID de la faction ennemie, puis synchronisez pour construire un tableau de guerre en direct avec timers d'hopital, disponibilite et classement.", revengeDashboard: "Tableau de revanche", revengeCopy: "Chargez des cibles de revanche par ID de joueur. Les cibles de revanche restent toujours actionnables dans le tracker.", revengeInputLabel: "Saisie ID de revanche",
            apiPlaceholder: "Cle API limitee", apiSavedPlaceholder: "Cle API enregistree (masquee)", factionPlaceholder: "ID de faction ennemie", revengeIdsPlaceholder: "Entrez les ID de revanche", idHelper: "Entrez un ou plusieurs ID Torn separes par des virgules ou des espaces.",
            apiRequired: "requis", apiRedacted: "masquee", apiNotSet: "non definie", apiKey: "Cle API", revengeIds: "ID revanche", factionId: "ID faction",
            syncFaction: "Sync faction", refreshPlayerData: "Rafraichir joueurs", refreshPageLinks: "Rafraichir liens", resetTracker: "Reinitialiser", loadRevenge: "Charger revanche", refreshRevengeData: "Rafraichir revanche",
            availableNow: "Disponibles", hospitalized: "Hospitalises", blocked: "Bloques", availableSoon: "Bientot dispo", nextHospitalRelease: "Prochaine sortie hopital", nextTargets: "Prochaines cibles", nextRevengeTargets: "Prochaines revanches", allTargets: "Toutes les cibles", allRevengeTargets: "Toutes les revanches", noImmediateTargets: "Aucune cible immediate identifiee.",
            instructionsTitle: "Instructions", instructionsCopy: "Cet onglet explique comment le score du tracker est calcule.", scoreOverview: "Vue d'ensemble du score", scoreOverview1: "Chaque cible recoit un score de 0 a 99. Un score plus eleve suggere une cible plus actionnable ou importante.", scoreOverview2: "Le tracker combine niveau, historique de combat, vie, disponibilite, statut, derniere activite et certains tags.",
            baseScoreInputs: "Elements du score de base", base1: "Le niveau ajoute jusqu'a 45 points.", base2: "Les defaites enregistrees contre cette cible ajoutent jusqu'a 30 points.", base3: "Les victoires enregistrees contre cette cible soustraient jusqu'a 24 points.", base4: "Les escapes ajoutes jusqu'a 15 points.", base5: "La vie actuelle ajoute jusqu'a 8 points.",
            availabilityEffects: "Effets de disponibilite", avail1: "Les cibles disponibles recoivent un bonus positif.", avail2: "Les cibles qui sortent bientot de l'hopital recoivent un bonus plus faible.", avail3: "Les cibles hospitalisees, en prison, en voyage ou en federal perdent du score.", avail4: "Les cibles de revanche recoivent toujours un gros bonus de disponibilite.",
            statusEffects: "Effets de statut et activite", status1: "Un statut comme okay ou online ajoute des points.", status2: "Un statut comme hospital, travel, abroad, offline ou federal retire des points.", status3: "Une activite recente comme online ou active ajoute des points, tandis que offline en retire.",
            tagEffects: "Effets des tags", tag1: "priority et dangerous ajoutent un gros bonus.", tag2: "watch ajoute un petit bonus.", tag3: "med ajoute un leger bonus.", tag4: "inactive et easy reduisent le score.", tag5: "Les cibles de revanche recoivent aussi le tag revenge.",
            finalNumber: "Lire le score final", final1: "80 a 99 signifie generalement haute priorite.", final2: "45 a 79 correspond a une priorite moyenne.", final3: "En dessous de 45 signifie souvent une urgence plus faible.", final4: "Le score reste un guide. Vos notes, tags et votre strategie doivent guider le choix final.",
            warTarget: "Cible de guerre", revengeTarget: "Cible de revanche", factionUnknown: "Faction inconnue", levelUnknown: "Niveau inconnu", lifeUnknown: "Vie inconnue", statusLabel: "Statut", locationLabel: "Lieu", hospLabel: "Hop.", lastActionLabel: "Derniere activite", fightLog: "Journal combat", battleMemory: "Memo bataille", seenLabel: "Vu", updatedLabel: "Maj", apiRefreshed: "API rafraichie", contexts: "Contextes", noSourcePages: "Aucune page source", tagsPlaceholder: "tags : priority, easy", manualStatusPlaceholder: "statut manuel", notesPlaceholder: "notes", win: "Victoire", loss: "Defaite", escape: "Escape", remove: "Suppr.", lifeFormat: "Vie {current}/{maximum}", levelFormat: "Niveau {level}", scoreWord: "score", copiedReport: "Rapport copie.", recentFights: "Combats recents"
        },
        es: {
            languageLabel: "Idioma", sizeLabel: "Tamano", btnPin: "Fijar", btnUnpin: "Soltar", btnMobile: "Movil", btnDesktop: "Escritorio", ownerLine: "Propietario: {owner}", tabWar: "Guerra", tabMatchmaker: "M/M", tabRevenge: "Revancha", tabInstructions: "Instrucciones",
            btnSync: "Sync", btnRefresh: "Actualizar", btnLinks: "Links", btnReset: "Reiniciar", btnHide: "Ocultar", btnOpen: "Abrir", btnExport: "Exportar", btnImport: "Importar",
            searchPlaceholder: "Buscar enemigos o etiquetas", sortScore: "Orden: puntuacion", sortRecent: "Orden: reciente", sortName: "Orden: nombre", sortLevel: "Orden: nivel",
            filterAll: "Todos", filterPriority: "Prioridad", filterDangerous: "Peligroso", filterEasy: "Facil", filterInactive: "Inactivo",
            noTracked: "Aun no hay objetivos seguidos.", noTrackedWar: "Agrega tu clave API limitada y el ID de la faccion enemiga arriba, luego pulsa Sync para cargar la lista de guerra.", noTrackedRevenge: "Agrega IDs de revancha arriba y pulsa Cargar revancha.",
            warDashboard: "Panel de guerra", warCopy: "Introduce tu clave API limitada y el ID de la faccion enemiga para crear un panel de guerra con tiempos de hospital, disponibilidad y ranking.", revengeDashboard: "Panel de revancha", revengeCopy: "Carga objetivos de revancha por ID de jugador. Los objetivos de revancha siempre se mantienen visibles como accionables.", revengeInputLabel: "Entrada ID de revancha",
            apiPlaceholder: "Clave API limitada", apiSavedPlaceholder: "Clave API guardada (oculta)", factionPlaceholder: "ID de faccion enemiga", revengeIdsPlaceholder: "Introduce ID(s) de revancha", idHelper: "Introduce uno o varios IDs de Torn separados por comas o espacios.",
            apiRequired: "requerido", apiRedacted: "oculta", apiNotSet: "sin definir", apiKey: "Clave API", revengeIds: "IDs de revancha", factionId: "ID de faccion",
            syncFaction: "Sync faccion", refreshPlayerData: "Actualizar jugadores", refreshPageLinks: "Actualizar links", resetTracker: "Reiniciar panel", loadRevenge: "Cargar revancha", refreshRevengeData: "Actualizar revancha",
            availableNow: "Disponibles", hospitalized: "Hospitalizados", blocked: "Bloqueados", availableSoon: "Disponibles pronto", nextHospitalRelease: "Siguiente salida hospital", nextTargets: "Proximos objetivos", nextRevengeTargets: "Proximas revanchas", allTargets: "Todos los objetivos", allRevengeTargets: "Todas las revanchas", noImmediateTargets: "No se han identificado objetivos inmediatos.",
            instructionsTitle: "Instrucciones", instructionsCopy: "Esta pestana explica como se calcula la puntuacion del tracker.", scoreOverview: "Resumen de puntuacion", scoreOverview1: "Cada objetivo obtiene una puntuacion de 0 a 99. Cuanto mas alta, mas accionable o importante se considera.", scoreOverview2: "El tracker combina nivel, historial de combates, vida, disponibilidad, estado, ultima actividad y algunas etiquetas.",
            baseScoreInputs: "Factores base", base1: "El nivel aporta hasta 45 puntos.", base2: "Las derrotas registradas contra ese objetivo suman hasta 30 puntos.", base3: "Las victorias registradas contra ese objetivo restan hasta 24 puntos.", base4: "Las escapes suman hasta 15 puntos.", base5: "La vida actual suma hasta 8 puntos.",
            availabilityEffects: "Efectos de disponibilidad", avail1: "Los objetivos disponibles reciben un impulso positivo.", avail2: "Los objetivos que salen pronto del hospital reciben un impulso menor.", avail3: "Los hospitalizados, encarcelados, viajando o en federal pierden puntuacion.", avail4: "Los objetivos de revancha siempre reciben un fuerte impulso de disponibilidad.",
            statusEffects: "Efectos de estado y actividad", status1: "Texto de estado como okay u online suma puntos.", status2: "Texto como hospital, travel, abroad, offline o federal resta puntos.", status3: "Actividad reciente como online o active suma puntos, mientras offline resta.",
            tagEffects: "Efectos de etiquetas", tag1: "priority y dangerous anaden un gran impulso.", tag2: "watch anade un pequeno impulso.", tag3: "med anade un leve impulso.", tag4: "inactive y easy reducen la puntuacion.", tag5: "Los objetivos de revancha tambien llevan la etiqueta revenge.",
            finalNumber: "Como leer el numero final", final1: "80 a 99 suele significar prioridad alta.", final2: "45 a 79 es prioridad media.", final3: "Por debajo de 45 suele significar menor urgencia.", final4: "La puntuacion es solo una guia. Tus notas, etiquetas y estrategia final siguen mandando.",
            warTarget: "Objetivo de guerra", revengeTarget: "Objetivo de revancha", factionUnknown: "Faccion desconocida", levelUnknown: "Nivel desconocido", lifeUnknown: "Vida desconocida", statusLabel: "Estado", locationLabel: "Ubicacion", hospLabel: "Hosp.", lastActionLabel: "Ultima actividad", fightLog: "Registro", battleMemory: "Memoria de combate", seenLabel: "Visto", updatedLabel: "Actualizado", apiRefreshed: "API actualizada", contexts: "Contextos", noSourcePages: "Sin paginas fuente", tagsPlaceholder: "tags: priority, easy", manualStatusPlaceholder: "estado manual", notesPlaceholder: "notas", win: "Victoria", loss: "Derrota", escape: "Escape", remove: "Quitar", lifeFormat: "Vida {current}/{maximum}", levelFormat: "Nivel {level}", scoreWord: "score", copiedReport: "Informe copiado.", recentFights: "Combates recientes"
        },
        it: {
            languageLabel: "Lingua", sizeLabel: "Dimensione", btnPin: "Fissa", btnUnpin: "Sblocca", btnMobile: "Mobile", btnDesktop: "Desktop", ownerLine: "Proprietario: {owner}", tabWar: "Guerra", tabMatchmaker: "M/M", tabRevenge: "Vendetta", tabInstructions: "Istruzioni",
            btnSync: "Sync", btnRefresh: "Aggiorna", btnLinks: "Link", btnReset: "Reset", btnHide: "Nascondi", btnOpen: "Apri", btnExport: "Esporta", btnImport: "Importa",
            searchPlaceholder: "Cerca nemici o tag", sortScore: "Ordina: punteggio", sortRecent: "Ordina: recente", sortName: "Ordina: nome", sortLevel: "Ordina: livello",
            filterAll: "Tutti", filterPriority: "Priorita", filterDangerous: "Pericoloso", filterEasy: "Facile", filterInactive: "Inattivo",
            noTracked: "Nessun bersaglio tracciato.", noTrackedWar: "Inserisci la tua chiave API limitata e l'ID della fazione nemica, poi premi Sync per caricare la lista guerra.", noTrackedRevenge: "Aggiungi gli ID vendetta sopra e premi Carica vendetta.",
            warDashboard: "Cruscotto guerra", warCopy: "Inserisci la tua chiave API limitata e l'ID della fazione nemica per costruire un pannello guerra con timer ospedale, disponibilita e priorita.", revengeDashboard: "Cruscotto vendetta", revengeCopy: "Carica bersagli di vendetta tramite ID giocatore. I bersagli di vendetta restano sempre evidenziati come azionabili.", revengeInputLabel: "Input ID vendetta",
            apiPlaceholder: "Chiave API limitata", apiSavedPlaceholder: "Chiave API salvata (nascosta)", factionPlaceholder: "ID fazione nemica", revengeIdsPlaceholder: "Inserisci ID vendetta", idHelper: "Inserisci uno o piu ID Torn separati da virgole o spazi.",
            apiRequired: "richiesto", apiRedacted: "nascosta", apiNotSet: "non impostata", apiKey: "Chiave API", revengeIds: "ID vendetta", factionId: "ID fazione",
            syncFaction: "Sync fazione", refreshPlayerData: "Aggiorna giocatori", refreshPageLinks: "Aggiorna link", resetTracker: "Reset tracker", loadRevenge: "Carica vendetta", refreshRevengeData: "Aggiorna vendetta",
            availableNow: "Disponibili", hospitalized: "In ospedale", blocked: "Bloccati", availableSoon: "Disponibili presto", nextHospitalRelease: "Prossima uscita ospedale", nextTargets: "Prossimi bersagli", nextRevengeTargets: "Prossime vendette", allTargets: "Tutti i bersagli", allRevengeTargets: "Tutte le vendette", noImmediateTargets: "Nessun bersaglio immediato identificato.",
            instructionsTitle: "Istruzioni", instructionsCopy: "Questa scheda spiega come viene calcolato il punteggio del tracker.", scoreOverview: "Panoramica punteggio", scoreOverview1: "Ogni bersaglio riceve un punteggio da 0 a 99. Punteggi piu alti indicano bersagli piu azionabili o importanti.", scoreOverview2: "Il tracker combina livello, storico combattimenti, vita, disponibilita, stato, ultima attivita e alcuni tag.",
            baseScoreInputs: "Fattori base", base1: "Il livello aggiunge fino a 45 punti.", base2: "Le sconfitte registrate contro quel bersaglio aggiungono fino a 30 punti.", base3: "Le vittorie registrate contro quel bersaglio sottraggono fino a 24 punti.", base4: "Le escape aggiungono fino a 15 punti.", base5: "La vita attuale aggiunge fino a 8 punti.",
            availabilityEffects: "Effetti disponibilita", avail1: "I bersagli disponibili ricevono un bonus positivo.", avail2: "I bersagli che escono presto dall'ospedale ricevono un bonus minore.", avail3: "Bersagli in ospedale, in prigione, in viaggio o federal perdono punteggio.", avail4: "I bersagli di vendetta ricevono sempre un forte bonus di disponibilita.",
            statusEffects: "Effetti di stato e attivita", status1: "Testi come okay o online aggiungono punti.", status2: "Testi come hospital, travel, abroad, offline o federal tolgono punti.", status3: "Attivita recenti come online o active aggiungono punti, mentre offline ne toglie.",
            tagEffects: "Effetti dei tag", tag1: "priority e dangerous aggiungono un forte bonus.", tag2: "watch aggiunge un piccolo bonus.", tag3: "med aggiunge un leggero bonus.", tag4: "inactive e easy riducono il punteggio.", tag5: "I bersagli di vendetta ricevono anche il tag revenge.",
            finalNumber: "Come leggere il numero finale", final1: "80 a 99 indica di solito priorita alta.", final2: "45 a 79 indica priorita media.", final3: "Sotto 45 indica di solito urgenza minore.", final4: "Il punteggio e solo una guida. Note, tag e strategia finale restano piu importanti.",
            warTarget: "Bersaglio guerra", revengeTarget: "Bersaglio vendetta", factionUnknown: "Fazione sconosciuta", levelUnknown: "Livello sconosciuto", lifeUnknown: "Vita sconosciuta", statusLabel: "Stato", locationLabel: "Posizione", hospLabel: "Osp.", lastActionLabel: "Ultima attivita", fightLog: "Storico", battleMemory: "Memoria battaglia", seenLabel: "Visto", updatedLabel: "Aggiornato", apiRefreshed: "API aggiornata", contexts: "Contesti", noSourcePages: "Nessuna pagina sorgente", tagsPlaceholder: "tag: priority, easy", manualStatusPlaceholder: "stato manuale", notesPlaceholder: "note", win: "Vittoria", loss: "Sconfitta", escape: "Escape", remove: "Rimuovi", lifeFormat: "Vita {current}/{maximum}", levelFormat: "Livello {level}", scoreWord: "score", copiedReport: "Rapporto copiato.", recentFights: "Scontri recenti"
        },
        nl: {
            languageLabel: "Taal", sizeLabel: "Grootte", btnPin: "Vastzetten", btnUnpin: "Losmaken", btnMobile: "Mobiel", btnDesktop: "Desktop", btnOpen: "Openen", btnHide: "Verbergen", btnExport: "Exporteren", btnImport: "Importeren",
            tabWar: "Oorlog", tabMatchmaker: "M/M", tabRevenge: "Wraak", tabFTAB: "FTAB", tabInstructions: "Instructies",
            searchPlaceholder: "Zoek vijanden of tags", sortScore: "Sorteren: score", sortRecent: "Sorteren: recent", sortName: "Sorteren: naam", sortLevel: "Sorteren: level",
            filterAll: "Alles", filterPriority: "Prioriteit", filterDangerous: "Gevaarlijk", filterEasy: "Makkelijk", filterInactive: "Inactief",
            warDashboard: "Oorlogsdashboard", revengeDashboard: "Wraakdashboard", instructionsTitle: "Instructies", ownerLine: "Eigenaar: {owner}",
            noTracked: "Nog geen gevolgde doelen.", noTrackedWar: "Voeg hierboven je beperkte API-sleutel en vijandige factie-ID toe en druk dan op Sync om het huidige oorlogsoverzicht te laden.", noTrackedRevenge: "Voeg hierboven wraak-ID's toe en klik daarna op Wraak laden.",
            apiPlaceholder: "Beperkte API-sleutel", apiSavedPlaceholder: "Opgeslagen API-sleutel (verborgen)", factionPlaceholder: "Vijandige factie-ID", revengeIdsPlaceholder: "Voer wraakspeler-ID('s) in",
            apiKey: "API-sleutel", revengeIds: "Wraak-ID's", factionId: "Factie-ID", syncFaction: "Factie syncen", refreshPlayerData: "Spelerdata verversen", refreshPageLinks: "Paginalinks verversen", resetTracker: "Tracker resetten", loadRevenge: "Wraak laden", refreshRevengeData: "Wraakdata verversen",
            availableNow: "Nu beschikbaar", availableSoon: "Straks beschikbaar", hospitalized: "In ziekenhuis", blocked: "Geblokkeerd", nextTargets: "Volgende doelen", nextRevengeTargets: "Volgende wraakdoelen", allTargets: "Alle doelen", allRevengeTargets: "Alle wraakdoelen", matchmakerCopy: "Deze weergave rangschikt alle gevolgde doelen op Matchmaker-advies, zodat je die laag los van de gewone oorlogs- en wraakdashboards kunt beoordelen.",
            instructionsCopy: "Dit tabblad legt in gewone taal uit hoe de tracker en Matchmaker hun keuzes maken.", scoreOverview: "Score-overzicht", scoreOverview1: "Elk doel krijgt een score van 0 tot 99. Hogere scores wijzen meestal op een belangrijker of directer bruikbaar doel.", scoreOverview2: "De tracker combineert level, recente gevechtshistorie, life, beschikbaarheid, status, laatste actie en tags.",
            baseScoreInputs: "Basiselementen van de score", base1: "Level voegt tot 45 punten toe. Hogere levels scoren hoger.", base2: "Geregistreerde verliezen tegen dat doel tellen op tot 30 punten.", base3: "Geregistreerde overwinningen op dat doel trekken tot 24 punten af.", base4: "Ontsnappingen kunnen tot 15 punten toevoegen.", base5: "Huidige life voegt tot 8 punten toe.",
            availabilityEffects: "Beschikbaarheidseffecten", avail1: "Beschikbare doelen krijgen een positieve boost.", avail2: "Doelen die bijna uit het ziekenhuis komen krijgen een kleinere boost.", avail3: "Doelen in ziekenhuis, cel, op reis of fed verliezen score omdat ze minder direct aan te vallen zijn.", avail4: "Wraakdoelen krijgen altijd een stevige beschikbaarheidsboost zodat ze zichtbaar blijven.",
            statusEffects: "Status en activiteit", status1: "Statuswoorden zoals okay of online leveren punten op.", status2: "Statuswoorden zoals hospital, travel, abroad, offline of federal verlagen de score.", status3: "Recente activiteit zoals online of active geeft een plus; offline geeft een min.",
            tagEffects: "Tageffecten", tag1: "priority en dangerous geven een grote bonus.", tag2: "watch geeft een kleine bonus.", tag3: "med geeft een lichte bonus.", tag4: "inactive en easy verlagen de score.", tag5: "Wraakdoelen krijgen ook de revenge-tag wanneer ze worden toegevoegd.",
            finalNumber: "Het eindgetal lezen", final1: "80 tot 99 betekent meestal hoge prioriteit.", final2: "45 tot 79 is middenprioriteit.", final3: "Onder 45 betekent meestal lagere urgentie of minder directe bruikbaarheid.", final4: "De score is alleen een gids. Jouw notities, tags en tactiek blijven leidend.",
            fightLog: "Gevechtslog", battleMemory: "Gevechtsgeheugen", lastActionLabel: "Laatste actie", statusLabel: "Status", locationLabel: "Locatie", hospLabel: "Hosp.", seenLabel: "Gezien", updatedLabel: "Bijgewerkt", apiRefreshed: "API ververst", contexts: "Contexten",
            tagsPlaceholder: "tags: priority, easy", manualStatusPlaceholder: "handmatige status", notesPlaceholder: "notities", win: "Winst", loss: "Verlies", escape: "Ontsnapt", remove: "Verwijderen", copiedProfile: "Profiellink gekopieerd.", copiedAttack: "Aanvallink gekopieerd.", copiedReport: "Rapport gekopieerd.", scoreWord: "score", recentFights: "Recente gevechten"
        },
        id: {
            languageLabel: "Bahasa", sizeLabel: "Ukuran", btnPin: "Sematkan", btnUnpin: "Lepas", btnMobile: "Ponsel", btnDesktop: "Desktop", btnOpen: "Buka", btnHide: "Sembunyikan", btnExport: "Ekspor", btnImport: "Impor",
            tabWar: "Perang", tabMatchmaker: "M/M", tabRevenge: "Balas", tabFTAB: "FTAB", tabInstructions: "Petunjuk",
            searchPlaceholder: "Cari musuh atau tag", sortScore: "Urut: skor", sortRecent: "Urut: terbaru", sortName: "Urut: nama", sortLevel: "Urut: level",
            filterAll: "Semua", filterPriority: "Prioritas", filterDangerous: "Berbahaya", filterEasy: "Mudah", filterInactive: "Tidak aktif",
            warDashboard: "Dasbor Perang", revengeDashboard: "Dasbor Balas", instructionsTitle: "Petunjuk", ownerLine: "Pemilik: {owner}",
            noTracked: "Belum ada target yang dilacak.", noTrackedWar: "Tambahkan YATA API key dan ID faction musuh di atas, lalu tekan Sync untuk memuat roster perang saat ini.", noTrackedRevenge: "Tambahkan ID balas di atas, lalu klik Muat balas.",
            apiPlaceholder: "YATA API key", apiSavedPlaceholder: "YATA API key tersimpan (disembunyikan)", factionPlaceholder: "ID faction musuh", revengeIdsPlaceholder: "Masukkan ID pemain balas",
            apiKey: "API key", revengeIds: "ID balas", factionId: "ID faction", syncFaction: "Sinkron faction", refreshPlayerData: "Segarkan data pemain", refreshPageLinks: "Segarkan tautan halaman", resetTracker: "Reset tracker", loadRevenge: "Muat balas", refreshRevengeData: "Segarkan data balas",
            availableNow: "Siap sekarang", availableSoon: "Segera siap", hospitalized: "Di rumah sakit", blocked: "Diblokir", nextTargets: "Target berikutnya", nextRevengeTargets: "Balas berikutnya", allTargets: "Semua target", allRevengeTargets: "Semua target balas", matchmakerCopy: "Tampilan ini mengurutkan semua target terlacak berdasarkan saran Matchmaker supaya kamu bisa menilai lapisan itu terpisah dari dasbor perang dan balas biasa.",
            instructionsCopy: "Tab ini menjelaskan dengan bahasa yang mudah bagaimana tracker dan Matchmaker membuat penilaian.", scoreOverview: "Ringkasan skor", scoreOverview1: "Setiap target mendapat skor 0 sampai 99. Skor lebih tinggi biasanya berarti target itu lebih penting atau lebih siap diserang.", scoreOverview2: "Tracker menggabungkan level, riwayat pertarungan terbaru, life, ketersediaan, status, last action, dan tag.",
            baseScoreInputs: "Dasar perhitungan skor", base1: "Level menambah sampai 45 poin. Level lebih tinggi berarti skor lebih tinggi.", base2: "Kekalahan tercatat melawan target itu menambah sampai 30 poin.", base3: "Kemenangan tercatat atas target itu mengurangi sampai 24 poin.", base4: "Hasil kabur bisa menambah sampai 15 poin.", base5: "Life saat ini menambah sampai 8 poin.",
            availabilityEffects: "Pengaruh ketersediaan", avail1: "Target yang tersedia mendapat dorongan positif.", avail2: "Target yang sebentar lagi keluar rumah sakit mendapat dorongan lebih kecil.", avail3: "Target di rumah sakit, penjara, bepergian, atau federal kehilangan skor karena kurang siap diserang.", avail4: "Target balas selalu mendapat dorongan ketersediaan yang kuat supaya tetap terlihat.",
            statusEffects: "Status dan aktivitas", status1: "Teks status seperti okay atau online menambah poin.", status2: "Teks status seperti hospital, travel, abroad, offline, atau federal mengurangi poin.", status3: "Aktivitas terbaru seperti online atau active memberi bonus, sedangkan offline memberi pengurang.",
            tagEffects: "Pengaruh tag", tag1: "priority dan dangerous memberi bonus besar.", tag2: "watch memberi bonus kecil.", tag3: "med memberi bonus ringan.", tag4: "inactive dan easy menurunkan skor.", tag5: "Target balas juga mendapat tag revenge saat dimasukkan.",
            finalNumber: "Membaca angka akhir", final1: "80 sampai 99 biasanya berarti prioritas tinggi.", final2: "45 sampai 79 berarti prioritas sedang.", final3: "Di bawah 45 biasanya berarti urgensi lebih rendah atau kurang cocok saat ini.", final4: "Skor hanyalah panduan. Catatan, tag, dan strategi kamu tetap lebih penting.",
            fightLog: "Log pertarungan", battleMemory: "Memori pertarungan", lastActionLabel: "Aksi terakhir", statusLabel: "Status", locationLabel: "Lokasi", hospLabel: "Hosp.", seenLabel: "Terlihat", updatedLabel: "Diperbarui", apiRefreshed: "API diperbarui", contexts: "Konteks",
            tagsPlaceholder: "tag: priority, easy", manualStatusPlaceholder: "status manual", notesPlaceholder: "catatan", win: "Menang", loss: "Kalah", escape: "Kabur", remove: "Hapus", copiedProfile: "Tautan profil disalin.", copiedAttack: "Tautan serang disalin.", copiedReport: "Laporan disalin.", scoreWord: "skor", recentFights: "Pertarungan terbaru"
        },
        gd: {
            languageLabel: "Canan", sizeLabel: "Meud", btnPin: "Prionnaich", btnUnpin: "Neo-phrionnaich", btnMobile: "Fòn", btnDesktop: "Deasg", btnOpen: "Fosgail", btnHide: "Falaich", btnExport: "Às-phortaich", btnImport: "Ion-phortaich",
            tabWar: "Cogadh", tabMatchmaker: "M/M", tabRevenge: "Dìoghaltas", tabFTAB: "FTAB", tabInstructions: "Stiùireadh",
            searchPlaceholder: "Lorg nàimhdean no tagaichean", sortScore: "Seòrsaich: sgòr", sortRecent: "Seòrsaich: o chionn ghoirid", sortName: "Seòrsaich: ainm", sortLevel: "Seòrsaich: ìre",
            filterAll: "Uile", filterPriority: "Prìomhachas", filterDangerous: "Cunnartach", filterEasy: "Furasta", filterInactive: "Neo-ghnìomhach",
            warDashboard: "Deas-bhòrd Cogaidh", revengeDashboard: "Deas-bhòrd Dìoghaltais", instructionsTitle: "Stiùireadh", ownerLine: "Sealbhadair: {owner}",
            noTracked: "Chan eil targaidean gan tracadh fhathast.", noTrackedWar: "Cuir a-steach an iuchair API chuingealaichte agad agus ID na faction nàmhaid gu h-àrd, agus brùth Sync gus roster a' chogaidh a luchdadh.", noTrackedRevenge: "Cuir a-steach IDan dìoghaltais gu h-àrd, agus cliog Luchdaich dìoghaltas.",
            apiPlaceholder: "Iuchair API chuingealaichte", apiSavedPlaceholder: "Iuchair API sàbhailte (falaichte)", factionPlaceholder: "ID faction nàmhaid", revengeIdsPlaceholder: "Cuir a-steach IDan cluicheadair dìoghaltais",
            apiKey: "Iuchair API", revengeIds: "IDan dìoghaltais", factionId: "ID faction", syncFaction: "Sync faction", refreshPlayerData: "Ùraich dàta cluicheadair", refreshPageLinks: "Ùraich ceanglaichean duilleige", resetTracker: "Ath-shuidhich tracker", loadRevenge: "Luchdaich dìoghaltas", refreshRevengeData: "Ùraich dàta dìoghaltais",
            availableNow: "Ri fhaighinn a-nis", availableSoon: "Ri fhaighinn a dh'aithghearr", hospitalized: "San ospadal", blocked: "Bacte", nextTargets: "Na h-ath thargaidean", nextRevengeTargets: "An ath dhìoghaltasan", allTargets: "A h-uile targaid", allRevengeTargets: "A h-uile dìoghaltas", matchmakerCopy: "Tha an sealladh seo a' rangachadh gach targaid a tha air a thracadh a rèir molaidh Matchmaker gus am faic thu an còmhdach sin air leth bhon deas-bhòrd cogaidh is dìoghaltais àbhaisteach.",
            instructionsCopy: "Tha an taba seo a' mìneachadh ann an cainnt shìmplidh mar a tha an tracker agus Matchmaker a' dèanamh an co-dhùnaidhean.", scoreOverview: "Sealladh iomlan air an sgòr", scoreOverview1: "Gheibh gach targaid sgòr eadar 0 agus 99. Mar as àirde an sgòr, mar as cudromaiche no nas fhasa a dh'fhaodadh an targaid a bhith.", scoreOverview2: "Bidh an tracker a' cothlamadh ìre, eachdraidh sabaid o chionn ghoirid, beatha, ruigsinneachd, inbhe, gnìomh mu dheireadh agus cuid de thagaichean.",
            baseScoreInputs: "Bunaitean na sgòra", base1: "Faodaidh ìre suas ri 45 puingean a chur ris. Bidh ìrean nas àirde a' faighinn sgòr nas àirde.", base2: "Faodaidh callaichean clàraichte an aghaidh na targaid suas ri 30 puingean a chur ris.", base3: "Faodaidh buaidhean clàraichte an aghaidh na targaid suas ri 24 puingean a thoirt air falbh.", base4: "Faodaidh teicheadh suas ri 15 puingean a chur ris.", base5: "Faodaidh beatha làithreach suas ri 8 puingean a chur ris.",
            availabilityEffects: "Buaidhean ruigsinneachd", avail1: "Gheibh targaidean a tha ri fhaighinn àrdachadh math.", avail2: "Gheibh targaidean a tha gu bhith a-mach às an ospadal àrdachadh nas lugha.", avail3: "Caillidh targaidean san ospadal, sa phrìosan, a' siubhal no ann am federal sgòr oir chan eil iad cho furasta an bualadh.", avail4: "Bidh targaidean dìoghaltais an-còmhnaidh a' faighinn àrdachadh làidir gus am fuirich iad follaiseach.",
            statusEffects: "Inbhe agus gnìomhachd", status1: "Bheir faclan inbhe mar okay no online puingean a-steach.", status2: "Bheir faclan mar hospital, travel, abroad, offline no federal puingean air falbh.", status3: "Bheir gnìomhachd o chionn ghoirid mar online no active buannachd, agus bheir offline ana-cothrom.",
            tagEffects: "Buaidhean thagaichean", tag1: "Bheir priority agus dangerous bònas mòr.", tag2: "Bheir watch bònas beag.", tag3: "Bheir med bònas aotrom.", tag4: "Lùghdaichidh inactive agus easy an sgòr.", tag5: "Gheibh targaidean dìoghaltais an taga revenge cuideachd nuair a thèid an cur ris.",
            finalNumber: "A' leughadh an àireimh dheireannaich", final1: "Mar as trice tha 80 gu 99 a' ciallachadh prìomhachas àrd.", final2: "Tha 45 gu 79 na phrìomhachas meadhanach.", final3: "Tha fo 45 mar as trice a' ciallachadh nas lugha de dh'èiginn no nas lugha de chothrom sa bhad.", final4: "Chan eil an sgòr ach mar stiùireadh. Bu chòir do na notaichean, na tagaichean agus an ro-innleachd agad a bhith os cionn sin fhathast.",
            fightLog: "Log sabaid", battleMemory: "Cuimhne sabaid", lastActionLabel: "Gnìomh mu dheireadh", statusLabel: "Inbhe", locationLabel: "Àite", hospLabel: "Osp.", seenLabel: "Air fhaicinn", updatedLabel: "Air ùrachadh", apiRefreshed: "API air ùrachadh", contexts: "Co-theacsan",
            tagsPlaceholder: "tagaichean: priority, easy", manualStatusPlaceholder: "inbhe làimhe", notesPlaceholder: "notaichean", win: "Buaidh", loss: "Call", escape: "Theich", remove: "Thoir air falbh", copiedProfile: "Chaidh ceangal na pròifil a chopaigeadh.", copiedAttack: "Chaidh ceangal an ionnsaigh a chopaigeadh.", copiedReport: "Chaidh aithisg a chopaigeadh.", scoreWord: "sgòr", recentFights: "Sabaidean o chionn ghoirid"
        },
        cockney: {
            languageLabel: "Lingo", sizeLabel: "Size, mate", btnPin: "Stick it", btnUnpin: "Unstick it", btnMobile: "Mobile", btnDesktop: "Desktop", btnOpen: "Open it", btnHide: "Hide it", btnExport: "Ship out", btnImport: "Bring in",
            tabWar: "War", tabMatchmaker: "M/M", tabRevenge: "Get-even", tabFTAB: "FTAB", tabInstructions: "Wot's this then",
            searchPlaceholder: "Find geezers or tags", sortScore: "Sort: score", sortRecent: "Sort: latest", sortName: "Sort: name", sortLevel: "Sort: level",
            filterAll: "All of 'em", filterPriority: "Top lot", filterDangerous: "Dodgy", filterEasy: "Soft", filterInactive: "Dozin'",
            warDashboard: "War board", revengeDashboard: "Get-even board", instructionsTitle: "How it works, guv", ownerLine: "Boss: {owner}",
            noTracked: "No villains on the list yet.", noTrackedWar: "Chuck in your YATA API key and the enemy faction ID up top, then hit Sync to pull the war lot in.", noTrackedRevenge: "Chuck in revenge IDs up top, then tap Load revenge.",
            apiPlaceholder: "YATA API key", apiSavedPlaceholder: "Saved YATA API key (kept schtum)", factionPlaceholder: "Enemy faction ID", revengeIdsPlaceholder: "Drop revenge player IDs in 'ere",
            apiKey: "API key", revengeIds: "Revenge IDs", factionId: "Faction ID", syncFaction: "Sync faction", refreshPlayerData: "Freshen player data", refreshPageLinks: "Freshen page links", resetTracker: "Reset the lot", loadRevenge: "Load revenge", refreshRevengeData: "Freshen revenge data",
            availableNow: "Ready right now", availableSoon: "Ready in a tick", hospitalized: "In the hospital, innit", blocked: "No go", nextTargets: "Next lot", nextRevengeTargets: "Next get-even jobs", allTargets: "All targets", allRevengeTargets: "All revenge jobs", matchmakerCopy: "This little view lines up every tracked mug by Matchmaker advice so you can judge the brains of the thing away from the usual war and get-even boards.",
            instructionsCopy: "This bit tells ya, plain and simple, how the tracker and Matchmaker decide who's worth a proper look.", scoreOverview: "Score rundown", scoreOverview1: "Every target gets a score from 0 to 99. Bigger number usually means more useful, more urgent, or more smackable.", scoreOverview2: "The tracker mixes level, recent fight history, life, availability, status, last action, and tags.",
            baseScoreInputs: "Wot builds the score", base1: "Level can bung on up to 45 points. Bigger level, bigger score.", base2: "Recorded losses against that geezer can add up to 30 points.", base3: "Recorded wins over that geezer can knock off up to 24 points.", base4: "Escapes can stick on up to 15 points.", base5: "Current life can add up to 8 points.",
            availabilityEffects: "Availability bits", avail1: "Targets ready to go get a nice boost.", avail2: "Targets nearly out the hospital get a smaller bump.", avail3: "Targets in hospital, nick, travel, or federal lose points 'cause they're less useful right this second.", avail4: "Get-even targets always get a chunky boost so they stay in sight.",
            statusEffects: "Status and activity bits", status1: "Stuff like okay or online adds points.", status2: "Stuff like hospital, travel, abroad, offline, or federal knocks points off.", status3: "Recent activity like online or active helps; offline don't.",
            tagEffects: "Tag bits", tag1: "priority and dangerous give a big shove upward.", tag2: "watch gives a little shove.", tag3: "med gives a tiny nudge.", tag4: "inactive and easy drag the score down.", tag5: "Get-even targets also pick up the revenge tag when added.",
            finalNumber: "Reading the final number", final1: "80 to 99 usually means proper high priority.", final2: "45 to 79 means middling priority.", final3: "Below 45 usually means less urgent or not much use right now.", final4: "The score's just a guide, mate. Your notes, tags, and actual plan still call the shots.",
            fightLog: "Fight log", battleMemory: "Dust-up memory", lastActionLabel: "Last seen", statusLabel: "Status", locationLabel: "Place", hospLabel: "Hosp.", seenLabel: "Seen", updatedLabel: "Updated", apiRefreshed: "API freshened up", contexts: "Where from", 
            tagsPlaceholder: "tags: priority, easy", manualStatusPlaceholder: "manual status, mate", notesPlaceholder: "notes, son", win: "Win", loss: "Loss", escape: "Legged it", remove: "Bin it", copiedProfile: "Profile link copied, nice one.", copiedAttack: "Attack link copied, sorted.", copiedReport: "Report copied, lovely.", scoreWord: "score", recentFights: "Recent scraps"
        }
    };

    const state = loadState();
    let panel;
    let panelBody;
    let panelSearch;
    let apiKeyInput;
    let sortSelect;
    let filterSelect;
    let languageSelect;
    let scaleInput;
    let factionIdInput;
    let revengeIdInput;
    let scanTimer;
    let clockTimer;
    let observer;
    let apiQueueTimer;
    let observerTimer;
    let suppressObserver = false;
    let dragState = null;
    let chainMinutesInput;
    let ftabBulkInput;
    let selfProfileInflight = false;
    let panelScrollUnlockTimer = null;
    let pendingFrozenPanelRefresh = false;
    const availabilityStateById = new Map();
    const travelStateById = new Map();
    const travelAlertStageById = new Map();
    const chainAlertStages = new Set();
    const pendingApiIds = new Set();
    const inflightApiIds = new Set();
    let ftabAjaxHookInstalled = false;
    let ftabFactionObserver = null;
    let ftabCityRootObserver = null;
    let ftabCityPopupObserver = null;
    let ftabProfileWarningState = { lastProfileId: "", lastFactionId: "", applied: false };
    let battleCaptureState = { targetId: "", signature: "" };
    const TWET_GLOBAL_KEY = "__TORN_WAR_TRACKER_ACTIVE__";

    installFtabAjaxHooks();

    if (document.readyState === "loading") {
        document.addEventListener("DOMContentLoaded", boot, { once: true });
    } else {
        boot();
    }

    function boot() {
        if (state.ui.activeTab === "matchmaker") {
            state.ui.activeTab = "war";
            saveState();
        }
        if (window[TWET_GLOBAL_KEY]) {
            removeDuplicatePanels();
            return;
        }
        window[TWET_GLOBAL_KEY] = true;
        removeDuplicatePanels();
        ensureStyles();
        createPanel();
        scanPage();
        refreshPanel();
        installObservers();
        installKeyboardShortcuts();
        scanTimer = window.setInterval(scanPage, PAGE_SCAN_INTERVAL_MS);
        clockTimer = window.setInterval(() => {
            checkAlertsAndReminders();
            if (!isEditingPanel() && !state.ui.collapsed) {
                if (shouldFreezeLivePanelRefresh()) {
                    pendingFrozenPanelRefresh = true;
                } else {
                    refreshPanel();
                }
            }
        }, 5000);
        apiQueueTimer = window.setInterval(flushApiQueue, 3000);
    }

    function ensureStyles() {
        const css = `
            #matchmaker-panel {
                position: fixed;
                top: 80px;
                right: 16px;
                width: 420px;
                max-height: calc(100vh - 112px);
                z-index: 999999;
                display: flex;
                flex-direction: column;
                background: linear-gradient(180deg, rgba(11, 17, 28, 0.98), rgba(15, 23, 42, 0.98));
                color: #edf2f7;
                border: 1px solid rgba(120, 149, 203, 0.26);
                border-radius: 20px;
                box-shadow: 0 26px 64px rgba(0, 0, 0, 0.4);
                overflow: hidden;
                font: 12px/1.5 "Segoe UI Variable Text", "Trebuchet MS", "Segoe UI", Arial, sans-serif;
                backdrop-filter: blur(8px);
            }
            #matchmaker-panel.twet-panel-collapsed {
                width: 72px;
                max-height: 44px;
                border-radius: 12px;
                overflow: hidden;
            }
            #matchmaker-panel.twet-panel-mobile.twet-panel-collapsed {
                width: 56px;
                max-height: 34px;
                border-radius: 10px;
            }
            #matchmaker-panel.twet-panel-mobile {
                width: min(92vw, 360px);
                top: 12px;
                right: 12px;
                max-height: calc(100vh - 24px);
                font-size: 11px;
            }
            #matchmaker-panel.twet-panel-mobile .twet-toolbar,
            #matchmaker-panel.twet-panel-mobile .twet-summary-grid,
            #matchmaker-panel.twet-panel-mobile .twet-settings-grid {
                grid-template-columns: 1fr;
            }
            #matchmaker-panel.twet-panel-mobile .twet-actions,
            #matchmaker-panel.twet-panel-mobile .twet-controls {
                gap: 6px;
            }
            #matchmaker-panel.twet-panel-mobile .twet-controls {
                grid-template-columns: repeat(2, minmax(0, 1fr));
            }
            #matchmaker-panel.twet-panel-mobile .twet-mobile-control-toggles {
                display: grid;
                width: 100%;
                gap: 6px;
                grid-template-columns: repeat(2, minmax(0, 1fr));
            }
            #matchmaker-panel.twet-panel-mobile .twet-button {
                padding: 0 10px;
            }
            #matchmaker-panel.twet-panel-mobile .twet-row {
                align-items: flex-start;
                flex-direction: column;
            }
            #matchmaker-panel * {
                box-sizing: border-box;
            }
            .twet-header {
                display: flex;
                align-items: center;
                justify-content: flex-start;
                gap: 10px;
                flex-wrap: wrap;
                padding: 12px 14px 10px;
                background: linear-gradient(135deg, #162133, #0f1724);
                border-bottom: 1px solid rgba(120, 149, 203, 0.16);
            }
            #matchmaker-panel.twet-panel-pinned .twet-header {
                cursor: move;
            }
            #matchmaker-panel.twet-panel-collapsed .twet-header {
                padding: 8px 10px;
                min-height: 44px;
                align-items: center;
                justify-content: center;
                border-bottom: 0;
                cursor: pointer;
            }
            #matchmaker-panel.twet-panel-mobile.twet-panel-collapsed .twet-header {
                padding: 5px 6px;
                min-height: 34px;
            }
            .twet-title {
                font-weight: 800;
                letter-spacing: 0.02em;
                font-size: 14px;
            }
            #matchmaker-panel.twet-panel-collapsed .twet-title {
                font-size: 12px;
                letter-spacing: 0.08em;
                text-align: center;
            }
            #matchmaker-panel.twet-panel-mobile.twet-panel-collapsed .twet-title {
                font-size: 10px;
                letter-spacing: 0.04em;
            }
            .twet-title-wrap {
                display: flex;
                flex-direction: column;
                gap: 8px;
                min-width: 0;
                flex: 1 1 100%;
            }
            #matchmaker-panel.twet-panel-collapsed .twet-title-wrap {
                flex: 0 0 auto;
            }
            .twet-tabs {
                display: flex;
                gap: 6px;
                flex-wrap: wrap;
                padding: 2px 2px 0;
                border-bottom: 1px solid rgba(138, 166, 217, 0.14);
            }
            .twet-tab {
                height: 32px;
                padding: 0 10px;
                border-radius: 10px 10px 0 0;
                border: 1px solid rgba(138, 166, 217, 0.24);
                border-bottom: 0;
                background: linear-gradient(180deg, rgba(255, 255, 255, 0.08), rgba(255, 255, 255, 0.02));
                color: #cbd5e1;
                font-size: 11px;
                cursor: pointer;
                transition: background 140ms ease, border-color 140ms ease, color 140ms ease;
                box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04);
            }
            .twet-tab.active {
                background: linear-gradient(180deg, rgba(56, 189, 248, 0.22), rgba(37, 99, 235, 0.14));
                color: #f8fafc;
                border-color: rgba(125, 211, 252, 0.42);
                transform: translateY(1px);
            }
            .twet-controls,
            .twet-toolbar {
                display: flex;
                gap: 10px;
                align-items: center;
                flex-wrap: wrap;
            }
            .twet-controls {
                display: grid;
                grid-template-columns: repeat(4, minmax(0, 1fr));
                width: 100%;
                gap: 8px;
                flex: 1 1 100%;
            }
            .twet-mobile-control-toggles {
                display: grid;
                width: 100%;
                gap: 8px;
                grid-template-columns: repeat(2, minmax(0, 1fr));
            }
            .twet-controls .twet-button {
                width: 100%;
                justify-content: center;
            }
            #matchmaker-panel.twet-panel-collapsed .twet-tabs,
            #matchmaker-panel.twet-panel-collapsed .twet-controls,
            #matchmaker-panel.twet-panel-collapsed .twet-toolbar,
            #matchmaker-panel.twet-panel-collapsed .twet-body {
                display: none !important;
            }
            .twet-toolbar {
                display: grid;
                grid-template-columns: 1fr 1fr;
                padding: 10px 14px;
                border-bottom: 1px solid rgba(120, 149, 203, 0.12);
                gap: 8px;
            }
            .twet-toolbar-wide {
                grid-column: 1 / -1;
            }
            .twet-scale-control {
                display: flex;
                align-items: center;
                gap: 8px;
                color: #cbd5e1;
                min-width: 0;
                padding: 0 2px;
            }
            .twet-scale-control input[type="range"] {
                width: 100%;
                margin: 0;
            }
            .twet-scale-value {
                min-width: 42px;
                text-align: right;
                color: #94a3b8;
                font-size: 11px;
            }
            .twet-panel input,
            .twet-panel select,
            .twet-panel textarea,
            .twet-panel button {
                font: inherit;
            }
            .twet-input,
            .twet-select,
            .twet-button,
            .twet-textarea {
                border-radius: 12px;
                border: 1px solid rgba(138, 166, 217, 0.22);
                background: rgba(255, 255, 255, 0.06);
                color: #edf2f7;
            }
            .twet-input,
            .twet-select {
                height: 34px;
                padding: 0 11px;
                width: 100%;
            }
            .twet-select option {
                color: #111827;
                background: #f8fafc;
            }
            .twet-textarea {
                width: 100%;
                min-height: 64px;
                padding: 9px 11px;
                resize: vertical;
            }
            .twet-button {
                min-height: 34px;
                padding: 0 12px;
                cursor: pointer;
                font-weight: 600;
                letter-spacing: 0.01em;
                transition: background 140ms ease, border-color 140ms ease, transform 140ms ease;
            }
            .twet-button:hover {
                background: rgba(255, 255, 255, 0.12);
                border-color: rgba(148, 180, 235, 0.34);
                transform: translateY(-1px);
            }
            .twet-button.ftab-master-on {
                background: rgba(34, 197, 94, 0.18);
                border-color: rgba(34, 197, 94, 0.42);
                color: #dcfce7;
            }
            .twet-button.ftab-master-off {
                background: rgba(239, 68, 68, 0.16);
                border-color: rgba(239, 68, 68, 0.34);
                color: #fee2e2;
            }
            .twet-body {
                flex: 1 1 auto;
                min-height: 0;
                overflow: auto;
                max-height: none;
                padding: 12px 14px 28px;
                scroll-padding-bottom: 24px;
            }
            .twet-card {
                padding: 12px;
                margin-bottom: 10px;
                border-radius: 16px;
                background: rgba(255, 255, 255, 0.05);
                border: 1px solid rgba(138, 166, 217, 0.14);
            }
            .twet-row {
                display: flex;
                align-items: center;
                justify-content: space-between;
                gap: 10px;
            }
            .twet-name-wrap {
                display: flex;
                align-items: center;
                gap: 8px;
                min-width: 0;
            }
            .twet-name {
                font-weight: 700;
                color: #ffffff;
                text-decoration: none;
                min-width: 0;
                overflow: hidden;
                text-overflow: ellipsis;
                white-space: nowrap;
            }
            .twet-copy-icon {
                width: 28px;
                height: 28px;
                display: inline-flex;
                align-items: center;
                justify-content: center;
                border-radius: 8px;
                border: 1px solid rgba(148, 163, 184, 0.3);
                background: rgba(255, 255, 255, 0.04);
                color: #e2e8f0;
                cursor: pointer;
                flex: 0 0 auto;
                padding: 0;
                font-size: 14px;
                line-height: 1;
            }
            .twet-copy-icon:hover {
                background: rgba(59, 130, 246, 0.16);
                border-color: rgba(96, 165, 250, 0.42);
            }
            .twet-card-focused {
                border-color: rgba(250, 204, 21, 0.55);
                box-shadow: 0 0 0 1px rgba(250, 204, 21, 0.18) inset;
            }
            .twet-meta,
            .twet-contexts,
            .twet-stats {
                margin-top: 7px;
                color: #cbd5e0;
            }
            .twet-pill-row {
                display: flex;
                flex-wrap: wrap;
                gap: 6px;
                margin-top: 10px;
            }
            .twet-pill {
                display: inline-flex;
                align-items: center;
                gap: 4px;
                padding: 3px 9px;
                border-radius: 999px;
                background: rgba(77, 103, 149, 0.28);
                color: #dbeafe;
            }
            .twet-score {
                min-width: 52px;
                text-align: center;
                padding: 4px 9px;
                border-radius: 999px;
                font-weight: 700;
                background: linear-gradient(135deg, #065f46, #047857);
                color: #ecfdf5;
            }
            .twet-score.high {
                background: linear-gradient(135deg, #9a3412, #dc2626);
                color: #fff7ed;
            }
            .twet-score.mid {
                background: linear-gradient(135deg, #92400e, #d97706);
                color: #fff7ed;
            }
            .twet-matchmaker {
                display: flex;
                align-items: center;
                justify-content: space-between;
                gap: 10px;
                margin-top: 10px;
                padding: 10px 12px;
                border-radius: 12px;
                background: rgba(255, 255, 255, 0.04);
                border: 1px solid rgba(138, 166, 217, 0.14);
            }
            .twet-matchmaker-copy {
                min-width: 0;
            }
            .twet-matchmaker-title {
                color: #f8fafc;
                font-weight: 700;
            }
            .twet-matchmaker-meta {
                margin-top: 4px;
                color: #cbd5e1;
                font-size: 11px;
                line-height: 1.35;
            }
            .twet-threat-row {
                display: flex;
                flex-wrap: wrap;
                gap: 6px;
                margin-top: 8px;
            }
            .twet-threat-pill {
                display: inline-flex;
                align-items: center;
                padding: 3px 8px;
                border-radius: 999px;
                background: rgba(59, 130, 246, 0.16);
                border: 1px solid rgba(96, 165, 250, 0.2);
                color: #dbeafe;
                font-size: 10px;
                line-height: 1.2;
                white-space: nowrap;
            }
            .twet-matchmaker-badge {
                flex: 0 0 auto;
                min-width: 92px;
                text-align: center;
                padding: 6px 10px;
                border-radius: 999px;
                font-size: 11px;
                font-weight: 800;
                letter-spacing: 0.05em;
                text-transform: uppercase;
                color: #f8fafc;
                background: linear-gradient(135deg, #475569, #334155);
            }
            .twet-matchmaker-badge.greenlight {
                background: linear-gradient(135deg, #166534, #15803d);
            }
            .twet-matchmaker-badge.playable {
                background: linear-gradient(135deg, #0369a1, #0284c7);
            }
            .twet-matchmaker-badge.caution {
                background: linear-gradient(135deg, #b45309, #d97706);
            }
            .twet-matchmaker-badge.avoid {
                background: linear-gradient(135deg, #9f1239, #be123c);
            }
            .twet-matchmaker-badge.blocked,
            .twet-matchmaker-badge.protected {
                background: linear-gradient(135deg, #4b5563, #374151);
            }
            .twet-actions {
                display: flex;
                flex-wrap: wrap;
                gap: 8px;
                margin-top: 12px;
            }
            .twet-summary-grid {
                display: grid;
                grid-template-columns: repeat(2, minmax(0, 1fr));
                gap: 10px;
                margin-bottom: 12px;
            }
            .twet-hero {
                padding: 14px;
                margin-bottom: 10px;
                border-radius: 16px;
                background: linear-gradient(135deg, rgba(30, 41, 59, 0.92), rgba(15, 23, 42, 0.96));
                border: 1px solid rgba(125, 211, 252, 0.18);
            }
            .twet-hero.twet-hero-revenge {
                background: linear-gradient(135deg, rgba(88, 28, 135, 0.92), rgba(127, 29, 29, 0.96));
                border-color: rgba(244, 114, 182, 0.28);
                box-shadow: 0 10px 24px rgba(88, 28, 135, 0.18);
            }
            .twet-summary-grid.twet-summary-grid-compact {
                margin-bottom: 10px;
            }
            .twet-shelf-grid {
                display: grid;
                grid-template-columns: repeat(3, minmax(0, 1fr));
                gap: 8px;
                margin-bottom: 10px;
            }
            #matchmaker-panel.twet-panel-mobile .twet-shelf-grid {
                grid-template-columns: 1fr;
            }
            .twet-shelf-card {
                padding: 10px;
                border-radius: 14px;
                border: 1px solid rgba(138, 166, 217, 0.14);
                background: rgba(255, 255, 255, 0.04);
                min-height: 0;
            }
            .twet-shelf-card .twet-section-title {
                margin-bottom: 8px;
            }
            .twet-shelf-card .twet-card {
                padding: 10px;
            }
            .twet-shelf-card .twet-meta {
                font-size: 11px;
                line-height: 1.35;
            }
            .twet-shelf-card .twet-actions {
                margin-top: 8px;
            }
            .twet-shelf-card .twet-button {
                min-width: 0;
                padding: 6px 10px;
            }
            .twet-shelf-card .twet-threat-row {
                gap: 4px;
                margin-top: 6px;
            }
            .twet-shelf-card .twet-threat-pill {
                padding: 3px 7px;
                font-size: 10px;
            }
            .twet-hero-title {
                font-size: 15px;
                font-weight: 700;
                color: #f8fafc;
            }
            .twet-hero-copy {
                margin-top: 6px;
                color: #cbd5e1;
            }
            .twet-settings-grid {
                display: grid;
                grid-template-columns: 1fr 1fr;
                gap: 10px;
                margin-top: 12px;
            }
            .twet-settings-grid .twet-input {
                height: 36px;
            }
            .twet-actions.primary {
                margin-top: 14px;
            }
            .twet-id-helper {
                margin-top: 8px;
                color: #94a3b8;
            }
            .twet-field-label {
                color: #cbd5e1;
                font-size: 11px;
                font-weight: 700;
                margin-top: 12px;
                margin-bottom: 8px;
                text-transform: uppercase;
                letter-spacing: 0.04em;
            }
            .twet-summary-box {
                padding: 10px;
                border-radius: 12px;
                background: rgba(255, 255, 255, 0.04);
                border: 1px solid rgba(138, 166, 217, 0.14);
            }
            .twet-summary-label {
                color: #94a3b8;
                font-size: 11px;
                margin-bottom: 4px;
            }
            .twet-summary-value {
                color: #f8fafc;
                font-size: 18px;
                font-weight: 700;
            }
            .twet-section-title {
                margin: 14px 0 10px;
                color: #cbd5e1;
                font-weight: 700;
                letter-spacing: 0.02em;
            }
            .twet-availability {
                display: inline-flex;
                align-items: center;
                gap: 4px;
                padding: 2px 8px;
                border-radius: 999px;
                font-weight: 700;
            }
            .twet-availability.available {
                background: rgba(22, 163, 74, 0.18);
                color: #86efac;
            }
            .twet-availability.soon {
                background: rgba(217, 119, 6, 0.18);
                color: #fcd34d;
            }
            .twet-availability.blocked {
                background: rgba(220, 38, 38, 0.18);
                color: #fca5a5;
            }
            .twet-muted {
                color: #94a3b8;
            }
            .twet-link-badge {
                display: inline-flex;
                align-items: center;
                gap: 4px;
                margin-left: 6px;
                padding: 1px 6px;
                border-radius: 999px;
                font-size: 11px;
                background: rgba(217, 119, 6, 0.18);
                color: #fcd34d;
                vertical-align: middle;
            }
            .twet-link-badge.high {
                background: rgba(220, 38, 38, 0.18);
                color: #fca5a5;
            }
            .twet-highlight {
                outline: 2px solid rgba(252, 211, 77, 0.65);
                outline-offset: 2px;
                border-radius: 4px;
            }
            .twet-highlight-ready {
                outline-color: rgba(34, 197, 94, 0.78);
                background: rgba(34, 197, 94, 0.08);
            }
            .twet-highlight-soon {
                outline-color: rgba(245, 158, 11, 0.78);
                background: rgba(245, 158, 11, 0.08);
            }
            .twet-mini {
                font-size: 11px;
            }
            .ftab-ali-tag {
                display: inline-flex;
                align-items: center;
                margin-left: 6px;
                padding: 2px 8px;
                border-radius: 999px;
                font-weight: 700;
                font-size: 11px;
                letter-spacing: 0.02em;
                background: linear-gradient(135deg, rgba(14, 116, 144, 0.9), rgba(2, 132, 199, 0.92));
                color: #f0f9ff;
                border: 1px solid rgba(125, 211, 252, 0.45);
                box-shadow: 0 0 0 1px rgba(8, 47, 73, 0.28), 0 4px 12px rgba(2, 132, 199, 0.22);
                vertical-align: middle;
            }
            .ftab-page-tag {
                display: inline-flex;
                align-items: center;
                margin-right: 8px;
                padding: 4px 10px;
                border-radius: 999px;
                font-weight: 800;
                font-size: 12px;
                letter-spacing: 0.04em;
                text-transform: uppercase;
                background: linear-gradient(135deg, rgba(8, 47, 73, 0.96), rgba(14, 116, 144, 0.96));
                color: #ecfeff;
                border: 1px solid rgba(103, 232, 249, 0.5);
                box-shadow: 0 6px 16px rgba(8, 47, 73, 0.28);
                vertical-align: middle;
            }
            .ftab-grid {
                display: grid;
                grid-template-columns: minmax(0, 1fr) 120px;
                gap: 10px;
                margin-top: 12px;
            }
            .ftab-grid-wide {
                grid-column: 1 / -1;
            }
            .ftab-grid .twet-input[type="color"] {
                padding: 4px;
                min-height: 36px;
            }
            .ftab-color-preview {
                min-height: 36px;
                border-radius: 10px;
                border: 1px solid rgba(255, 255, 255, 0.12);
                box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08);
            }
            .ftab-toggle-grid {
                display: grid;
                grid-template-columns: repeat(2, minmax(0, 1fr));
                gap: 8px;
                margin-top: 12px;
            }
            .ftab-toggle {
                display: flex;
                align-items: center;
                gap: 8px;
                padding: 9px 10px;
                border-radius: 10px;
                background: rgba(255, 255, 255, 0.04);
                border: 1px solid rgba(138, 166, 217, 0.16);
                color: #dbeafe;
            }
            .ftab-id-list {
                min-height: 86px;
            }
            .ftab-lock-copy {
                margin-top: 8px;
                color: #94a3b8;
            }
            .ftab-card-head {
                display: flex;
                align-items: center;
                justify-content: space-between;
                gap: 10px;
            }
            .ftab-preview-pill {
                display: inline-flex;
                align-items: center;
                gap: 6px;
                padding: 4px 10px;
                border-radius: 999px;
                font-size: 11px;
                font-weight: 800;
                letter-spacing: 0.04em;
                text-transform: uppercase;
                color: #fff;
                box-shadow: 0 6px 18px rgba(15, 23, 42, 0.25);
            }
            .ftab-help-grid {
                display: grid;
                grid-template-columns: repeat(3, minmax(0, 1fr));
                gap: 10px;
                margin-top: 12px;
            }
            .ftab-help-card {
                padding: 10px 12px;
                border-radius: 12px;
                background: rgba(255, 255, 255, 0.04);
                border: 1px solid rgba(138, 166, 217, 0.14);
                color: #cbd5e1;
            }
            .ftab-bulk-box {
                min-height: 92px;
            }
            .ftab-readonly-note {
                margin-top: 10px;
                color: #fcd34d;
            }
            .ftab-warning-banner {
                display: block;
                margin: 12px 0 14px;
                padding: 14px 16px;
                border-radius: 14px;
                background: linear-gradient(135deg, rgba(127, 29, 29, 0.96), rgba(153, 27, 27, 0.98));
                color: #fff7f7;
                border: 1px solid rgba(252, 165, 165, 0.7);
                box-shadow: 0 10px 24px rgba(127, 29, 29, 0.28);
                font-weight: 800;
                font-size: 14px;
                line-height: 1.25;
            }
            .ftab-warning-banner strong {
                display: block;
                margin-bottom: 3px;
                font-size: 15px;
                letter-spacing: 0.01em;
            }
            .ftab-warning-meta {
                display: block;
                margin-top: 6px;
                font-size: 12px;
                font-weight: 600;
                opacity: 0.92;
            }
            .d.ftab-fullbright .shape {
                fill-opacity: 1 !important;
            }
            .d.ftab-fullbright .leaflet-zoom-hide .shape:not(.war):not(.selected) {
                stroke-width: 0.5 !important;
            }
            .d.ftab-fullbright .leaflet-zoom-hide[width="1564"] .shape:not(.war):not(.selected) {
                stroke-width: 0.7 !important;
            }
            .d.ftab-fullbright .leaflet-zoom-hide[width="3128"] .shape:not(.war):not(.selected) {
                stroke-width: 1.1 !important;
            }
            .d.ftab-fullbright .leaflet-zoom-hide[width="6256"] .shape:not(.war):not(.selected) {
                stroke-width: 1.7 !important;
            }
            .d.ftab-fullbright .shape.territory[fill="#999999"] {
                fill: #5c5c5c !important;
            }
            #map-cont.ftab-hideMarkers .leaflet-shadow-pane > img {
                display: none !important;
            }
        `;

        if (typeof GM_addStyle === "function") {
            GM_addStyle(css);
            return;
        }

        const style = document.createElement("style");
        style.textContent = css;
        document.head.appendChild(style);
    }

    function loadState() {
        try {
            const raw = localStorage.getItem(STORAGE_KEY);
            if (!raw) {
                return { enemies: {}, ui: { collapsed: false, activeTab: "war", mobileToolbarCollapsed: false, mobileControlsCollapsed: false, orderCache: {}, focusEnemyId: "", pendingFocusRestore: false, scrollByTab: {}, ftabBulkDraft: "", hideAvoids: false, hideCautions: false, hideGreenlights: false }, settings: getDefaultSettings() };
            }

            const parsed = JSON.parse(raw);
            return {
                enemies: parsed.enemies || {},
                ui: { collapsed: false, activeTab: "war", mobileToolbarCollapsed: false, mobileControlsCollapsed: false, orderCache: {}, focusEnemyId: "", pendingFocusRestore: false, scrollByTab: {}, ftabBulkDraft: "", hideAvoids: false, hideCautions: false, hideGreenlights: false, ...(parsed.ui || {}) },
                settings: normalizeSettings(parsed.settings || {})
            };
        } catch (error) {
            console.warn("TWET: failed to load saved data", error);
            return { enemies: {}, ui: { collapsed: false, activeTab: "war", mobileToolbarCollapsed: false, mobileControlsCollapsed: false, orderCache: {}, focusEnemyId: "", pendingFocusRestore: false, scrollByTab: {}, ftabBulkDraft: "", hideAvoids: false, hideCautions: false, hideGreenlights: false }, settings: getDefaultSettings() };
        }
    }

    function saveState() {
        localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
    }

    function createPanel() {
        removeDuplicatePanels();
        const existingPanel = document.getElementById("matchmaker-panel");
        if (existingPanel) {
            panel = existingPanel;
            panelBody = panel.querySelector(".twet-body");
            return;
        }
        panel = document.createElement("aside");
        panel.id = "matchmaker-panel";
        panel.className = "twet-panel";

        const header = document.createElement("div");
        header.className = "twet-header";
        header.innerHTML = `
            <div class="twet-title-wrap">
            <div class="twet-title">TWAT Test</div>
                <div class="twet-tabs">
                    <button class="twet-tab ${state.ui.activeTab === "war" ? "active" : ""}" data-action="switch-tab" data-tab="war">${t("tabWar")}</button>
                    <button class="twet-tab ${state.ui.activeTab === "revenge" ? "active" : ""}" data-action="switch-tab" data-tab="revenge">${t("tabRevenge")}</button>
                    <button class="twet-tab ${state.ui.activeTab === "ftab" ? "active" : ""}" data-action="switch-tab" data-tab="ftab">${t("tabFTAB")}</button>
                    <button class="twet-tab ${state.ui.activeTab === "instructions" ? "active" : ""}" data-action="switch-tab" data-tab="instructions">${t("tabInstructions")}</button>
                </div>
            </div>
            <div class="twet-mobile-control-toggles">
                <button class="twet-button" data-action="toggle-controls">${state.ui.mobileControlsCollapsed ? t("btnActions") : t("btnHideActions")}</button>
                <button class="twet-button" data-action="toggle-toolbar">${state.ui.mobileToolbarCollapsed ? t("btnTools") : t("btnHideTools")}</button>
            </div>
            <div class="twet-controls">
                <button class="twet-button ${state.settings.ftab?.enabled ? "ftab-master-on" : "ftab-master-off"}" data-action="toggle-ftab-master">${state.settings.ftab?.enabled ? "FTAB On" : "FTAB Off"}</button>
                <button class="twet-button" data-action="sync-faction" data-hide-on-ftab="true">${t("btnSync")}</button>
                <button class="twet-button" data-action="refresh-api" data-hide-on-ftab="true">${t("btnRefresh")}</button>
                <button class="twet-button" data-action="refresh-links" data-hide-on-ftab="true">${t("btnLinks")}</button>
                <button class="twet-button" data-action="toggle-pin" data-hide-on-mobile="true">${state.settings.pinned ? t("btnUnpin") : t("btnPin")}</button>
                <button class="twet-button" data-action="toggle-mobile">${state.settings.mobileMode ? t("btnDesktop") : t("btnMobile")}</button>
                <button class="twet-button" data-action="reset-keep-api" data-hide-on-ftab="true">${t("btnReset")}</button>
                <button class="twet-button" data-action="collapse">${state.ui.collapsed ? t("btnOpen") : t("btnHide")}</button>
            </div>
        `;

        const toolbar = document.createElement("div");
        toolbar.className = "twet-toolbar";
        toolbar.innerHTML = `
            <input class="twet-input" data-role="search" placeholder="${t("searchPlaceholder")}" />
            <select class="twet-select" data-role="sort">
                <option value="score">${t("sortScore")}</option>
                <option value="recent">${t("sortRecent")}</option>
                <option value="name">${t("sortName")}</option>
                <option value="level">${t("sortLevel")}</option>
            </select>
            <select class="twet-select" data-role="language">
                <option value="en"${getLanguage() === "en" ? " selected" : ""}>English</option>
                <option value="fr"${getLanguage() === "fr" ? " selected" : ""}>Francais</option>
                <option value="es"${getLanguage() === "es" ? " selected" : ""}>Espanol</option>
                <option value="it"${getLanguage() === "it" ? " selected" : ""}>Italiano</option>
                <option value="nl"${getLanguage() === "nl" ? " selected" : ""}>Nederlands</option>
                <option value="id"${getLanguage() === "id" ? " selected" : ""}>Indonesia</option>
                <option value="gd"${getLanguage() === "gd" ? " selected" : ""}>Gaidhlig</option>
                <option value="cockney"${getLanguage() === "cockney" ? " selected" : ""}>Cockney</option>
            </select>
            <select class="twet-select" data-role="filter">
                <option value="all">${t("filterAll")}</option>
                <option value="priority">${t("filterPriority")}</option>
                <option value="dangerous">${t("filterDangerous")}</option>
                <option value="easy">${t("filterEasy")}</option>
                <option value="inactive">${t("filterInactive")}</option>
            </select>
            <label class="twet-scale-control">
                <span>${t("sizeLabel")}</span>
                <input type="range" min="80" max="140" step="5" value="${Math.round(getPanelScale() * 100)}" data-role="scale" />
                <span class="twet-scale-value" data-role="scale-value">${Math.round(getPanelScale() * 100)}%</span>
            </label>
            <div class="twet-actions">
                <button class="twet-button" data-action="export">${t("btnExport")}</button>
                <button class="twet-button" data-action="import">${t("btnImport")}</button>
            </div>
            ${state.ui.activeTab === "war" ? `
                <div class="twet-card twet-toolbar-wide">
                    <div class="twet-pill-row">
                        <label class="twet-pill"><input type="checkbox" data-role="hide-avoid"${state.ui.hideAvoids ? " checked" : ""} /> ${t("hideAvoids")}</label>
                        <label class="twet-pill"><input type="checkbox" data-role="hide-caution"${state.ui.hideCautions ? " checked" : ""} /> ${t("hideCautions")}</label>
                        <label class="twet-pill"><input type="checkbox" data-role="hide-greenlight"${state.ui.hideGreenlights ? " checked" : ""} /> ${t("hideGreenlights")}</label>
                        <button class="twet-button" data-action="reset-filters">${t("resetFilters")}</button>
                    </div>
                </div>
            ` : ""}
        `;

        panelBody = document.createElement("div");
        panelBody.className = "twet-body";

        panel.append(header, toolbar, panelBody);
        document.body.appendChild(panel);

        panelSearch = toolbar.querySelector('[data-role="search"]');
        sortSelect = toolbar.querySelector('[data-role="sort"]');
        languageSelect = toolbar.querySelector('[data-role="language"]');
        filterSelect = toolbar.querySelector('[data-role="filter"]');
        scaleInput = toolbar.querySelector('[data-role="scale"]');

        header.addEventListener("click", handlePanelClick);
        toolbar.addEventListener("click", handlePanelClick);
        toolbar.addEventListener("change", handleToolbarChange);
        toolbar.addEventListener("input", handleToolbarInput);
        panelBody.addEventListener("click", handlePanelClick);
        panelBody.addEventListener("change", handlePanelChange);
        panelBody.addEventListener("input", handlePanelInput);
        panelBody.addEventListener("scroll", rememberPanelScroll, { passive: true });
        panelBody.addEventListener("wheel", handlePanelIntentScroll, { passive: true });
        panelBody.addEventListener("touchmove", handlePanelIntentScroll, { passive: true });
        header.addEventListener("mousedown", handleHeaderMouseDown);

        syncPanelCollapsed();
        updatePanelPosition();
        updatePanelScale();
    }

    function removeDuplicatePanels() {
        const panels = Array.from(document.querySelectorAll("#matchmaker-panel"));
        if (panels.length <= 1) {
            return;
        }
        panels.slice(1).forEach((node) => node.remove());
    }

    function handlePanelClick(event) {
        const button = event.target.closest("[data-action]");
        const nameLink = event.target.closest("a.twet-name[data-enemy-id]");
        if (!button && nameLink?.dataset.enemyId) {
            rememberFocusedEnemy(nameLink.dataset.enemyId);
            return;
        }
        if (!button && state.ui.collapsed && event.currentTarget === panel.querySelector(".twet-header")) {
            state.ui.collapsed = false;
            saveState();
            syncPanelCollapsed();
            refreshPanel();
            return;
        }
        if (!button) {
            return;
        }

        const action = button.dataset.action;
        if (state.ui.collapsed) {
            if (action === "collapse") {
                state.ui.collapsed = false;
                saveState();
                syncPanelCollapsed();
                refreshPanel();
            }
            return;
        }
        if (action === "switch-tab") {
            const tab = button.dataset.tab;
            state.ui.activeTab = tab === "revenge" || tab === "instructions" || tab === "ftab" ? tab : "war";
            saveState();
            refreshPanel();
            return;
        }
        if (action === "collapse") {
            state.ui.collapsed = !state.ui.collapsed;
            saveState();
            syncPanelCollapsed();
            return;
        }
        if (action === "refresh-links") {
            scanPage();
            refreshPanel();
            return;
        }
        if (action === "toggle-pin") {
            togglePin();
            return;
        }
        if (action === "toggle-mobile") {
            state.settings.mobileMode = !state.settings.mobileMode;
            if (state.settings.mobileMode && Number(state.settings.scale) > 1) {
                state.settings.scale = 1;
            }
            saveState();
            syncPanelCollapsed();
            rebuildPanel();
            return;
        }
        if (action === "toggle-toolbar") {
            state.ui.mobileToolbarCollapsed = !state.ui.mobileToolbarCollapsed;
            saveState();
            syncPanelCollapsed();
            return;
        }
        if (action === "toggle-controls") {
            state.ui.mobileControlsCollapsed = !state.ui.mobileControlsCollapsed;
            saveState();
            syncPanelCollapsed();
            return;
        }
        if (action === "toggle-ftab-master") {
            state.settings.ftab.enabled = !state.settings.ftab.enabled;
            saveState();
            if (state.settings.ftab.enabled) {
                scanPage();
            } else {
                removeFtabDecorations();
                applyFtabVisualToggles();
            }
            refreshPanel();
            syncHeaderButtons();
            return;
        }
        if (action === "sync-faction") {
            syncSettingsInputsForActions();
            clearStableOrder("war");
            syncFactionTargets();
            return;
        }
        if (action === "refresh-api") {
            syncSettingsInputsForActions();
            queueAllTrackedEnemiesForApiRefresh(true);
            flushApiQueue();
            return;
        }
        if (action === "add-revenge") {
            clearStableOrder("revenge");
            addRevengeTargets();
            return;
        }
        if (action === "reset-keep-api") {
            clearStableOrder();
            resetKeepApi();
            return;
        }
        if (action === "export") {
            exportData();
            return;
        }
        if (action === "import") {
            importData();
            return;
        }
        if (action === "ftab-unlock") {
            unlockFtabAdmin();
            return;
        }
        if (action === "ftab-lock") {
            state.settings.ftab.adminUnlocked = false;
            saveState();
            refreshPanel();
            return;
        }
        if (action === "ftab-add-alliance") {
            if (!ensureFtabEditorUnlocked()) {
                return;
            }
            state.settings.ftab.alliances.push(createBlankAlliance());
            saveState();
            refreshPanel();
            return;
        }
        if (action === "ftab-restore-defaults") {
            if (!ensureFtabEditorUnlocked()) {
                return;
            }
            state.settings.ftab.alliances = cloneDefaultFtabAlliances();
            saveState();
            refreshPanel();
            return;
        }
        if (action === "ftab-bulk-import") {
            if (!ensureFtabEditorUnlocked()) {
                return;
            }
            importFtabBulk();
            return;
        }
        if (action === "ftab-export") {
            exportFtabConfig();
            return;
        }
        if (action === "ftab-clear-bulk") {
            clearFtabBulkInput();
            return;
        }
        if (action === "ftab-remove-alliance") {
            if (!ensureFtabEditorUnlocked()) {
                return;
            }
            const index = Number(button.dataset.ftabIndex);
            if (Number.isInteger(index) && index >= 0) {
                state.settings.ftab.alliances.splice(index, 1);
                saveState();
                refreshPanel();
            }
            return;
        }
        if (action === "start-chain") {
            startChainTimer();
            return;
        }
        if (action === "clear-chain") {
            clearChainTimer();
            return;
        }
        if (action === "reset-filters") {
            state.ui.hideAvoids = false;
            state.ui.hideCautions = false;
            state.ui.hideGreenlights = false;
            saveState();
            refreshPanel();
            return;
        }

        const enemyId = button.dataset.enemyId;
        if (!enemyId) {
            return;
        }

        if (action === "attack") {
            rememberFocusedEnemy(enemyId);
            window.open(attackUrl(enemyId), "_blank", "noopener");
            return;
        }
        if (action === "copy-profile") {
            copyText(buildEnemyReport(state.enemies[enemyId]), t("copiedReport"));
            return;
        }
        if (action === "copy-attack") {
            copyText(attackUrl(enemyId), t("copiedAttack"));
            return;
        }
        if (action === "toggle-travel-silence") {
            const enemy = state.enemies[enemyId];
            if (!enemy) {
                return;
            }
            enemy.travelAlarmSilenced = !enemy.travelAlarmSilenced;
            enemy.updatedAt = Date.now();
            saveState();
            refreshPanel();
            return;
        }
        if (action === "remove") {
            removeTrackedEnemy(enemyId);
            return;
        }
        if (action === "visit") {
            rememberFocusedEnemy(enemyId);
            window.open(profileUrl(enemyId), "_blank", "noopener");
            return;
        }
        if (action === "refresh-enemy") {
            queueEnemyForApiRefresh(enemyId, true);
            flushApiQueue();
            return;
        }
        if (action === "record-win") {
            recordFightOutcome(enemyId, "win");
            return;
        }
        if (action === "record-loss") {
            recordFightOutcome(enemyId, "loss");
            return;
        }
        if (action === "record-escape") {
            recordFightOutcome(enemyId, "escape");
            return;
        }
        if (action === "repair-record") {
            repairEnemyRecord(enemyId);
            return;
        }
    }

    function handlePanelChange(event) {
        if (event.target === apiKeyInput) {
            state.settings.apiKey = sanitizeApiKey(event.target.value);
            saveState();
            return;
        }
        if (event.target === factionIdInput) {
            state.settings.targetFactionId = sanitizeFactionId(event.target.value);
            saveState();
            return;
        }
        if (event.target === languageSelect) {
            state.settings.language = normalizeLanguage(event.target.value);
            saveState();
            rebuildPanel();
            return;
        }
        if (event.target === revengeIdInput) {
            state.settings.revengeIds = sanitizeIdList(event.target.value);
            saveState();
            return;
        }
        if (event.target.matches('[data-role="ready-alerts"]')) {
            state.settings.readyAlerts = !!event.target.checked;
            if (state.settings.readyAlerts) {
                ensureNotificationPermission();
            }
            saveState();
            return;
        }
        if (event.target.matches('[data-role="soon-alerts"]')) {
            state.settings.soonAlerts = !!event.target.checked;
            if (state.settings.soonAlerts) {
                ensureNotificationPermission();
            }
            saveState();
            return;
        }
        if (event.target.matches('[data-role="sound-alerts"]')) {
            state.settings.soundAlerts = !!event.target.checked;
            saveState();
            return;
        }
        if (event.target.matches('[data-role="hide-avoid"]')) {
            state.ui.hideAvoids = !!event.target.checked;
            saveState();
            refreshPanel();
            return;
        }
        if (event.target.matches('[data-role="hide-caution"]')) {
            state.ui.hideCautions = !!event.target.checked;
            saveState();
            refreshPanel();
            return;
        }
        if (event.target.matches('[data-role="hide-greenlight"]')) {
            state.ui.hideGreenlights = !!event.target.checked;
            saveState();
            refreshPanel();
            return;
        }
        if (event.target.matches('[data-role="travel-alarm"]')) {
            const enemyId = event.target.dataset.enemyId;
            const enemy = enemyId ? state.enemies[enemyId] : null;
            if (enemy) {
                enemy.travelAlarmEnabled = !!event.target.checked;
                enemy.travelAlarmSilenced = false;
                enemy.updatedAt = Date.now();
                saveState();
            }
            return;
        }
        if (event.target.matches('[data-role="ftab-toggle"]')) {
            const key = event.target.dataset.key;
            if (key && Object.prototype.hasOwnProperty.call(state.settings.ftab, key)) {
                state.settings.ftab[key] = !!event.target.checked;
                saveState();
                applyFtabVisualToggles();
            }
            return;
        }
        if (event.target.matches('[data-role="ftab-alliance-field"]')) {
            updateFtabAllianceField(event.target);
            return;
        }
        if (event.target === ftabBulkInput) {
            state.ui.ftabBulkDraft = event.target.value || "";
            saveState();
            return;
        }

        const enemyId = event.target.dataset.enemyId;
        if (!enemyId || !state.enemies[enemyId]) {
            return;
        }

        const enemy = state.enemies[enemyId];
        if (event.target.matches('[data-field="tags"]')) {
            enemy.tags = parseTags(event.target.value);
        } else if (event.target.matches('[data-field="status"]')) {
            enemy.manualStatus = event.target.value.trim();
        }

        enemy.updatedAt = Date.now();
        saveState();
        scanPage();
        refreshPanel();
    }

    function handleToolbarChange(event) {
        if (event.target === languageSelect) {
            state.settings.language = normalizeLanguage(event.target.value);
            saveState();
            rebuildPanel();
            return;
        }
        if (event.target === scaleInput) {
            state.settings.scale = normalizeScale(event.target.value);
            saveState();
            updatePanelScale();
            syncScaleLabel();
            return;
        }
        refreshPanel();
    }

    function handleToolbarInput(event) {
        if (event.target === languageSelect) {
            state.settings.language = normalizeLanguage(event.target.value);
            saveState();
            rebuildPanel();
            return;
        }
        if (event.target === scaleInput) {
            state.settings.scale = normalizeScale(event.target.value);
            saveState();
            updatePanelScale();
            syncScaleLabel();
            return;
        }
        refreshPanel();
    }

    function handlePanelInput(event) {
        if (event.target === apiKeyInput) {
            state.settings.apiKey = sanitizeApiKey(event.target.value);
            saveState();
            return;
        }
        if (event.target === factionIdInput) {
            state.settings.targetFactionId = sanitizeFactionId(event.target.value);
            saveState();
            return;
        }
        if (event.target === languageSelect) {
            state.settings.language = normalizeLanguage(event.target.value);
            saveState();
            rebuildPanel();
            return;
        }
        if (event.target === revengeIdInput) {
            state.settings.revengeIds = sanitizeIdList(event.target.value);
            saveState();
            return;
        }
        if (event.target === chainMinutesInput) {
            state.settings.chainMinutesPreset = normalizeChainMinutes(event.target.value);
            saveState();
            return;
        }
        if (event.target.matches('[data-role="hide-avoid"]')) {
            state.ui.hideAvoids = !!event.target.checked;
            saveState();
            refreshPanel();
            return;
        }
        if (event.target.matches('[data-role="hide-caution"]')) {
            state.ui.hideCautions = !!event.target.checked;
            saveState();
            refreshPanel();
            return;
        }
        if (event.target.matches('[data-role="hide-greenlight"]')) {
            state.ui.hideGreenlights = !!event.target.checked;
            saveState();
            refreshPanel();
            return;
        }
        if (event.target.matches('[data-role="travel-alarm"]')) {
            const enemyId = event.target.dataset.enemyId;
            const enemy = enemyId ? state.enemies[enemyId] : null;
            if (enemy) {
                enemy.travelAlarmEnabled = !!event.target.checked;
                enemy.travelAlarmSilenced = false;
                enemy.updatedAt = Date.now();
                saveState();
            }
            return;
        }
        if (event.target === ftabBulkInput) {
            state.ui.ftabBulkDraft = event.target.value || "";
            saveState();
            return;
        }
        if (event.target.matches('[data-role="ftab-alliance-field"]')) {
            updateFtabAllianceField(event.target);
            return;
        }

        const enemyId = event.target.dataset.enemyId;
        if (!enemyId || !state.enemies[enemyId]) {
            return;
        }

        const enemy = state.enemies[enemyId];
        if (event.target.matches('[data-field="notes"]')) {
            enemy.notes = event.target.value;
            enemy.updatedAt = Date.now();
            saveState();
        }
    }

    function handlePanelIntentScroll() {
        if (!panelBody || !shouldFreezeLivePanelRefresh()) {
            return;
        }
        if (panelScrollUnlockTimer) {
            clearTimeout(panelScrollUnlockTimer);
        }
        const tabAtScroll = state.ui.activeTab;
        panelScrollUnlockTimer = setTimeout(() => {
            clearStableOrder(tabAtScroll);
            if (pendingFrozenPanelRefresh && state.ui.activeTab === tabAtScroll && !state.ui.collapsed) {
                pendingFrozenPanelRefresh = false;
                refreshPanel();
            }
            panelScrollUnlockTimer = null;
        }, 120);
    }

    function rememberFocusedEnemy(enemyId) {
        state.ui.focusEnemyId = enemyId ? String(enemyId) : "";
        state.ui.pendingFocusRestore = !!enemyId;
        rememberPanelScroll();
        saveState();
    }

    function rememberPanelScroll() {
        if (!panelBody) {
            return;
        }
        const key = state.ui.activeTab || "war";
        const scrollByTab = state.ui.scrollByTab && typeof state.ui.scrollByTab === "object" ? state.ui.scrollByTab : (state.ui.scrollByTab = {});
        scrollByTab[key] = panelBody.scrollTop || 0;
    }

    function restorePanelViewport() {
        if (!panelBody) {
            return;
        }
        const activeTab = state.ui.activeTab || "war";
        const focusEnemyId = String(state.ui.focusEnemyId || "");
        const pendingFocusRestore = !!state.ui.pendingFocusRestore;
        const scrollByTab = state.ui.scrollByTab && typeof state.ui.scrollByTab === "object" ? state.ui.scrollByTab : {};
        queueMicrotask(() => {
            if (!panelBody) {
                return;
            }
            if (pendingFocusRestore && focusEnemyId) {
                const focusedCard = panelBody.querySelector(`[data-enemy-card="${focusEnemyId}"]`);
                if (focusedCard) {
                    focusedCard.classList.add("twet-card-focused");
                    focusedCard.scrollIntoView({ block: "center", behavior: "auto" });
                    state.ui.focusEnemyId = "";
                    state.ui.pendingFocusRestore = false;
                    saveState();
                    return;
                }
            }
            const savedScroll = Number(scrollByTab[activeTab]);
            if (Number.isFinite(savedScroll) && savedScroll > 0) {
                panelBody.scrollTop = savedScroll;
            }
        });
    }

    function shouldFreezeLivePanelRefresh() {
        return state.ui.activeTab === "war" || state.ui.activeTab === "revenge";
    }

    function syncPanelCollapsed() {
        const collapsed = !!state.ui.collapsed;
        const title = panel.querySelector(".twet-title");
        const toolbar = panel.querySelector(".twet-toolbar");
        const controls = panel.querySelector(".twet-controls");
        const hideToolbar = !!state.ui.mobileToolbarCollapsed;
        const hideControls = !!state.ui.mobileControlsCollapsed;
        panel.classList.toggle("twet-panel-collapsed", collapsed);
        panel.classList.toggle("twet-panel-pinned", !!state.settings.pinned);
        panel.classList.toggle("twet-panel-mobile", !!state.settings.mobileMode);
        title.textContent = collapsed ? "TWAT" : "TWAT Test";
        toolbar.style.display = collapsed || state.ui.activeTab === "ftab" || hideToolbar ? "none" : "";
        controls.style.display = collapsed || hideControls ? "none" : "";
        panelBody.style.display = collapsed ? "none" : "";
        panel.querySelector('[data-action="collapse"]').textContent = collapsed ? t("btnOpen") : t("btnHide");
        const pinButton = panel.querySelector('[data-action="toggle-pin"]');
        if (pinButton) {
            pinButton.textContent = state.settings.pinned ? t("btnUnpin") : t("btnPin");
        }
        const mobileButton = panel.querySelector('[data-action="toggle-mobile"]');
        if (mobileButton) {
            mobileButton.textContent = state.settings.mobileMode ? t("btnDesktop") : t("btnMobile");
        }
        syncHeaderButtons();
        updatePanelPosition();
        updatePanelScale();
        syncHeaderTabs();
    }

    function syncHeaderButtons() {
        if (!panel) {
            return;
        }
        const ftabTabActive = state.ui.activeTab === "ftab";
        const ftabButton = panel.querySelector('[data-action="toggle-ftab-master"]');
        if (ftabButton) {
            ftabButton.textContent = state.settings.ftab?.enabled ? "FTAB On" : "FTAB Off";
            ftabButton.classList.toggle("ftab-master-on", !!state.settings.ftab?.enabled);
            ftabButton.classList.toggle("ftab-master-off", !state.settings.ftab?.enabled);
        }
        panel.querySelectorAll("[data-hide-on-ftab]").forEach((button) => {
            button.style.display = ftabTabActive ? "none" : "";
        });
        panel.querySelectorAll("[data-hide-on-mobile]").forEach((button) => {
            button.style.display = state.settings.mobileMode ? "none" : "";
        });
        const toolbarButton = panel.querySelector('[data-action="toggle-toolbar"]');
        if (toolbarButton) {
            toolbarButton.textContent = ftabTabActive ? t("btnToolsUnavailable") : (state.ui.mobileToolbarCollapsed ? t("btnTools") : t("btnHideTools"));
            toolbarButton.disabled = ftabTabActive;
        }
        const controlsButton = panel.querySelector('[data-action="toggle-controls"]');
        if (controlsButton) {
            controlsButton.textContent = state.ui.mobileControlsCollapsed ? t("btnActions") : t("btnHideActions");
            controlsButton.disabled = false;
        }
    }

    function refreshPanel() {
        if (!panelBody) {
            return;
        }
        pendingFrozenPanelRefresh = false;
        syncLiveSettingInputs();
        state.settings.ftab = normalizeFtabSettings(state.settings.ftab || {});
        saveState();

        if (state.ui.activeTab === "instructions") {
            suppressObserver = true;
            panelBody.innerHTML = renderInstructionsTab();
            bindSettingsInputs();
            queueMicrotask(() => {
                suppressObserver = false;
            });
            syncHeaderButtons();
            restorePanelViewport();
            return;
        }
        if (state.ui.activeTab === "ftab") {
            suppressObserver = true;
            panelBody.innerHTML = renderFtabTab();
            bindSettingsInputs();
            queueMicrotask(() => {
                suppressObserver = false;
            });
            syncHeaderButtons();
            restorePanelViewport();
            return;
        }
        const allEnemies = Object.values(state.enemies);
        const tabEnemies = getOrderedTabEnemies(allEnemies);
        const enemies = tabEnemies
            .filter(applyPanelFilters)
            .slice();
        const summary = buildWarSummary(tabEnemies);
        const nextTargets = getNextAvailableTargets(tabEnemies).slice(0, 5);

        suppressObserver = true;
        if (!enemies.length) {
            panelBody.innerHTML = `
                ${renderSettingsCard()}
                <div class="twet-card">
                    <div class="twet-meta">${t("noTracked")}</div>
                    <div class="twet-contexts">${state.ui.activeTab === "revenge" ? t("noTrackedRevenge") : t("noTrackedWar")}</div>
                </div>
            `;
            bindSettingsInputs();
            queueMicrotask(() => {
                suppressObserver = false;
            });
            syncHeaderButtons();
            restorePanelViewport();
            return;
        }

        panelBody.innerHTML = `
            ${renderSettingsCard()}
            ${renderSummary(summary, nextTargets, enemies)}
            ${renderAttackAssist(nextTargets)}
            ${enemies.map(renderEnemyCard).join("")}
        `;
        bindSettingsInputs();
        queueMicrotask(() => {
            suppressObserver = false;
        });
        syncHeaderButtons();
        restorePanelViewport();
    }

    function applyPanelFilters(enemy) {
        const searchValue = (panelSearch?.value || "").trim().toLowerCase();
        const filterValue = filterSelect?.value || "all";
        const tags = enemy.tags || [];
        const decision = getMatchmakerAssessment(enemy).decision;
        const haystack = [
            enemy.name,
            enemy.id,
            enemy.notes,
            enemy.faction,
            enemy.status,
            enemy.manualStatus,
            tags.join(" ")
        ].join(" ").toLowerCase();

        if (searchValue && !haystack.includes(searchValue)) {
            return false;
        }
        if (filterValue !== "all" && !tags.includes(filterValue)) {
            return false;
        }
        if (state.ui.hideAvoids && (decision === "Avoid" || decision === "Blocked" || decision === "Protected")) {
            return false;
        }
        if (state.ui.hideCautions && decision === "Caution") {
            return false;
        }
        if (state.ui.hideGreenlights && (decision === "Greenlight" || decision === "Playable")) {
            return false;
        }
        return true;
    }

    function compareEnemies(a, b) {
        const sortValue = sortSelect?.value || "score";
        if (sortValue === "recent") {
            return (b.lastSeenAt || 0) - (a.lastSeenAt || 0);
        }
        if (sortValue === "name") {
            return String(a.name || "").localeCompare(String(b.name || ""));
        }
        if (sortValue === "level") {
            return (b.level || 0) - (a.level || 0);
        }
        const availabilityDelta = availabilityRank(a) - availabilityRank(b);
        if (availabilityDelta !== 0) {
            return availabilityDelta;
        }
        const releaseDelta = getAvailabilityTimestamp(a) - getAvailabilityTimestamp(b);
        if (releaseDelta !== 0) {
            return releaseDelta;
        }
        return scoreEnemy(b) - scoreEnemy(a);
    }

    function compareMatchmakerEnemies(a, b) {
        const assessmentA = getMatchmakerAssessment(a);
        const assessmentB = getMatchmakerAssessment(b);
        const decisionDelta = getMatchmakerDecisionRank(assessmentB.decision) - getMatchmakerDecisionRank(assessmentA.decision);
        if (decisionDelta !== 0) {
            return decisionDelta;
        }
        const scoreDelta = assessmentB.score - assessmentA.score;
        if (scoreDelta !== 0) {
            return scoreDelta;
        }
        return compareEnemies(a, b);
    }

    function getMatchmakerDecisionRank(decision) {
        const ranks = {
            Greenlight: 5,
            Playable: 4,
            Caution: 3,
            Avoid: 2,
            Blocked: 1,
            Protected: 0
        };
        return ranks[decision] ?? 0;
    }

    function getOrderedTabEnemies(enemies) {
        const tabEnemies = getTabEnemies(enemies);
        if (state.ui.activeTab !== "war" && state.ui.activeTab !== "revenge") {
            return tabEnemies.slice().sort(compareEnemies);
        }
        return applyStableOrder(tabEnemies);
    }

    function applyStableOrder(enemies) {
        const sorted = enemies.slice().sort(compareEnemies);
        const cache = state.ui.orderCache && typeof state.ui.orderCache === "object" ? state.ui.orderCache : (state.ui.orderCache = {});
        const key = `${state.ui.activeTab}:${sortSelect?.value || "score"}`;
        const previousIds = Array.isArray(cache[key]) ? cache[key].map(String) : [];
        if (!previousIds.length) {
            cache[key] = sorted.map((enemy) => String(enemy.id));
            saveState();
            return sorted;
        }

        const remaining = new Map(sorted.map((enemy) => [String(enemy.id), enemy]));
        const preserved = [];
        previousIds.forEach((id) => {
            if (remaining.has(id)) {
                preserved.push(remaining.get(id));
                remaining.delete(id);
            }
        });

        const newcomers = sorted.filter((enemy) => remaining.has(String(enemy.id)));
        const finalOrder = preserved.concat(newcomers);
        const nextIds = finalOrder.map((enemy) => String(enemy.id));
        if (nextIds.join(",") !== previousIds.join(",")) {
            cache[key] = nextIds;
            saveState();
        }
        return finalOrder;
    }

    function clearStableOrder(tabName) {
        if (!state.ui.orderCache || typeof state.ui.orderCache !== "object") {
            state.ui.orderCache = {};
            return;
        }
        if (!tabName) {
            state.ui.orderCache = {};
            return;
        }
        Object.keys(state.ui.orderCache).forEach((key) => {
            if (key.startsWith(`${tabName}:`)) {
                delete state.ui.orderCache[key];
            }
        });
    }

    function renderEnemyCard(enemy) {
        const score = scoreEnemy(enemy);
        const scoreClass = score >= 80 ? "high" : score >= 45 ? "mid" : "";
        const matchmaker = getMatchmakerAssessment(enemy);
        const fightSummary = `You won ${enemy.wins || 0} | Lost ${enemy.losses || 0} | Escaped ${enemy.escapes || 0}`;
        const tags = enemy.tags?.length ? enemy.tags : [];
        const contexts = enemy.contexts?.length ? enemy.contexts.join(", ") : t("noSourcePages");
        const notes = escapeHtml(enemy.notes || "");
        const status = escapeHtml(enemy.manualStatus || enemy.status || "Unknown");
        const faction = enemy.faction ? `${t("factionId")}: ${escapeHtml(enemy.faction)}` : t("factionUnknown");
        const level = enemy.level ? formatTranslation("levelFormat", { level: enemy.level }) : t("levelUnknown");
        const life = enemy.lifeCurrent && enemy.lifeMaximum ? formatTranslation("lifeFormat", { current: enemy.lifeCurrent, maximum: enemy.lifeMaximum }) : t("lifeUnknown");
        const lastAction = enemy.lastAction?.relative || enemy.lastAction?.status || "unknown";
        const apiStamp = enemy.apiFetchedAt ? formatTime(enemy.apiFetchedAt) : "never";
        const apiError = enemy.apiError ? `<div class="twet-meta twet-mini">API: ${escapeHtml(enemy.apiError)}</div>` : "";
        const battleMemory = renderBattleMemory(enemy);
        const battleHistory = renderBattleHistory(enemy);
        const battleThreatPills = renderBattleThreatPills(enemy);
        const availability = getAvailability(enemy);
        const hospTime = getHospitalTimeText(enemy);
        const typeLabel = enemy.sourceType === "revenge" ? t("revengeTarget") : t("warTarget");
        const travelLocation = getTravelLocation(enemy);
        const travelRemainingMs = getTravelRemainingMs(enemy);
        const attackButton = availability.kind === "available" || availability.kind === "soon"
            ? `<button class="twet-button" data-action="attack" data-enemy-id="${enemy.id}">${t("btnAttack")}</button>`
            : "";

        return `
            <div class="twet-card${String(state.ui.focusEnemyId || "") === String(enemy.id) ? " twet-card-focused" : ""}" data-enemy-card="${enemy.id}">
                <div class="twet-row">
                    <div class="twet-name-wrap">
                        <a class="twet-name" data-enemy-id="${enemy.id}" href="${profileUrl(enemy.id)}" target="_blank" rel="noopener">${escapeHtml(enemy.name || `Unknown [${enemy.id}]`)}</a>
                        <button class="twet-copy-icon" data-action="copy-profile" data-enemy-id="${enemy.id}" title="Copy report" aria-label="Copy report">&#x2398;</button>
                    </div>
                    <span class="twet-score ${scoreClass}">${score}</span>
                </div>
                <div class="twet-meta">${typeLabel}</div>
                <div class="twet-stats">${level} | ${faction}</div>
                <div class="twet-meta"><span class="twet-availability ${availability.className}">${escapeHtml(availability.label)}</span></div>
                <div class="twet-meta">${life}</div>
                <div class="twet-meta">${t("statusLabel")}: ${status}</div>
                ${travelLocation ? `<div class="twet-meta">${t("locationLabel")}: ${escapeHtml(travelLocation)}</div>` : ""}
                ${travelRemainingMs > 0 ? `<div class="twet-meta">${t("travelEtaLabel")}: ${escapeHtml(formatCountdown(travelRemainingMs))}</div>` : ""}
                ${enemy.sourceType === "revenge" ? `
                    <div class="twet-meta">
                        <label class="twet-pill">
                            <input type="checkbox" data-role="travel-alarm" data-enemy-id="${enemy.id}"${enemy.travelAlarmEnabled ? " checked" : ""} />
                            ${t("travelAlarmLabel")}
                        </label>
                        ${enemy.travelAlarmEnabled ? `<button class="twet-button" data-action="toggle-travel-silence" data-enemy-id="${enemy.id}">${enemy.travelAlarmSilenced ? t("travelAlarmUnsilence") : t("travelAlarmSilence")}</button>` : ""}
                    </div>
                    ${(!travelLocation && travelRemainingMs <= 0) ? `<div class="twet-meta twet-mini">${t("travelAlarmIdle")}</div>` : ""}
                ` : ""}
                <div class="twet-meta">${t("hospLabel")}: ${escapeHtml(hospTime)}</div>
                <div class="twet-meta">${t("lastActionLabel")}: ${escapeHtml(lastAction)}</div>
                <div class="twet-matchmaker">
                    <div class="twet-matchmaker-copy">
                        <div class="twet-matchmaker-title">Matchmaker ${matchmaker.score}</div>
                        <div class="twet-matchmaker-meta">${escapeHtml(matchmaker.reason)}</div>
                        <div class="twet-matchmaker-meta">Confidence: ${escapeHtml(matchmaker.confidence)} | Availability: ${escapeHtml(matchmaker.availabilityLabel)} | Risk: ${escapeHtml(matchmaker.riskLabel)}</div>
                        ${battleThreatPills}
                    </div>
                    <span class="twet-matchmaker-badge ${matchmaker.className}">${escapeHtml(matchmaker.decision)}</span>
                </div>
                <div class="twet-meta">${t("fightLog")}: ${fightSummary}</div>
                ${battleMemory}
                ${battleHistory}
                <div class="twet-meta twet-mini">${t("seenLabel")}: ${formatTime(enemy.lastSeenAt)} | ${t("updatedLabel")}: ${formatTime(enemy.updatedAt)}</div>
                <div class="twet-meta twet-mini">${t("apiRefreshed")}: ${apiStamp}</div>
                <div class="twet-contexts twet-mini">${t("contexts")}: ${escapeHtml(contexts)}</div>
                ${apiError}
                <div class="twet-pill-row">
                    ${tags.map((tag) => `<span class="twet-pill">${escapeHtml(tag)}</span>`).join("")}
                </div>
                <div class="twet-meta" style="margin-top: 10px;">
                    <input class="twet-input" data-field="tags" data-enemy-id="${enemy.id}" value="${escapeAttribute(tags.join(", "))}" placeholder="${t("tagsPlaceholder")}" />
                </div>
                <div class="twet-meta" style="margin-top: 8px;">
                    <input class="twet-input" data-field="status" data-enemy-id="${enemy.id}" value="${escapeAttribute(enemy.manualStatus || "")}" placeholder="${t("manualStatusPlaceholder")}" />
                </div>
                <div class="twet-meta" style="margin-top: 8px;">
                    <textarea class="twet-textarea" data-field="notes" data-enemy-id="${enemy.id}" placeholder="${t("notesPlaceholder")}">${notes}</textarea>
                </div>
                <div class="twet-actions">
                    ${attackButton}
                    <button class="twet-button" data-action="record-win" data-enemy-id="${enemy.id}">${t("win")}</button>
                    <button class="twet-button" data-action="record-loss" data-enemy-id="${enemy.id}">${t("loss")}</button>
                    <button class="twet-button" data-action="record-escape" data-enemy-id="${enemy.id}">${t("escape")}</button>
                    <button class="twet-button" data-action="repair-record" data-enemy-id="${enemy.id}" title="Repair W/L/E from stored battle history">${t("repairRecord")}</button>
                    <button class="twet-button" data-action="refresh-enemy" data-enemy-id="${enemy.id}">API</button>
                    <button class="twet-button" data-action="visit" data-enemy-id="${enemy.id}">${t("btnOpen")}</button>
                    <button class="twet-button" data-action="remove" data-enemy-id="${enemy.id}">${t("remove")}</button>
                </div>
            </div>
        `;
    }

    function scanPage() {
        annotateTrackedLinks();
        initFtabPageFeatures();
        applyFtabVisualToggles();
        captureBattleMemoryFromPage();
        flushApiQueue();
    }

    function annotateTrackedLinks() {
        suppressObserver = true;
        const links = Array.from(document.querySelectorAll('a[href*="XID="], a[href*="profiles.php"], a[href*="sid=profiles"]'));
        links.forEach((link) => {
            const id = extractPlayerId(link.href);
            if (!id || !state.enemies[id]) {
                return;
            }

            const enemy = state.enemies[id];
            link.classList.remove("twet-highlight-ready", "twet-highlight-soon");
            if (!link.classList.contains("twet-highlight")) {
                link.classList.add("twet-highlight");
            }
            const availability = getAvailability(enemy);
            if (availability.kind === "available") {
                link.classList.add("twet-highlight-ready");
            } else if (availability.kind === "soon") {
                link.classList.add("twet-highlight-soon");
            }

            const existingBadge = link.parentElement?.querySelector(`.twet-link-badge[data-enemy-id="${id}"]`);
            if (existingBadge) {
                const nextText = badgeText(enemy);
                const nextClass = `twet-link-badge ${scoreEnemy(enemy) >= 80 ? "high" : ""}`;
                if (existingBadge.textContent !== nextText) {
                    existingBadge.textContent = nextText;
                }
                if (existingBadge.className !== nextClass) {
                    existingBadge.className = nextClass;
                }
                return;
            }

            const badge = document.createElement("span");
            badge.dataset.enemyId = id;
            badge.className = `twet-link-badge ${scoreEnemy(enemy) >= 80 ? "high" : ""}`;
            badge.textContent = badgeText(enemy);
            link.insertAdjacentElement("afterend", badge);
        });
        queueMicrotask(() => {
            suppressObserver = false;
        });
    }

    function badgeText(enemy) {
        const parts = [`${t("scoreWord")} ${scoreEnemy(enemy)}`];
        if (enemy.tags?.length) {
            parts.push(enemy.tags[0]);
        }
        return parts.join(" | ");
    }

    function upsertEnemy(id, patch, context, markSeen = true) {
        if (!id) {
            return;
        }

        const existing = state.enemies[id] || {
            id,
            name: patch.name || `Unknown [${id}]`,
            tags: [],
            notes: "",
            contexts: [],
            createdAt: Date.now(),
            updatedAt: Date.now(),
            lastSeenAt: Date.now(),
            wins: 0,
            losses: 0,
            escapes: 0
        };

        existing.name = patch.name || existing.name;
        existing.level = patch.level || existing.level;
        existing.faction = patch.faction || existing.faction;
        existing.status = patch.status || existing.status;
        existing.statusState = patch.statusState || existing.statusState;
        existing.until = patch.until ?? existing.until;
        existing.hospitalUntil = patch.hospitalUntil || existing.hospitalUntil;
        existing.lastAction = patch.lastAction || existing.lastAction;
        existing.travel = patch.travel || existing.travel;
        existing.lifeCurrent = patch.lifeCurrent ?? existing.lifeCurrent;
        existing.lifeMaximum = patch.lifeMaximum ?? existing.lifeMaximum;
        existing.apiFetchedAt = patch.apiFetchedAt || existing.apiFetchedAt;
        existing.apiError = patch.apiError ?? existing.apiError;
        existing.sourceType = patch.sourceType || existing.sourceType || "war";
        existing.updatedAt = Date.now();

        if (markSeen) {
            existing.lastSeenAt = Date.now();
        }

        if (context) {
            existing.contexts = mergeContexts(existing.contexts, context);
        }

        state.enemies[id] = existing;
        saveState();
    }

    function mergeContexts(existing, incoming) {
        const list = Array.isArray(existing) ? existing.slice() : [];
        if (incoming && !list.includes(incoming)) {
            list.unshift(incoming);
        }
        return list.slice(0, MAX_CONTEXTS);
    }

    function recordFightOutcome(enemyId, outcome) {
        const enemy = state.enemies[enemyId];
        if (!enemy) {
            return;
        }

        if (outcome === "win") {
            enemy.wins = (enemy.wins || 0) + 1;
        } else if (outcome === "loss") {
            enemy.losses = (enemy.losses || 0) + 1;
        } else if (outcome === "escape") {
            enemy.escapes = (enemy.escapes || 0) + 1;
        }

        enemy.updatedAt = Date.now();
        saveState();
        scanPage();
        refreshPanel();
    }

    function getRecordedBattleOutcome(entry) {
        const rawOutcome = cleanText(entry?.outcome || "");
        const summary = cleanText(entry?.summary || "");
        const detectedFromSummary = summary ? detectBattleOutcome([summary]) : "";
        return detectedFromSummary || rawOutcome;
    }

    function repairEnemyRecord(enemyId) {
        const enemy = state.enemies[enemyId];
        if (!enemy) {
            return;
        }
        const history = Array.isArray(enemy.battleHistory) ? enemy.battleHistory : [];
        const repairedHistory = history.map((entry) => ({
            ...entry,
            outcome: getRecordedBattleOutcome(entry)
        }));
        let wins = 0;
        let losses = 0;
        let escapes = 0;
        repairedHistory.forEach((entry) => {
            const normalized = normalizeBattleOutcomeForTally(entry.outcome);
            if (normalized === "win") {
                wins += 1;
            } else if (normalized === "loss") {
                losses += 1;
            } else if (normalized === "escape") {
                escapes += 1;
            }
        });
        enemy.battleHistory = repairedHistory;
        if (enemy.lastBattle) {
            enemy.lastBattle = {
                ...enemy.lastBattle,
                outcome: getRecordedBattleOutcome(enemy.lastBattle)
            };
        }
        enemy.wins = wins;
        enemy.losses = losses;
        enemy.escapes = escapes;
        enemy.updatedAt = Date.now();
        saveState();
        refreshPanel();
        scanPage();
    }

    function applyAutoBattleOutcomeTallies(enemy, snapshot, signature) {
        if (!enemy || !snapshot || enemy.lastAutoBattleSignature === signature) {
            return;
        }

        const normalized = normalizeBattleOutcomeForTally(snapshot.outcome);
        if (normalized === "win") {
            enemy.wins = (enemy.wins || 0) + 1;
        } else if (normalized === "loss") {
            enemy.losses = (enemy.losses || 0) + 1;
        } else if (normalized === "escape") {
            enemy.escapes = (enemy.escapes || 0) + 1;
        }

        enemy.lastAutoBattleSignature = signature;
    }

    function normalizeBattleOutcomeForTally(outcome) {
        const value = String(outcome || "").toLowerCase();
        if (value === "won" || value === "mugged" || value === "hospitalized") {
            return "win";
        }
        if (value === "lost") {
            return "loss";
        }
        if (value === "escaped") {
            return "escape";
        }
        return "";
    }

    function removeTrackedEnemy(enemyId) {
        const existing = state.enemies[enemyId];
        if (!existing) {
            return;
        }

        delete state.enemies[enemyId];
        if (existing.sourceType === "revenge") {
            state.settings.revengeIds = removeIdFromList(state.settings.revengeIds, enemyId);
        }
        saveState();
        scanPage();
        refreshPanel();
    }

    function scoreEnemy(enemy) {
        let score = 0;
        score += Math.min((enemy.level || 0) * 1.6, 45);
        score += Math.min((enemy.losses || 0) * 10, 30);
        score -= Math.min((enemy.wins || 0) * 6, 24);
        score += Math.min((enemy.escapes || 0) * 5, 15);
        score += Math.min(Math.round((enemy.lifeCurrent || 0) / 200), 8);
        score += availabilityScore(enemy);

        const statusText = String(enemy.manualStatus || enemy.status || "").toLowerCase();
        if (statusText.includes("okay") || statusText.includes("online")) {
            score += 10;
        }
        if (statusText.includes("hospital")) {
            score -= 18;
        }
        if (statusText.includes("travel") || statusText.includes("abroad")) {
            score -= 10;
        }
        if (statusText.includes("offline") || statusText.includes("idle")) {
            score -= 6;
        }
        if (statusText.includes("federal")) {
            score -= 20;
        }

        const actionStatus = String(enemy.lastAction?.status || enemy.lastAction?.relative || "").toLowerCase();
        if (actionStatus.includes("online") || actionStatus.includes("active")) {
            score += 10;
        }
        if (actionStatus.includes("offline")) {
            score -= 8;
        }

        for (const tag of enemy.tags || []) {
            if (tag === "priority" || tag === "dangerous") {
                score += 20;
            }
            if (tag === "watch") {
                score += 8;
            }
            if (tag === "inactive") {
                score -= 12;
            }
            if (tag === "easy") {
                score -= 18;
            }
            if (tag === "med") {
                score += 4;
            }
        }

        return Math.max(0, Math.min(99, Math.round(score)));
    }

    function renderBattleMemory(enemy) {
        const memory = enemy.lastBattle;
        if (!memory || (!memory.outcome && !Array.isArray(memory.weaponHints))) {
            return "";
        }
        const parts = [];
        const profile = classifyWeaponHints(memory.weaponHints);
        const normalizedOutcome = getRecordedBattleOutcome(memory);
        if (normalizedOutcome) {
            parts.push(escapeHtml(normalizedOutcome));
        }
        if (profile.label) {
            parts.push(`Profile: ${escapeHtml(profile.label)}`);
        }
        if (profile.detailLine) {
            parts.push(escapeHtml(profile.detailLine));
        } else if (Array.isArray(memory.weaponHints) && memory.weaponHints.length) {
            parts.push(`Weapons: ${memory.weaponHints.slice(0, 3).map(escapeHtml).join(", ")}`);
        }
        if (memory.summary) {
            parts.push(escapeHtml(memory.summary));
        }
        return `<div class="twet-meta twet-mini">${t("battleMemory")}: ${parts.join(" | ")}</div>`;
    }

    function renderBattleHistory(enemy) {
        const history = Array.isArray(enemy?.battleHistory) ? enemy.battleHistory : [];
        if (!history.length) {
            return "";
        }
        const summary = history.slice(0, 3).map((entry) => {
            const outcome = cleanText(getRecordedBattleOutcome(entry) || "Unknown");
            const age = entry?.capturedAt ? formatRelativeAge(entry.capturedAt) : "recently";
            return `${outcome} (${age})`;
        }).join(" | ");
        return `<div class="twet-meta twet-mini">${t("recentFights")}: ${escapeHtml(summary)}</div>`;
    }

    function buildEnemyReport(enemy) {
        if (!enemy) {
            return "";
        }
        const assessment = getMatchmakerAssessment(enemy);
        const availability = getAvailability(enemy);
        const lines = [];
        lines.push(`${enemy.name || `Unknown [${enemy.id}]`} [${enemy.id}]`);
        lines.push(`Matchmaker ${assessment.score} - ${assessment.decision}`);
        if (assessment.reason) {
            lines.push(assessment.reason);
        }
        lines.push(`Confidence: ${assessment.confidence} | Availability: ${assessment.availabilityLabel} | Risk: ${assessment.riskLabel}`);
        lines.push(`${enemy.sourceType === "revenge" ? t("revengeTarget") : t("warTarget")}`);
        if (enemy.level) {
            lines.push(`Level ${enemy.level}`);
        }
        if (enemy.faction) {
            lines.push(`Faction: ${decodeHtmlEntities(enemy.faction)}`);
        }
        lines.push(`${t("statusLabel")}: ${enemy.manualStatus || enemy.status || "Unknown"}`);
        lines.push(`${t("lastActionLabel")}: ${enemy.lastAction?.relative || enemy.lastAction?.status || "unknown"}`);
        if (enemy.lifeCurrent && enemy.lifeMaximum) {
            lines.push(formatTranslation("lifeFormat", { current: enemy.lifeCurrent, maximum: enemy.lifeMaximum }));
        }
        lines.push(`${t("fightLog")}: You won ${enemy.wins || 0} | Lost ${enemy.losses || 0} | Escaped ${enemy.escapes || 0}`);

        const memory = enemy.lastBattle;
        if (memory) {
            const memoryParts = [];
            const profile = classifyWeaponHints(memory.weaponHints);
            const normalizedOutcome = getRecordedBattleOutcome(memory);
            if (normalizedOutcome) {
                memoryParts.push(normalizedOutcome);
            }
            if (profile.label) {
                memoryParts.push(`Profile: ${profile.label}`);
            }
            if (profile.detailLine) {
                memoryParts.push(profile.detailLine);
            } else if (Array.isArray(memory.weaponHints) && memory.weaponHints.length) {
                memoryParts.push(`Weapons: ${memory.weaponHints.join(", ")}`);
            }
            if (memory.summary) {
                memoryParts.push(memory.summary);
            }
            if (memoryParts.length) {
                lines.push(`${t("battleMemory")}: ${memoryParts.join(" | ")}`);
            }
        }

        const history = Array.isArray(enemy.battleHistory) ? enemy.battleHistory : [];
        if (history.length) {
            const recent = history.slice(0, 3).map((entry) => {
                const outcome = cleanText(getRecordedBattleOutcome(entry) || "Unknown");
                const age = entry?.capturedAt ? formatRelativeAge(entry.capturedAt) : "recently";
                return `${outcome} (${age})`;
            }).join(" | ");
            lines.push(`${t("recentFights")}: ${recent}`);
        }

        const threatProfile = getBattleThreatProfile(getBattleMemorySignals(enemy));
        if (threatProfile) {
            lines.push(`Threat profile: ${threatProfile}`);
        }
        if (enemy.tags?.length) {
            lines.push(`Tags: ${enemy.tags.join(", ")}`);
        }
        if (enemy.notes) {
            lines.push(`Notes: ${enemy.notes}`);
        }
        lines.push(`Profile: ${profileUrl(enemy.id)}`);
        if (availability.kind === "available" || availability.kind === "soon") {
            lines.push(`Attack: ${attackUrl(enemy.id)}`);
        }
        return lines.filter(Boolean).join("\n");
    }

    function renderBattleThreatPills(enemy) {
        const battleSignals = getBattleMemorySignals(enemy);
        const threatProfile = getBattleThreatProfile(battleSignals);
        const pills = [];
        if (threatProfile) {
            pills.push(threatProfile);
        }
        if (battleSignals.hasMemory && enemy.lastBattle?.capturedAt) {
            pills.push(`Seen ${formatRelativeAge(enemy.lastBattle.capturedAt)}`);
        }
        if (battleSignals.tempLikely) {
            pills.push("Temps seen");
        }
        if (battleSignals.multiWeaponLikely) {
            pills.push("Multi-weapon");
        }
        if (!pills.length) {
            return "";
        }
        return `<div class="twet-threat-row">${pills.slice(0, 3).map((pill) => `<span class="twet-threat-pill">${escapeHtml(pill)}</span>`).join("")}</div>`;
    }

    function classifyWeaponHints(weaponHints) {
        const hints = Array.isArray(weaponHints) ? weaponHints.map((hint) => cleanText(hint)).filter(Boolean) : [];
        const temp = [];
        const firearm = [];
        const melee = [];
        const heavy = [];
        hints.forEach((hint) => {
            const lower = hint.toLowerCase();
            if (lower.includes("grenade") || lower.includes("spray") || lower.includes("temporary")) {
                temp.push(hint);
            }
            if (lower.includes("pistol") || lower.includes("rifle") || lower.includes("carbine") || lower.includes("shotgun") || lower.includes("qsz") || lower.includes("usp")) {
                firearm.push(hint);
            }
            if (lower.includes("cutlass") || lower.includes("knife") || lower.includes("sword") || lower.includes("bat") || lower.includes("axe") || lower.includes("hammer")) {
                melee.push(hint);
            }
            if (lower.includes("shotgun") || lower.includes("rifle") || lower.includes("launcher") || lower.includes("carbine")) {
                heavy.push(hint);
            }
        });
        const dedupe = (items) => [...new Set(items)];
        const tempList = dedupe(temp);
        const firearmList = dedupe(firearm);
        const meleeList = dedupe(melee);
        const heavyList = dedupe(heavy);
        const categories = [
            tempList.length ? "temps" : "",
            firearmList.length ? "firearms" : "",
            meleeList.length ? "melee" : ""
        ].filter(Boolean);
        let label = "";
        if (tempList.length && firearmList.length && meleeList.length) {
            label = "temp/firearm/melee mix";
        } else if (tempList.length && firearmList.length) {
            label = "temp + firearm setup";
        } else if (tempList.length && meleeList.length) {
            label = "temp + melee setup";
        } else if (firearmList.length && meleeList.length) {
            label = heavyList.length ? "heavy firearm + melee setup" : "firearm + melee setup";
        } else if (tempList.length) {
            label = "temporary-item setup";
        } else if (heavyList.length) {
            label = "heavy firearm setup";
        } else if (firearmList.length) {
            label = "firearm setup";
        } else if (meleeList.length) {
            label = "melee setup";
        }
        const detailParts = [];
        if (tempList.length) {
            detailParts.push(`Temps: ${tempList.join(", ")}`);
        }
        if (firearmList.length) {
            detailParts.push(`Firearms: ${firearmList.join(", ")}`);
        }
        if (meleeList.length) {
            detailParts.push(`Melee: ${meleeList.join(", ")}`);
        }
        return {
            label,
            detailLine: detailParts.slice(0, 3).join(" | "),
            categories
        };
    }

    function getBattleMemorySignals(enemy) {
        const memory = enemy?.lastBattle;
        const history = Array.isArray(enemy?.battleHistory) ? enemy.battleHistory : [];
        const empty = {
            hasMemory: false,
            recent: false,
            outcome: "",
            hasWeaponHints: false,
            tempLikely: false,
            heavyLikely: false,
            meleeLikely: false,
            firearmLikely: false,
            multiWeaponLikely: false,
            hintCount: 0,
            negativeOutcome: false,
            recentLosses: 0,
            recentWins: 0,
            recentEscapes: 0
        };
        if (!memory) {
            return empty;
        }
        const outcome = String(getRecordedBattleOutcome(memory) || "").toLowerCase();
        const weaponHints = Array.isArray(memory.weaponHints) ? memory.weaponHints.map((hint) => String(hint).toLowerCase()) : [];
        const profile = classifyWeaponHints(memory.weaponHints);
        const capturedAt = Number(memory.capturedAt) || 0;
        const recent = !!capturedAt && (Date.now() - capturedAt) < (2 * 24 * 60 * 60 * 1000);
        const tempLikely = profile.categories.includes("temps");
        const heavyLikely = profile.label.includes("heavy");
        const meleeLikely = profile.categories.includes("melee");
        const firearmLikely = profile.categories.includes("firearms");
        const multiWeaponLikely = weaponHints.length >= 3 || profile.categories.length >= 2;
        const negativeOutcome = outcome === "lost" || outcome === "escaped";
        const historyWindow = history.slice(0, 5);
        const recentLosses = historyWindow.filter((entry) => {
            const entryOutcome = String(getRecordedBattleOutcome(entry) || "").toLowerCase();
            return entryOutcome === "lost";
        }).length;
        const recentWins = historyWindow.filter((entry) => {
            const entryOutcome = String(getRecordedBattleOutcome(entry) || "").toLowerCase();
            return entryOutcome === "won" || entryOutcome === "mugged" || entryOutcome === "hospitalized";
        }).length;
        const recentEscapes = historyWindow.filter((entry) => String(getRecordedBattleOutcome(entry) || "").toLowerCase() === "escaped").length;
        return {
            hasMemory: true,
            recent,
            outcome,
            hasWeaponHints: weaponHints.length > 0,
            tempLikely,
            heavyLikely,
            meleeLikely,
            firearmLikely,
            multiWeaponLikely,
            hintCount: weaponHints.length,
            negativeOutcome,
            recentLosses,
            recentWins,
            recentEscapes
        };
    }

    function getBattleThreatProfile(battleSignals) {
        if (!battleSignals?.hasMemory) {
            return "";
        }
        if (battleSignals.tempLikely && battleSignals.multiWeaponLikely) {
            return "temp-heavy multi-weapon profile";
        }
        if (battleSignals.tempLikely) {
            return "temporary-item profile";
        }
        if (battleSignals.multiWeaponLikely) {
            return "multi-weapon profile";
        }
        if (battleSignals.heavyLikely && battleSignals.meleeLikely) {
            return "mixed firearm/melee profile";
        }
        if (battleSignals.heavyLikely) {
            return "heavy firearm profile";
        }
        if (battleSignals.meleeLikely) {
            return "aggressive melee profile";
        }
        if (battleSignals.firearmLikely) {
            return "firearm profile";
        }
        return "";
    }

    function getMatchmakerAssessment(enemy) {
        const availability = getAvailability(enemy);
        const protectedAlliance = getProtectedAlliance(enemy);
        const battleSignals = getBattleMemorySignals(enemy);
        const threatProfile = getBattleThreatProfile(battleSignals);
        const availabilityValue = availability.kind === "available" ? 38 : availability.kind === "soon" ? 25 : availability.kind === "hospital" ? 8 : 0;
        const activityText = String(enemy.lastAction?.status || enemy.lastAction?.relative || "").toLowerCase();
        const statusText = String(enemy.manualStatus || enemy.status || enemy.statusState || "").toLowerCase();
        const tags = enemy.tags || [];
        let priority = 0;
        let risk = 0;
        let opportunity = 0;

        if (enemy.sourceType === "revenge") {
            priority += 18;
        }
        priority += Math.min((enemy.losses || 0) * 5, 15);
        priority += Math.min((enemy.escapes || 0) * 5, 15);
        if (tags.includes("priority")) {
            priority += 18;
        }
        if (tags.includes("watch")) {
            priority += 6;
        }
        if (tags.includes("chain")) {
            priority += 10;
        }
        if (tags.includes("med")) {
            priority += 4;
        }
        if (activityText.includes("online") || activityText.includes("active")) {
            priority += 10;
            opportunity += 8;
        }
        if (activityText.includes("offline")) {
            priority -= 4;
        }
        if (statusText.includes("okay")) {
            priority += 8;
            opportunity += 5;
        }
        if (availability.kind === "soon") {
            priority += 8;
            opportunity += 6;
        }
        if (enemy.lifeCurrent && enemy.lifeMaximum) {
            const lifeRatio = enemy.lifeMaximum > 0 ? enemy.lifeCurrent / enemy.lifeMaximum : 1;
            if (lifeRatio < 0.35) {
                priority += 10;
                opportunity += 14;
            } else if (lifeRatio < 0.65) {
                priority += 4;
                opportunity += 6;
            } else if (lifeRatio > 0.95) {
                risk += 6;
            }
        }
        if (battleSignals.recent && (battleSignals.outcome === "won" || battleSignals.outcome === "mugged")) {
            priority += 8;
            opportunity += 10;
        }
        if (battleSignals.recentWins >= 2) {
            priority += 6;
            opportunity += 8;
        }

        risk += Math.min((enemy.losses || 0) * 9, 27);
        risk -= Math.min((enemy.wins || 0) * 6, 18);
        risk += Math.min(Math.max((enemy.level || 0) - 55, 0) / 1.8, 20);
        if (tags.includes("dangerous")) {
            risk += 18;
        }
        if (tags.includes("easy")) {
            risk -= 16;
        }
        if (tags.includes("inactive")) {
            risk -= 10;
        }
        if (availability.kind === "blocked" || availability.kind === "hospital") {
            risk += 10;
        }
        if (activityText.includes("online") || activityText.includes("active")) {
            risk += 3;
        }
        if (statusText.includes("travel") || statusText.includes("abroad") || statusText.includes("federal")) {
            risk += 12;
        }
        if (battleSignals.recent && battleSignals.outcome === "lost") {
            risk += 18;
        }
        if (battleSignals.recent && battleSignals.outcome === "escaped") {
            risk += 12;
        }
        if (battleSignals.tempLikely) {
            risk += 12;
        }
        if (battleSignals.heavyLikely) {
            risk += 8;
        }
        if (battleSignals.meleeLikely) {
            risk += 5;
        }
        if (battleSignals.multiWeaponLikely) {
            risk += 12;
        }
        if (battleSignals.tempLikely && battleSignals.meleeLikely && battleSignals.firearmLikely) {
            risk += 10;
        }
        if (battleSignals.hintCount >= 4) {
            risk += 6;
        }
        if (battleSignals.recent && (battleSignals.outcome === "won" || battleSignals.outcome === "mugged" || battleSignals.outcome === "hospitalized")) {
            risk -= 10;
        }
        if (battleSignals.recentLosses >= 2) {
            risk += 12;
        }
        if (battleSignals.recentEscapes >= 2) {
            risk += 8;
        }
        if (battleSignals.recentWins >= 2) {
            risk -= 6;
        }
        if (availability.kind === "available") {
            opportunity += 12;
        }
        if (risk >= 45) {
            opportunity -= 8;
        }
        if (risk >= 60) {
            opportunity -= 12;
        }

        const confidence = getMatchmakerConfidence(enemy);
        const confidenceLabel = confidence >= 0.75 ? "High" : confidence >= 0.45 ? "Medium" : "Low";
        const riskValue = Math.max(0, Math.min(99, Math.round(risk)));
        const riskLabel = riskValue >= 60 ? "High" : riskValue >= 30 ? "Medium" : "Low";

        if (protectedAlliance) {
            return {
                score: 0,
                decision: "Protected",
                className: "protected",
                confidence: confidenceLabel,
                reason: `FTAB protected alliance member: ${protectedAlliance.tag}.`,
                availabilityLabel: availability.label,
                riskLabel: "Blocked"
            };
        }

        if (availability.kind === "blocked") {
            return {
                score: 0,
                decision: "Blocked",
                className: "blocked",
                confidence: confidenceLabel,
                reason: `Target is not actionable right now: ${availability.label}.`,
                availabilityLabel: availability.label,
                riskLabel
            };
        }

        const score = Math.max(0, Math.min(99, Math.round(availabilityValue + priority + opportunity - risk + 28)));
        let decision = "Avoid";
        let className = "avoid";
        if ((battleSignals.negativeOutcome && (battleSignals.tempLikely || battleSignals.multiWeaponLikely)) || battleSignals.recentLosses >= 2 || (riskValue >= 72 && !battleSignals.recent)) {
            decision = "Avoid";
            className = "avoid";
        } else if (score >= 78 && riskValue < 58 && availability.kind === "available") {
            decision = "Greenlight";
            className = "greenlight";
        } else if (score >= 58 && riskValue < 72) {
            decision = "Playable";
            className = "playable";
        } else if (score >= 40) {
            decision = "Caution";
            className = "caution";
        }
        if (battleSignals.recent && battleSignals.outcome === "lost" && riskValue >= 55) {
            decision = "Avoid";
            className = "avoid";
        }
        if (availability.kind === "soon" && decision === "Greenlight") {
            decision = "Playable";
            className = "playable";
        }

        let reason = "Limited confidence. Review notes before committing.";
        if (battleSignals.recentLosses >= 2) {
            reason = "Multiple recent bad results against this target. Matchmaker is treating this as a repeat danger.";
        } else if (battleSignals.recent && battleSignals.outcome === "lost") {
            reason = battleSignals.tempLikely
                ? "Recent loss with temporary weapon signs. Respect this target unless you have a better setup."
                : battleSignals.multiWeaponLikely
                    ? "Recent bad result against a target using multiple weapon types. Matchmaker is downgrading this heavily."
                    : "Recent loss recorded here. Treat as a support hit or plan carefully.";
        } else if (battleSignals.recent && battleSignals.outcome === "escaped") {
            reason = battleSignals.multiWeaponLikely
                ? "Recent escape against a heavily armed target. Matchmaker sees this as a poor solo matchup."
                : "Recent escape suggests this target is harder to finish than they first appear.";
        } else if (battleSignals.recentWins >= 2 && riskLabel !== "High") {
            reason = "Recent battle history trends in your favor. Matchmaker is giving extra credit to the pattern.";
        } else if (battleSignals.recent && (battleSignals.outcome === "won" || battleSignals.outcome === "mugged" || battleSignals.outcome === "hospitalized")) {
            reason = battleSignals.heavyLikely
                ? "You recently beat this target, but weapon hints suggest they still have some bite."
                : "Recent visible success here makes this target look more manageable.";
        } else if (decision === "Avoid" && threatProfile) {
            reason = `Battle memory suggests a ${threatProfile}. Matchmaker is keeping this target out of the green.`;
        } else if ((enemy.losses || 0) >= 3) {
            reason = "Repeated losses recorded here. Treat as a support hit or plan carefully.";
        } else if (tags.includes("dangerous")) {
            reason = "Tagged dangerous. Matchmaker is intentionally holding the recommendation down.";
        } else if (availability.kind === "soon") {
            reason = "Target is nearly available. Queue the hit and be ready.";
        } else if (enemy.lifeCurrent && enemy.lifeMaximum && enemy.lifeCurrent / enemy.lifeMaximum < 0.35) {
            reason = "Target is softened up and currently presents a stronger opening.";
        } else if (tags.includes("priority") || enemy.sourceType === "revenge") {
            reason = "High-priority target with strong immediate value.";
        } else if (riskLabel === "Low" && availability.kind === "available") {
            reason = "Currently actionable with low recorded resistance.";
        } else if ((activityText.includes("online") || activityText.includes("active")) && riskLabel !== "Low") {
            reason = "Active target with some risk. Decide quickly if you want the window.";
        } else if (decision === "Avoid" && riskLabel === "High") {
            reason = "Current threat profile outweighs the opening. Leave this one unless circumstances change.";
        }

        return {
            score,
            decision,
            className,
            confidence: confidenceLabel,
            reason,
            availabilityLabel: availability.label,
            riskLabel
        };
    }

    function getMatchmakerConfidence(enemy) {
        let checks = 0;
        let met = 0;
        const battleSignals = getBattleMemorySignals(enemy);
        checks += 1;
        if (enemy.level) {
            met += 1;
        }
        checks += 1;
        if (enemy.status || enemy.manualStatus || enemy.statusState) {
            met += 1;
        }
        checks += 1;
        if (enemy.apiFetchedAt && Date.now() - enemy.apiFetchedAt < API_REFRESH_MS * 2) {
            met += 1;
        }
        checks += 1;
        if (enemy.lastAction?.status || enemy.lastAction?.relative) {
            met += 1;
        }
        checks += 1;
        if ((enemy.wins || 0) + (enemy.losses || 0) + (enemy.escapes || 0) > 0) {
            met += 1;
        }
        checks += 1;
        if (battleSignals.hasMemory && (battleSignals.recent || battleSignals.hasWeaponHints)) {
            met += 1;
        }
        return checks ? met / checks : 0;
    }

    function getProtectedAlliance(enemy) {
        if (enemy?.sourceFactionId) {
            return getAllianceByFactionId(enemy.sourceFactionId);
        }
        return null;
    }


    function extractPlayerId(url) {
        if (!url) {
            return "";
        }

        const match = String(url).match(/[?&]XID=(\d+)/i) || String(url).match(/[?&]ID=(\d+)/i);
        return match ? match[1] : "";
    }

    function parseTags(value) {
        const tags = String(value || "")
            .split(",")
            .map((tag) => tag.trim().toLowerCase())
            .filter(Boolean);

        const merged = [...new Set([...DEFAULT_TAGS.filter((tag) => tags.includes(tag)), ...tags])];
        return merged.slice(0, 8);
    }

    function renderAttackAssist(nextTargets) {
        const topTargets = nextTargets
            .filter((enemy) => ["available", "soon"].includes(getAvailability(enemy).kind))
            .slice(0, 3);
        const chainStatus = getChainStatusText();
        const targetMarkup = topTargets.length
            ? topTargets.map((enemy) => {
                const availability = getAvailability(enemy);
                return `
                    <div class="twet-card${String(state.ui.focusEnemyId || "") === String(enemy.id) ? " twet-card-focused" : ""}" data-enemy-card="${enemy.id}">
                        <div class="twet-row">
                            <a class="twet-name" data-enemy-id="${enemy.id}" href="${profileUrl(enemy.id)}" target="_blank" rel="noopener">${escapeHtml(enemy.name || `Player [${enemy.id}]`)}</a>
                            <span class="twet-availability ${availability.className}">${escapeHtml(availability.kind === "available" ? t("readyNow") : t("readySoon"))}</span>
                        </div>
                        <div class="twet-meta">${enemy.level ? formatTranslation("levelFormat", { level: enemy.level }) : t("levelUnknown")} | ${capitalize(t("scoreWord"))} ${scoreEnemy(enemy)} | ${t("hospLabel")} ${escapeHtml(getHospitalTimeText(enemy))}</div>
                        <div class="twet-actions">
                            <button class="twet-button" data-action="attack" data-enemy-id="${enemy.id}">${t("btnAttack")}</button>
                            <button class="twet-button" data-action="visit" data-enemy-id="${enemy.id}">${t("btnOpen")}</button>
                            <button class="twet-button" data-action="copy-attack" data-enemy-id="${enemy.id}">${t("btnCopy")}</button>
                        </div>
                    </div>
                `;
            }).join("")
            : `<div class="twet-card"><div class="twet-meta">${t("noImmediateTargets")}</div></div>`;

        return `
            <div class="twet-hero">
                <div class="twet-hero-title">${t("attackAssist")}</div>
                <div class="twet-hero-copy">${t("attackAssistCopy")}</div>
            </div>
            <div class="twet-section-title">${t("topActionable")}</div>
            ${targetMarkup}
            <div class="twet-card">
                <div class="twet-section-title">${t("chainTimer")}</div>
                <div class="twet-meta">${t("chainTimerCopy")}</div>
                <div class="twet-meta" style="margin-top: 8px;">
                    <input class="twet-input" data-role="chain-minutes" type="number" min="1" max="180" value="${normalizeChainMinutes(state.settings.chainMinutesPreset || 15)}" placeholder="${t("chainMinutes")}" />
                </div>
                <div class="twet-actions">
                    <button class="twet-button" data-action="start-chain">${t("chainStart")}</button>
                    <button class="twet-button" data-action="clear-chain">${t("chainClear")}</button>
                </div>
                <div class="twet-meta">${escapeHtml(chainStatus)}</div>
            </div>
            <div class="twet-card">
                <div class="twet-section-title">${t("alertSettings")}</div>
                <div class="twet-meta">${t("alertSettingsCopy")}</div>
                <div class="twet-pill-row">
                    <label class="twet-pill"><input type="checkbox" data-role="ready-alerts"${state.settings.readyAlerts === false ? "" : " checked"} /> ${t("enableReadyAlerts")}</label>
                    <label class="twet-pill"><input type="checkbox" data-role="soon-alerts"${state.settings.soonAlerts === false ? "" : " checked"} /> ${t("enableSoonAlerts")}</label>
                    <label class="twet-pill"><input type="checkbox" data-role="sound-alerts"${state.settings.soundAlerts ? " checked" : ""} /> ${t("enableSound")}</label>
                </div>
            </div>
        `;
    }

    function exportData() {
        const payload = JSON.stringify(state, null, 2);
        copyText(payload, "Enemy tracker data copied to clipboard.");
    }

    function importData() {
        const raw = window.prompt("Paste previously exported tracker JSON:");
        if (!raw) {
            return;
        }

        try {
            const parsed = JSON.parse(raw);
            state.enemies = parsed.enemies || {};
            state.ui = { ...state.ui, ...(parsed.ui || {}) };
            state.settings = normalizeSettings({ ...state.settings, ...(parsed.settings || {}) });
            saveState();
            scanPage();
            refreshPanel();
            alert("Enemy tracker data imported.");
        } catch (error) {
            console.error("TWET: import failed", error);
            alert("Import failed. The JSON could not be parsed.");
        }
    }

    function installObservers() {
        observer = new MutationObserver((mutations) => {
            if (suppressObserver) {
                return;
            }

            const hasRelevantMutation = mutations.some((mutation) => {
                const target = mutation.target;
                return !(target instanceof Node) || !panel?.contains(target);
            });

            if (!hasRelevantMutation) {
                return;
            }

            if (observerTimer) {
                window.clearTimeout(observerTimer);
            }

            observerTimer = window.setTimeout(() => {
                if (!isEditingPanel()) {
                    scanPage();
                }
            }, 250);
        });

        observer.observe(document.body, {
            childList: true,
            subtree: true
        });
    }

    function installKeyboardShortcuts() {
        document.addEventListener("keydown", (event) => {
            if (event.target instanceof HTMLInputElement || event.target instanceof HTMLTextAreaElement) {
                return;
            }

            if (event.key.toLowerCase() === "e" && event.altKey) {
                state.ui.collapsed = !state.ui.collapsed;
                saveState();
                syncPanelCollapsed();
            }
        });
    }

    function profileUrl(id) {
        return `https://www.torn.com/profiles.php?XID=${id}`;
    }

    function queueEnemyForApiRefresh(id, force = false) {
        const enemy = state.enemies[id];
        if (!enemy || !getApiKey()) {
            return;
        }

        if (!force && enemy.apiFetchedAt && Date.now() - enemy.apiFetchedAt < API_REFRESH_MS) {
            return;
        }

        if (inflightApiIds.has(id)) {
            return;
        }

        pendingApiIds.add(id);
    }

    function queueAllTrackedEnemiesForApiRefresh(force = false) {
        Object.keys(state.enemies).forEach((id) => queueEnemyForApiRefresh(id, force));
    }

    function syncFactionTargets() {
        const factionId = sanitizeFactionId(factionIdInput?.value || state.settings.targetFactionId || "");
        state.settings.targetFactionId = factionId;
        saveState();

        if (!factionId) {
            alert("Enter an enemy faction ID first.");
            return;
        }

        fetchFactionMembers(factionId);
    }

    function flushApiQueue() {
        if (!getApiKey() || !canMakeHttpRequests()) {
            return;
        }

        maybeRefreshSelfProfile();

        const ids = Array.from(pendingApiIds).slice(0, API_BATCH_LIMIT);
        ids.forEach((id) => {
            pendingApiIds.delete(id);
            fetchEnemyFromApi(id);
        });
    }

    function maybeRefreshSelfProfile(force = false) {
        if (selfProfileInflight || !getApiKey() || !canMakeHttpRequests()) {
            return;
        }
        if (!force && state.settings.selfProfileFetchedAt && Date.now() - state.settings.selfProfileFetchedAt < API_REFRESH_MS) {
            return;
        }
        fetchSelfProfile();
    }

    function fetchSelfProfile() {
        const apiKey = getApiKey();
        if (!apiKey) {
            return;
        }
        selfProfileInflight = true;
        const url = `https://api.torn.com/user/?selections=profile,battlestats,personalstats&key=${encodeURIComponent(apiKey)}`;
        sendHttpGet(url, {
            timeout: 15000,
            onload: (response) => {
                selfProfileInflight = false;
                handleSelfProfileResponse(response);
            },
            onerror: () => {
                selfProfileInflight = false;
            },
            ontimeout: () => {
                selfProfileInflight = false;
            }
        });
    }

    function handleSelfProfileResponse(response) {
        let payload;
        try {
            payload = JSON.parse(response.responseText);
        } catch (error) {
            return;
        }
        if (payload.error) {
            return;
        }
        state.settings.selfTravel = mapTravelData(payload) || null;
        state.settings.selfStatus = cleanText(payload?.status?.description || payload?.status?.details || payload?.status?.state || "");
        state.settings.selfStatusState = cleanText(payload?.status?.state || "");
        state.settings.selfBattleStats = mapSelfBattleStats(payload);
        state.settings.selfPersonalStats = mapSelfPersonalStats(payload);
        state.settings.selfProfileFetchedAt = Date.now();
        saveState();
        refreshPanel();
    }

    function mapSelfBattleStats(payload) {
        const strength = Number(payload?.strength) || 0;
        const defense = Number(payload?.defense) || 0;
        const speed = Number(payload?.speed) || 0;
        const dexterity = Number(payload?.dexterity) || 0;
        const total = strength + defense + speed + dexterity;
        if (!total) {
            return null;
        }
        return { strength, defense, speed, dexterity, total };
    }

    function mapSelfPersonalStats(payload) {
        const personalstats = payload?.personalstats;
        if (!personalstats || typeof personalstats !== "object") {
            return null;
        }
        return {
            attacksWon: Number(personalstats.attackswon ?? personalstats.attackwon) || 0,
            attacksLost: Number(personalstats.attackslost ?? personalstats.attacklost) || 0,
            attacksMugged: Number(personalstats.attacksmug ?? personalstats.mugs ?? personalstats.mugswon) || 0,
            attacksHospitalized: Number(personalstats.attackshosp ?? personalstats.hospitalize ?? personalstats.hospitalized) || 0
        };
    }

    function fetchEnemyFromApi(id) {
        const apiKey = getApiKey();
        if (!apiKey) {
            return;
        }
        inflightApiIds.add(id);
        const url = `https://api.torn.com/user/${id}?selections=profile&key=${encodeURIComponent(apiKey)}`;

        sendHttpGet(url, {
            timeout: 15000,
            onload: (response) => {
                inflightApiIds.delete(id);
                handleApiResponse(id, response);
            },
            onerror: () => {
                inflightApiIds.delete(id);
                setEnemyApiError(id, "network error");
            },
            ontimeout: () => {
                inflightApiIds.delete(id);
                setEnemyApiError(id, "request timeout");
            }
        });
    }

    function fetchFactionMembers(factionId) {
        const apiKey = getApiKey();
        if (!apiKey) {
            alert("Enter your YATA API key first.");
            return;
        }

        const basicUrl = `https://api.torn.com/faction/${factionId}?selections=basic&key=${encodeURIComponent(apiKey)}`;

        sendHttpGet(basicUrl, {
            timeout: 15000,
            onload: (response) => {
                handleFactionResponse(factionId, response, false);
            },
            onerror: () => {
                alert("Faction sync failed due to a network error.");
            },
            ontimeout: () => {
                alert("Faction sync timed out.");
            }
        });
    }

    function handleFactionResponse(factionId, response, triedMembersSelection) {
        let payload;
        try {
            payload = JSON.parse(response.responseText);
        } catch (error) {
            alert("Faction sync failed: invalid API response.");
            return;
        }

        if (payload.error) {
            alert(`Faction sync failed: API error ${payload.error.code}: ${payload.error.error}`);
            return;
        }

        const members = normalizeFactionMembers(payload);
        if (!members.length && !triedMembersSelection) {
            const membersUrl = `https://api.torn.com/faction/${factionId}?selections=members&key=${encodeURIComponent(getApiKey())}`;
            sendHttpGet(membersUrl, {
                timeout: 15000,
                onload: (retryResponse) => handleFactionResponse(factionId, retryResponse, true),
                onerror: () => alert("Faction sync failed while retrying member lookup."),
                ontimeout: () => alert("Faction sync timed out while retrying member lookup.")
            });
            return;
        }

        replaceEnemiesFromFaction(factionId, payload, members);
    }

    function normalizeFactionMembers(payload) {
        const source = payload.members;
        if (!source || typeof source !== "object") {
            return [];
        }

        return Object.entries(source).map(([id, member]) => ({
            id: String(id),
            name: normalizeName(member?.name) || `Player [${id}]`,
            level: Number(member?.level) || undefined,
            status: cleanText(
                member?.status?.description ||
                member?.status?.details ||
                member?.last_action?.status ||
                member?.last_action?.relative ||
                ""
            ),
            lastAction: member?.last_action ? {
                status: member.last_action.status || "",
                relative: member.last_action.relative || "",
                timestamp: member.last_action.timestamp || 0
            } : undefined,
            statusState: cleanText(member?.status?.state || ""),
            until: Number(member?.status?.until) || 0,
            lifeCurrent: Number(member?.life?.current) || 0,
            lifeMaximum: Number(member?.life?.maximum) || 0
        }));
    }

    function replaceEnemiesFromFaction(factionId, payload, members) {
        const nextEnemies = {};
        const factionName = payload.name || payload.faction_name || `Faction ${factionId}`;

        members.forEach((member) => {
            const existing = state.enemies[member.id] || {};
            nextEnemies[member.id] = {
                ...existing,
                id: member.id,
                sourceType: "war",
                name: member.name || existing.name || `Player [${member.id}]`,
                level: member.level || existing.level,
                faction: factionName,
                sourceFactionId: factionId,
                status: member.status || existing.status,
                statusState: member.statusState || existing.statusState,
                until: member.until || existing.until || 0,
                lastAction: member.lastAction || existing.lastAction,
                lifeCurrent: member.lifeCurrent || existing.lifeCurrent,
                lifeMaximum: member.lifeMaximum || existing.lifeMaximum,
                tags: existing.tags || [],
                notes: existing.notes || "",
                contexts: mergeContexts(existing.contexts, `faction ${factionId}`),
                createdAt: existing.createdAt || Date.now(),
                updatedAt: Date.now(),
                lastSeenAt: Date.now(),
                wins: existing.wins || 0,
                losses: existing.losses || 0,
                escapes: existing.escapes || 0,
                apiFetchedAt: existing.apiFetchedAt || 0,
                apiError: existing.apiError || ""
            };
        });

        Object.values(state.enemies).forEach((enemy) => {
            if (enemy.sourceType === "revenge") {
                nextEnemies[enemy.id] = enemy;
            }
        });

        state.enemies = nextEnemies;
        saveState();
        queueAllTrackedEnemiesForApiRefresh(true);
        refreshPanel();
        annotateTrackedLinks();
        flushApiQueue();
    }

    function handleApiResponse(id, response) {
        let payload;
        try {
            payload = JSON.parse(response.responseText);
        } catch (error) {
            setEnemyApiError(id, "invalid API response");
            return;
        }

        if (payload.error) {
            setEnemyApiError(id, `API error ${payload.error.code}: ${payload.error.error}`);
            return;
        }

        upsertEnemy(id, mapApiProfile(id, payload), "api", false);
        refreshPanel();
        annotateTrackedLinks();
    }

    function setEnemyApiError(id, message) {
        const enemy = state.enemies[id];
        if (!enemy) {
            return;
        }

        enemy.apiError = message;
        enemy.updatedAt = Date.now();
        saveState();
        refreshPanel();
    }

    function mapApiProfile(id, payload) {
        const level = Number(payload.level) || undefined;
        const factionName = typeof payload.faction === "object"
            ? payload.faction.faction_name || payload.faction.name || ""
            : "";
        const statusDescription = typeof payload.status === "object"
            ? payload.status.description || payload.status.details || payload.status.state || ""
            : "";
        const statusState = typeof payload.status === "object"
            ? payload.status.state || ""
            : "";
        const until = typeof payload.status === "object"
            ? Number(payload.status.until) || 0
            : 0;
        const lastAction = typeof payload.last_action === "object"
            ? {
                status: payload.last_action.status || "",
                relative: payload.last_action.relative || "",
                timestamp: payload.last_action.timestamp || 0
            }
            : undefined;
        const lifeCurrent = typeof payload.life === "object" ? Number(payload.life.current) || 0 : 0;
        const lifeMaximum = typeof payload.life === "object" ? Number(payload.life.maximum) || 0 : 0;

        return {
            id,
            sourceType: state.enemies[id]?.sourceType || "war",
            name: normalizeName(payload.name) || `Player [${id}]`,
            level,
            faction: factionName,
            status: cleanText(statusDescription),
            statusState: cleanText(statusState),
            until,
            lastAction,
            travel: mapTravelData(payload),
            lifeCurrent,
            lifeMaximum,
            apiFetchedAt: Date.now(),
            apiError: ""
        };
    }

    function mapTravelData(payload) {
        if (!payload || typeof payload !== "object") {
            return undefined;
        }
        const travel = typeof payload.travel === "object" && payload.travel ? payload.travel : {};
        const destination = cleanText(travel.destination || travel.location || travel.area || "");
        const status = cleanText(travel.status || travel.description || "");
        const timeLeft = Number(travel.time_left || travel.timeLeft || travel.remaining) || 0;
        if (!destination && !status && !timeLeft) {
            return undefined;
        }
        return { destination, status, timeLeft };
    }

    function getTravelLocation(enemy) {
        const statusText = String(enemy.manualStatus || enemy.status || "").toLowerCase();
        const stateText = String(enemy.statusState || "").toLowerCase();
        if (!(stateText.includes("travel") || statusText.includes("travel") || statusText.includes("abroad") || statusText.includes("flying"))) {
            return "";
        }
        const destination = cleanText(enemy.travel?.destination || "");
        if (destination) {
            return destination;
        }
        const travelStatus = cleanText(enemy.travel?.status || "");
        if (travelStatus && !/^(travel|abroad|flying)$/i.test(travelStatus)) {
            return travelStatus;
        }
        const fallback = cleanText(enemy.status || "");
        if (/to\s+[a-z]/i.test(fallback) || /in\s+[a-z]/i.test(fallback)) {
            return fallback;
        }
        return "";
    }

    function getTravelRemainingMs(enemy) {
        const timeLeft = Number(enemy?.travel?.timeLeft) || 0;
        const fetchedAt = Number(enemy?.apiFetchedAt) || 0;
        if (timeLeft <= 0 || fetchedAt <= 0) {
            return 0;
        }
        return Math.max(0, (timeLeft * 1000) - (Date.now() - fetchedAt));
    }

    function isTravelingEnemy(enemy) {
        const stateText = String(enemy?.statusState || "").toLowerCase();
        const statusText = String(enemy?.manualStatus || enemy?.status || "").toLowerCase();
        return stateText.includes("travel")
            || statusText.includes("travel")
            || statusText.includes("flying")
            || statusText.includes("returning")
            || stateText.includes("returning");
    }

    function getTravelAlertKey(enemy) {
        const destination = cleanText(enemy?.travel?.destination || getTravelLocation(enemy) || "unknown").toLowerCase();
        const fetchedAt = Number(enemy?.apiFetchedAt) || 0;
        const timeLeft = Number(enemy?.travel?.timeLeft) || 0;
        return `${destination}|${fetchedAt}|${timeLeft}`;
    }

    function getLocationKey(statusState, statusText, travel) {
        const stateText = String(statusState || "").toLowerCase();
        const status = String(statusText || "").toLowerCase();
        const destination = cleanText(travel?.destination || "");
        if (stateText.includes("travel") || status.includes("travel") || status.includes("flying") || status.includes("returning") || stateText.includes("returning")) {
            return destination ? `travel:${destination.toLowerCase()}` : "travel:unknown";
        }
        if (status.includes("abroad")) {
            return destination ? `abroad:${destination.toLowerCase()}` : "abroad:unknown";
        }
        return "city";
    }

    function isSameLocationAsSelf(enemy) {
        const selfLocation = getLocationKey(state.settings.selfStatusState, state.settings.selfStatus, state.settings.selfTravel);
        const targetLocation = getLocationKey(enemy.statusState, enemy.manualStatus || enemy.status, enemy.travel);
        if (selfLocation.startsWith("travel:")) {
            return false;
        }
        return selfLocation === targetLocation;
    }

    function renderSummary(summary, nextTargets, filteredEnemies) {
        const title = state.ui.activeTab === "revenge" ? t("nextRevengeTargets") : t("nextTargets");
        const nextTargetLines = nextTargets.length
            ? nextTargets.map((enemy) => {
                const availability = getAvailability(enemy);
                return `
                    <div class="twet-card${String(state.ui.focusEnemyId || "") === String(enemy.id) ? " twet-card-focused" : ""}" data-enemy-card="${enemy.id}">
                        <div class="twet-row">
                            <a class="twet-name" data-enemy-id="${enemy.id}" href="${profileUrl(enemy.id)}" target="_blank" rel="noopener">${escapeHtml(enemy.name || `Player [${enemy.id}]`)}</a>
                            <span class="twet-availability ${availability.className}">${escapeHtml(availability.label)}</span>
                        </div>
                        <div class="twet-meta">${enemy.level ? formatTranslation("levelFormat", { level: enemy.level }) : t("levelUnknown")} | ${t("hospLabel")} ${escapeHtml(getHospitalTimeText(enemy))} | ${capitalize(t("scoreWord"))} ${scoreEnemy(enemy)}</div>
                    </div>
                `;
            }).join("")
            : `<div class="twet-card"><div class="twet-meta">${t("noImmediateTargets")}</div></div>`;

        return `
            <div class="twet-summary-grid">
                <div class="twet-summary-box">
                    <div class="twet-summary-label">${t("availableNow")}</div>
                    <div class="twet-summary-value">${summary.availableNow}</div>
                </div>
                <div class="twet-summary-box">
                    <div class="twet-summary-label">${t("hospitalized")}</div>
                    <div class="twet-summary-value">${summary.hospitalized}</div>
                </div>
                <div class="twet-summary-box">
                    <div class="twet-summary-label">${t("blocked")}</div>
                    <div class="twet-summary-value">${summary.blocked}</div>
                </div>
                <div class="twet-summary-box">
                    <div class="twet-summary-label">${t("availableSoon")}</div>
                    <div class="twet-summary-value">${summary.availableSoon}</div>
                </div>
            </div>
            <div class="twet-card">
                <div class="twet-meta">${t("apiKey")}: ${state.settings.apiKey ? t("apiRedacted") : t("apiNotSet")}</div>
                <div class="twet-meta">${state.ui.activeTab === "revenge" ? `${t("revengeIds")}: ${escapeHtml(state.settings.revengeIds || t("apiNotSet"))}` : `${t("factionId")}: ${escapeHtml(state.settings.targetFactionId || t("apiNotSet"))}`}</div>
                <div class="twet-meta">${t("nextHospitalRelease")}: ${escapeHtml(summary.nextHospitalRelease)}</div>
            </div>
            <div class="twet-section-title">${title}</div>
            ${nextTargetLines}
            <div class="twet-section-title">${state.ui.activeTab === "revenge" ? t("allRevengeTargets") : t("allTargets")}</div>
        `;
    }

    function renderInstructionsTab() {
        return `
            <div class="twet-hero">
                <div class="twet-hero-title">${t("instructionsTitle")}</div>
                <div class="twet-hero-copy">This guide is the quickest way to understand what each tab does, how to load targets, and how to read the board without guesswork.</div>
            </div>
            <div class="twet-hero" style="border-color: rgba(148, 163, 184, 0.28); background: linear-gradient(135deg, rgba(30, 41, 59, 0.96), rgba(15, 23, 42, 0.98));">
                <div class="twet-hero-title">Matchmaker</div>
                <div class="twet-hero-copy">Matchmaker is the tracker's own target-reading layer. It blends live availability, your fight record, battle memory, and FTAB protection to help you decide who looks safe, risky, or not worth touching.</div>
            </div>
            <div class="twet-card">
                <div class="twet-section-title">Start Here</div>
                <div class="twet-meta"><strong>1.</strong> Enter your YATA API key.</div>
                <div class="twet-meta"><strong>2.</strong> Enter the enemy faction ID on the War tab, then press <strong>Sync faction</strong>.</div>
                <div class="twet-meta"><strong>3.</strong> The War board will load live targets, hospital timing, tracker score, and Matchmaker advice.</div>
                <div class="twet-meta"><strong>4.</strong> Use Search, Sort, and Filter when the list gets crowded.</div>
                <div class="twet-meta"><strong>5.</strong> Use the small copy icon beside a name when you want to share a clean report in Discord.</div>
            </div>
            <div class="twet-card">
                <div class="twet-section-title">What Each Tab Does</div>
                <div class="twet-meta"><strong>War</strong> is the main working board. It combines live faction targets with Matchmaker summaries and your normal attack workflow.</div>
                <div class="twet-meta"><strong>Revenge</strong> keeps manual revenge targets separate so they stay easy to find and do not clutter the War list.</div>
                <div class="twet-meta"><strong>FTAB</strong> manages protected factions, colours, labels, and do-not-hit profile warnings.</div>
                <div class="twet-meta"><strong>Instructions</strong> is this guide.</div>
            </div>
            <div class="twet-card">
                <div class="twet-section-title">How To Read A Target</div>
                <div class="twet-meta"><strong>Tracker score</strong> is the older urgency number. It still helps bring active or troublesome targets upward.</div>
                <div class="twet-meta"><strong>Matchmaker</strong> is the recommendation layer. It decides whether a target looks like <strong>Greenlight</strong>, <strong>Playable</strong>, <strong>Caution</strong>, <strong>Avoid</strong>, <strong>Blocked</strong>, or <strong>Protected</strong>.</div>
                <div class="twet-meta"><strong>Confidence</strong> tells you how much solid information Matchmaker is working from.</div>
                <div class="twet-meta"><strong>Risk</strong> tells you how dangerous the hit appears to be.</div>
                <div class="twet-meta"><strong>Availability</strong> tells you whether the target is ready, nearly ready, or awkward to hit right now.</div>
            </div>
            <div class="twet-card">
                <div class="twet-section-title">Battle Memory And Fight History</div>
                <div class="twet-meta">When a visible attack result can be read, the tracker stores the outcome and the enemy weapon clues it can see.</div>
                <div class="twet-meta"><strong>Battle memory</strong> shows the latest readable fight result.</div>
                <div class="twet-meta"><strong>Recent fights</strong> shows a short rolling history instead of only the last battle.</div>
                <div class="twet-meta">Automatic battle capture also updates the main <strong>W / L / E</strong> tally when the result is clear enough to classify.</div>
                <div class="twet-meta">Repeated bad results will drag a target down harder. Repeated wins can reduce risk and improve confidence.</div>
            </div>
            <div class="twet-card">
                <div class="twet-section-title">Using The Board Day To Day</div>
                <div class="twet-meta">Use <strong>Refresh player data</strong> when you want another API pass without rebuilding everything.</div>
                <div class="twet-meta">Use <strong>Refresh page links</strong> if you want the tracker to rescan the page you are on.</div>
                <div class="twet-meta">Targets on War and Revenge are intentionally held more steady so they do not jump around while you are reading them.</div>
                <div class="twet-meta">When you open a target from the board, the dashboard will try to return you to that same person once, then let you scroll away normally.</div>
                <div class="twet-meta">Revenge travelers can use the <strong>Landing alarm</strong> checkbox for a 1-minute warning and an arrival alert, with a per-target <strong>Silence</strong> button for that trip.</div>
                <div class="twet-meta">Use tags and notes freely. Matchmaker reads those too.</div>
            </div>
            <div class="twet-card">
                <div class="twet-section-title">Copying And Sharing Reports</div>
                <div class="twet-meta">The small copy icon beside a target name copies a Discord-friendly report card.</div>
                <div class="twet-meta">That report includes the target's name, Matchmaker result, reason, confidence, risk, status, W / L / E tally, latest battle memory, recent fights, and useful links.</div>
                <div class="twet-meta">It is the quickest way to pass battle findings, weapon use, and target warnings to other people.</div>
            </div>
            <div class="twet-card">
                <div class="twet-section-title">How Matchmaker Decides</div>
                <div class="twet-meta">Availability is checked first. Protected FTAB members and blocked targets are never treated like normal hits.</div>
                <div class="twet-meta">Priority rises for revenge targets, chain tags, priority tags, escapes, and enemies you have repeatedly lost to and may need to plan around.</div>
                <div class="twet-meta">Risk rises for high-level targets, dangerous tags, blocked states, visible mixed weapon setups, temps, and repeated losses.</div>
                <div class="twet-meta">Previous wins, easier tags, and cleaner recent success can lower risk and help push a target upward.</div>
                <div class="twet-meta">Confidence is shown separately so you can tell whether Matchmaker is working from fresh API data and usable encounter history or only partial information.</div>
            </div>
            <div class="twet-card">
                <div class="twet-section-title">Tracker Score Basics</div>
                <div class="twet-meta">${t("scoreOverview1")}</div>
                <div class="twet-meta">${t("scoreOverview2")}</div>
                <div class="twet-meta">${t("base1")}</div>
                <div class="twet-meta">${t("base2")}</div>
                <div class="twet-meta">${t("base3")}</div>
                <div class="twet-meta">${t("base4")}</div>
                <div class="twet-meta">${t("base5")}</div>
            </div>
            <div class="twet-card">
                <div class="twet-section-title">Availability, Status, Tags, And Final Numbers</div>
                <div class="twet-meta">${t("avail1")}</div>
                <div class="twet-meta">${t("avail2")}</div>
                <div class="twet-meta">${t("avail3")}</div>
                <div class="twet-meta">${t("avail4")}</div>
                <div class="twet-meta">${t("status1")}</div>
                <div class="twet-meta">${t("status2")}</div>
                <div class="twet-meta">${t("status3")}</div>
                <div class="twet-meta">${t("tag1")}</div>
                <div class="twet-meta">${t("tag2")}</div>
                <div class="twet-meta">${t("tag3")}</div>
                <div class="twet-meta">${t("tag4")}</div>
                <div class="twet-meta">${t("tag5")}</div>
                <div class="twet-meta">${t("final1")}</div>
                <div class="twet-meta">${t("final2")}</div>
                <div class="twet-meta">${t("final3")}</div>
                <div class="twet-meta">${t("final4")}</div>
            </div>
            <div class="twet-hero" style="border-color: rgba(125, 211, 252, 0.35); background: linear-gradient(135deg, rgba(6, 78, 59, 0.92), rgba(8, 47, 73, 0.96));">
                <div class="twet-hero-title">FTAB</div>
                <div class="twet-hero-copy">Faction Tags - All Bagged is built into this tracker to protect allied factions and make their territory presence easier to read.</div>
            </div>
            <div class="twet-card">
                <div class="twet-section-title">How FTAB Works</div>
                <div class="twet-meta">FTAB labels allied factions on faction pages and in city territory popups.</div>
                <div class="twet-meta">It recolors allied territory ownership on the city map using your saved alliance colors.</div>
                <div class="twet-meta">It shows a clear warning banner on allied member profile pages so they are not attacked by mistake.</div>
                <div class="twet-meta">The FTAB tab is where alliance names, tags, colors, faction IDs, and custom warning text are managed locally for this browser.</div>
                <div class="twet-meta">The header FTAB toggle turns all FTAB visuals on or off without affecting the rest of the tracker.</div>
                <div class="twet-meta">Bulk import format: <code>TAG | Alliance Name | #HEXCOLOR | Warning text | 123,456,789</code></div>
            </div>
            <div class="twet-card">
                <div class="twet-section-title">Current Build</div>
                <div class="twet-meta">This is TWAT Test <strong>${MATCHMAKER_VERSION}</strong>.</div>
                <div class="twet-meta">${t("runtimeLabel")}: ${isPdaEnvironment() ? t("runtimePda") : t("runtimeBrowser")}</div>
                <div class="twet-meta">It currently includes live war and revenge tracking, FTAB protection, one-click report copying, automatic battle-memory capture, recent fight history, and one-time return-to-target support.</div>
                <div class="twet-meta">Treat Matchmaker as guidance rather than a perfect promise. Your faction plan and your own judgement still matter.</div>
            </div>
        `;
    }

    function renderSettingsCard() {
        const hasEffectiveApiKey = !!getApiKey();
        const apiSource = state.settings.apiKey ? t("apiSourceStored") : (getPdaInjectedApiKey() ? t("apiSourcePda") : "");
        const apiReady = hasEffectiveApiKey ? t("apiRedacted") : t("apiRequired");
        const factionReady = state.settings.targetFactionId || t("apiRequired");
        const revengeReady = state.settings.revengeIds || t("apiRequired");
        const apiReadyText = apiSource && hasEffectiveApiKey ? `${apiReady} (${apiSource})` : apiReady;
        if (state.ui.activeTab === "revenge") {
            return `
                <div class="twet-hero twet-hero-revenge">
                    <div class="twet-hero-title">${t("revengeDashboard")}</div>
                    <div class="twet-hero-copy">${t("revengeCopy")}</div>
                    <div class="twet-field-label">${t("revengeInputLabel")}</div>
                    <div class="twet-settings-grid">
                        <input class="twet-input" data-role="api-key" placeholder="${hasEffectiveApiKey ? t("apiSavedPlaceholder") : t("apiPlaceholder")}" value="" />
                        <input class="twet-input" data-role="revenge-ids" placeholder="${t("revengeIdsPlaceholder")}" value="${escapeAttribute(state.settings.revengeIds || "")}" />
                    </div>
                    <div class="twet-id-helper">${t("idHelper")}</div>
                    <div class="twet-meta">${t("apiKey")}: ${escapeHtml(apiReadyText)} | ${t("revengeIds")}: ${escapeHtml(revengeReady)}</div>
                    <div class="twet-actions primary">
                        <button class="twet-button" data-action="add-revenge">${t("loadRevenge")}</button>
                        <button class="twet-button" data-action="refresh-api">${t("refreshRevengeData")}</button>
                        <button class="twet-button" data-action="reset-keep-api">${t("resetTracker")}</button>
                    </div>
                </div>
                ${renderSelfIntelCard()}
            `;
        }
        return `
            <div class="twet-hero">
                <div class="twet-hero-title">${t("warDashboard")}</div>
                <div class="twet-hero-copy">${t("warCopy")}</div>
                <div class="twet-settings-grid">
                    <input class="twet-input" data-role="api-key" placeholder="${hasEffectiveApiKey ? t("apiSavedPlaceholder") : t("apiPlaceholder")}" value="" />
                    <input class="twet-input" data-role="faction-id" placeholder="${t("factionPlaceholder")}" value="${escapeAttribute(state.settings.targetFactionId || "")}" />
                </div>
                <div class="twet-meta">${t("apiKey")}: ${escapeHtml(apiReadyText)} | ${t("factionId")}: ${escapeHtml(factionReady)}</div>
                <div class="twet-actions primary">
                    <button class="twet-button" data-action="sync-faction">${t("syncFaction")}</button>
                    <button class="twet-button" data-action="refresh-api">${t("refreshPlayerData")}</button>
                    <button class="twet-button" data-action="refresh-links">${t("refreshPageLinks")}</button>
                    <button class="twet-button" data-action="reset-keep-api">${t("resetTracker")}</button>
                </div>
            </div>
            ${renderSelfIntelCard()}
        `;
    }

    function renderSelfIntelCard() {
        if (!state.settings.selfProfileFetchedAt) {
            return "";
        }
        const selfBattleStats = state.settings.selfBattleStats || null;
        const selfPersonalStats = state.settings.selfPersonalStats || null;
        const travel = state.settings.selfTravel || null;
        const location = cleanText(travel?.destination || travel?.status || state.settings.selfStatusState || "Torn City");
        const status = cleanText(state.settings.selfStatus || state.settings.selfStatusState || "Unknown");
        const statLine = selfBattleStats
            ? `Total battle stats: ${formatNumber(selfBattleStats.total)} | STR ${formatNumber(selfBattleStats.strength)} | DEF ${formatNumber(selfBattleStats.defense)} | SPD ${formatNumber(selfBattleStats.speed)} | DEX ${formatNumber(selfBattleStats.dexterity)}`
            : t("selfBattleStatsUnavailable");
        const personalLine = selfPersonalStats
            ? `Personal stats: Won ${formatNumber(selfPersonalStats.attacksWon)} | Lost ${formatNumber(selfPersonalStats.attacksLost)}`
            : t("selfPersonalStatsUnavailable");
        return `
            <div class="twet-card">
                <div class="twet-section-title">${t("selfIntelTitle")}</div>
                <div class="twet-meta">${t("selfStatus")}: ${escapeHtml(status)}</div>
                <div class="twet-meta">${t("selfLocation")}: ${escapeHtml(location)}</div>
                <div class="twet-meta">${escapeHtml(statLine)}</div>
                <div class="twet-meta">${escapeHtml(personalLine)}</div>
                <div class="twet-meta twet-mini">${t("selfLastRefreshed")}: ${formatTime(state.settings.selfProfileFetchedAt)}</div>
            </div>
        `;
    }

    function bindSettingsInputs() {
        apiKeyInput = panelBody.querySelector('[data-role="api-key"]');
        factionIdInput = panelBody.querySelector('[data-role="faction-id"]');
        revengeIdInput = panelBody.querySelector('[data-role="revenge-ids"]');
        chainMinutesInput = panelBody.querySelector('[data-role="chain-minutes"]');
        ftabBulkInput = panelBody.querySelector('[data-role="ftab-bulk-input"]');
    }

    function rebuildPanel() {
        const searchValue = panelSearch?.value || "";
        const sortValue = sortSelect?.value || "score";
        const filterValue = filterSelect?.value || "all";
        if (panel) {
            panel.remove();
        }
        createPanel();
        if (panelSearch) {
            panelSearch.value = searchValue;
        }
        if (sortSelect) {
            sortSelect.value = sortValue;
        }
        if (filterSelect) {
            filterSelect.value = filterValue;
        }
        if (scaleInput) {
            scaleInput.value = String(Math.round(getPanelScale() * 100));
        }
        syncScaleLabel();
        updatePanelScale();
        refreshPanel();
    }

    function syncHeaderTabs() {
        if (!panel) {
            return;
        }
        panel.querySelectorAll(".twet-tab").forEach((tab) => {
            tab.classList.toggle("active", tab.dataset.tab === state.ui.activeTab);
        });
    }

    function isEditingPanel() {
        const active = document.activeElement;
        if (!active || !panel || !panel.contains(active)) {
            return false;
        }
        return active instanceof HTMLInputElement || active instanceof HTMLTextAreaElement || active instanceof HTMLSelectElement;
    }

    function getDefaultSettings() {
        return {
            targetFactionId: "",
            apiKey: "",
            revengeIds: "",
            language: "en",
            scale: 1,
            pinned: false,
            position: null,
            mobileMode: false,
            readyAlerts: true,
            soonAlerts: true,
            soundAlerts: false,
            chainMinutesPreset: 15,
            chainTimerEndsAt: 0,
            selfTravel: null,
            selfStatus: "",
            selfStatusState: "",
            selfBattleStats: null,
            selfPersonalStats: null,
            selfProfileFetchedAt: 0,
            ftab: buildDefaultFtabSettings()
        };
    }

    function normalizeSettings(settings) {
        return {
            ...getDefaultSettings(),
            ...(settings || {}),
            ftab: normalizeFtabSettings(settings?.ftab || {})
        };
    }

    function buildDefaultFtabSettings() {
        return {
            enabled: true,
            territoryColors: true,
            fullbright: true,
            hideMarkers: true,
            hideDefaultTerts: true,
            adminUnlocked: false,
            alliances: cloneDefaultFtabAlliances()
        };
    }

    function normalizeFtabSettings(settings) {
        const normalized = {
            ...buildDefaultFtabSettings(),
            ...(settings || {})
        };
        normalized.adminUnlocked = !!normalized.adminUnlocked;
        normalized.alliances = Array.isArray(normalized.alliances) && normalized.alliances.length
            ? normalized.alliances.map(normalizeAlliance)
            : cloneDefaultFtabAlliances();
        normalized.alliances = normalized.alliances.map(mergeNamelessDefaults);
        if (!normalized.alliances.length) {
            normalized.alliances = cloneDefaultFtabAlliances();
        }
        return normalized;
    }

    function cloneDefaultFtabAlliances() {
        return FTAB_DEFAULT_ALLIANCES.map((alliance) => normalizeAlliance(alliance));
    }

    function createBlankAlliance() {
        return { name: "New alliance", tag: "TAG", color: "#6B7280", customWarning: "", factionIds: [] };
    }

    function normalizeAlliance(alliance) {
        return {
            name: cleanText(alliance?.name || "Alliance"),
            tag: cleanText(alliance?.tag || "TAG"),
            color: normalizeColor(alliance?.color || "#6B7280"),
            customWarning: cleanText(alliance?.customWarning || ""),
            factionIds: parseFactionIds(alliance?.factionIds)
        };
    }

    function isNamelessAllianceOnly(alliance) {
        const name = cleanText(alliance?.name || "").toLowerCase();
        const tag = cleanText(alliance?.tag || "").toUpperCase();
        return name === "nameless" || tag === "NA";
    }

    function mergeNamelessDefaults(alliance) {
        const normalizedAlliance = normalizeAlliance(alliance);
        if (!isNamelessAllianceOnly(normalizedAlliance)) {
            return normalizedAlliance;
        }
        const defaultNameless = cloneDefaultFtabAlliances().find(isNamelessAllianceOnly);
        if (!defaultNameless) {
            return normalizedAlliance;
        }
        return {
            ...normalizedAlliance,
            name: "Nameless",
            tag: "NA",
            color: normalizedAlliance.color || defaultNameless.color,
            customWarning: normalizedAlliance.customWarning || defaultNameless.customWarning || "",
            factionIds: [...new Set([...(normalizedAlliance.factionIds || []), ...(defaultNameless.factionIds || [])])].sort((a, b) => a - b)
        };
    }

    function normalizeColor(value) {
        const match = String(value || "").trim().match(/^#?[0-9a-fA-F]{6}$/);
        return match ? `#${match[0].replace("#", "").toUpperCase()}` : "#6B7280";
    }

    function mixHexColor(colorA, colorB, weight = 0.5) {
        const hexA = normalizeColor(colorA).slice(1);
        const hexB = normalizeColor(colorB).slice(1);
        const ratio = Math.max(0, Math.min(1, Number(weight)));
        const mixed = [0, 2, 4].map((index) => {
            const a = Number.parseInt(hexA.slice(index, index + 2), 16);
            const b = Number.parseInt(hexB.slice(index, index + 2), 16);
            const value = Math.round((a * (1 - ratio)) + (b * ratio));
            return value.toString(16).padStart(2, "0");
        }).join("");
        return `#${mixed.toUpperCase()}`;
    }

    function parseFactionIds(value) {
        if (Array.isArray(value)) {
            return [...new Set(value.map((id) => Number(id)).filter((id) => Number.isInteger(id) && id > 0))];
        }
        return [...new Set((String(value || "").match(/\d+/g) || []).map((id) => Number(id)).filter((id) => Number.isInteger(id) && id > 0))];
    }

    function serializeFactionIds(value) {
        return parseFactionIds(value).join(", ");
    }

    function renderFtabTab() {
        const ftab = normalizeFtabSettings(state.settings.ftab || {});
        const locked = !ftab.adminUnlocked;
        return `
            <div class="twet-hero">
                <div class="twet-hero-title">Faction Tags - All Bagged</div>
                <div class="twet-hero-copy">Manage alliance labels and territory display from this tab. Changes stay local to this browser profile.</div>
                <div class="ftab-toggle-grid">
                    <label class="ftab-toggle"><input type="checkbox" data-role="ftab-toggle" data-key="enabled"${ftab.enabled ? " checked" : ""}> Enable FTAB labels</label>
                    <label class="ftab-toggle"><input type="checkbox" data-role="ftab-toggle" data-key="territoryColors"${ftab.territoryColors ? " checked" : ""}> Territory colors</label>
                    <label class="ftab-toggle"><input type="checkbox" data-role="ftab-toggle" data-key="fullbright"${ftab.fullbright ? " checked" : ""}> Full bright map</label>
                    <label class="ftab-toggle"><input type="checkbox" data-role="ftab-toggle" data-key="hideMarkers"${ftab.hideMarkers ? " checked" : ""}> Hide marker shadows</label>
                    <label class="ftab-toggle"><input type="checkbox" data-role="ftab-toggle" data-key="hideDefaultTerts"${ftab.hideDefaultTerts ? " checked" : ""}> Hide default territories</label>
                </div>
                ${locked ? `
                    <div class="twet-field-label">Admin access</div>
                    <div class="twet-actions primary">
                        <button class="twet-button" data-action="ftab-unlock">Unlock editor</button>
                    </div>
                    <div class="ftab-lock-copy">Editing is locked until you press Unlock editor.</div>
                ` : `
                    <div class="twet-actions primary">
                        <button class="twet-button" data-action="ftab-add-alliance">Add alliance</button>
                        <button class="twet-button" data-action="ftab-bulk-import">Bulk import</button>
                        <button class="twet-button" data-action="ftab-export">Export FTAB</button>
                        <button class="twet-button" data-action="ftab-clear-bulk">Clear bulk box</button>
                        <button class="twet-button" data-action="ftab-restore-defaults">Restore defaults</button>
                        <button class="twet-button" data-action="ftab-lock">Lock editor</button>
                    </div>
                    <div class="ftab-help-grid">
                        <div class="ftab-help-card"><strong>Bulk IDs</strong><br>Paste comma or space separated faction IDs.</div>
                        <div class="ftab-help-card"><strong>Warning text</strong><br>Set a custom warning line per alliance.</div>
                        <div class="ftab-help-card"><strong>Live color</strong><br>The same alliance color now styles the profile warning.</div>
                    </div>
                    <div class="twet-field-label">Bulk import format</div>
                    <textarea class="twet-textarea ftab-bulk-box" data-role="ftab-bulk-input" placeholder="One alliance per line. Example:&#10;OBN | OBN | #34912D | Do not attack unless approved | 19,231,1149&#10;WW | West World | #E48C1B | | 366,2095,6731">${escapeHtml(state.ui.ftabBulkDraft || "")}</textarea>
                `}
            </div>
            ${ftab.alliances.map((alliance, index) => `
                <div class="twet-card">
                    <div class="ftab-card-head">
                        <div>
                            <div class="twet-name">${escapeHtml(alliance.name || `Alliance ${index + 1}`)}</div>
                            <div class="twet-meta">Tag: [${escapeHtml(alliance.tag)}] | Factions: ${parseFactionIds(alliance.factionIds).length}</div>
                        </div>
                        <span class="ftab-preview-pill" style="background:${escapeAttribute(normalizeColor(alliance.color))}">[${escapeHtml(alliance.tag)}]</span>
                        ${locked ? "" : `<button class="twet-button" data-action="ftab-remove-alliance" data-ftab-index="${index}">Remove</button>`}
                    </div>
                    <div class="ftab-grid">
                        <input class="twet-input" data-role="ftab-alliance-field" data-field="name" data-ftab-index="${index}" value="${escapeAttribute(alliance.name)}" ${locked ? "readonly" : ""} placeholder="Alliance name" />
                        <input class="twet-input" data-role="ftab-alliance-field" data-field="tag" data-ftab-index="${index}" value="${escapeAttribute(alliance.tag)}" ${locked ? "readonly" : ""} placeholder="Tag" />
                        <input class="twet-input" data-role="ftab-alliance-field" data-field="color" data-ftab-index="${index}" type="color" value="${escapeAttribute(normalizeColor(alliance.color))}" ${locked ? "disabled" : ""} />
                        <div class="ftab-color-preview" style="background:${escapeAttribute(normalizeColor(alliance.color))}"></div>
                        <textarea class="twet-textarea ftab-id-list ftab-grid-wide" data-role="ftab-alliance-field" data-field="factionIds" data-ftab-index="${index}" ${locked ? "readonly" : ""} placeholder="Faction IDs, comma separated">${escapeHtml(serializeFactionIds(alliance.factionIds))}</textarea>
                        <textarea class="twet-textarea ftab-grid-wide" data-role="ftab-alliance-field" data-field="customWarning" data-ftab-index="${index}" ${locked ? "readonly" : ""} placeholder="Custom warning text for this alliance">${escapeHtml(alliance.customWarning || "")}</textarea>
                    </div>
                    <div class="twet-meta">Warning preview: ${escapeHtml(getAllianceWarningText(alliance))}</div>
                </div>
            `).join("")}
            ${locked ? `<div class="twet-card"><div class="ftab-readonly-note">Unlock FTAB to add, edit, or remove alliances.</div></div>` : ""}
        `;
    }

    function unlockFtabAdmin() {
        state.settings.ftab.adminUnlocked = true;
        saveState();
        refreshPanel();
    }

    function ensureFtabEditorUnlocked() {
        if (state.settings.ftab.adminUnlocked) {
            return true;
        }
        alert("Unlock the FTAB editor first.");
        return false;
    }

    function updateFtabAllianceField(input) {
        if (!state.settings.ftab.adminUnlocked) {
            return;
        }
        const index = Number(input.dataset.ftabIndex);
        const field = input.dataset.field;
        if (!Number.isInteger(index) || index < 0 || !field) {
            return;
        }
        const alliance = state.settings.ftab.alliances[index];
        if (!alliance) {
            return;
        }
        if (field === "name") {
            alliance.name = cleanText(input.value || alliance.name);
        } else if (field === "tag") {
            alliance.tag = cleanText(input.value || alliance.tag);
        } else if (field === "color") {
            alliance.color = normalizeColor(input.value);
        } else if (field === "customWarning") {
            alliance.customWarning = cleanText(input.value);
        } else if (field === "factionIds") {
            alliance.factionIds = parseFactionIds(input.value);
        }
        saveState();
        scanPage();
    }

    function importFtabBulk() {
        const raw = String(ftabBulkInput?.value || "").trim();
        if (!raw) {
            alert("Paste FTAB bulk lines first.");
            return;
        }
        const parsed = raw
            .split(/\r?\n/)
            .map((line) => line.trim())
            .filter(Boolean)
            .map(parseFtabBulkLine)
            .filter(Boolean);
        if (!parsed.length) {
            alert("No valid FTAB lines were found.");
            return;
        }
        state.settings.ftab.alliances = parsed;
        state.ui.ftabBulkDraft = "";
        saveState();
        refreshPanel();
        scanPage();
    }

    function exportFtabConfig() {
        const lines = normalizeFtabSettings(state.settings.ftab || {}).alliances.map((alliance) => (
            [
                alliance.tag || "",
                alliance.name || "",
                normalizeColor(alliance.color),
                alliance.customWarning || "",
                serializeFactionIds(alliance.factionIds)
            ].join(" | ")
        ));
        const payload = lines.join("\n");
        copyText(payload, "FTAB config copied.");
    }

    function clearFtabBulkInput() {
        if (ftabBulkInput) {
            ftabBulkInput.value = "";
        }
        state.ui.ftabBulkDraft = "";
        saveState();
    }

    function parseFtabBulkLine(line) {
        const parts = line.split("|").map((part) => part.trim());
        if (parts.length < 5) {
            return null;
        }
        return normalizeAlliance({
            tag: parts[0],
            name: parts[1] || parts[0],
            color: parts[2],
            customWarning: parts[3],
            factionIds: parts.slice(4).join("|")
        });
    }

    function initFtabPageFeatures() {
        if (!state.settings.ftab.enabled) {
            return;
        }
        if (/factions\.php/i.test(window.location.href)) {
            installFtabFactionObserver();
        }
        if (/city\.php/i.test(window.location.href)) {
            installFtabCityObservers();
        }
        if (/profiles\.php/i.test(window.location.href) || /sid=profiles/i.test(window.location.href)) {
            applyProfileWarning();
        }
    }

    function installFtabFactionObserver() {
        if (ftabFactionObserver) {
            applyFactionPageTag();
            return;
        }
        ftabFactionObserver = new MutationObserver(() => {
            applyFactionPageTag();
        });
        ftabFactionObserver.observe(document.documentElement || document, { childList: true, subtree: true });
        applyFactionPageTag();
    }

    function applyFactionPageTag() {
        const title = document.querySelector("div.faction-info-wrap.faction-profile > div.title-black");
        if (!title) {
            return;
        }
        const factionId = getFactionIDFromFactionPage();
        const tag = getAllianceTag(Number(factionId));
        const existingTagNode = title.querySelector(".ftab-page-tag");
        if (!tag) {
            if (existingTagNode) {
                existingTagNode.remove();
            }
            return;
        }
        const nextText = `[${tag}] `;
        if (existingTagNode) {
            if (existingTagNode.textContent !== nextText) {
                existingTagNode.textContent = nextText;
            }
            if (title.firstChild !== existingTagNode) {
                title.prepend(existingTagNode);
            }
            return;
        }
        const tagNode = document.createElement("span");
        tagNode.className = "ftab-page-tag";
        tagNode.textContent = nextText;
        title.prepend(tagNode);
    }

    function installFtabCityObservers() {
        if (!ftabCityRootObserver) {
            ftabCityRootObserver = new MutationObserver(() => {
                const popupPane = document.querySelector("div.leaflet-popup-pane");
                if (popupPane && !ftabCityPopupObserver) {
                    ftabCityPopupObserver = new MutationObserver(() => {
                        applyCityPopupTags();
                    });
                    ftabCityPopupObserver.observe(popupPane, { childList: true, subtree: true });
                }
                applyCityPopupTags();
            });
            ftabCityRootObserver.observe(document.documentElement || document, { childList: true, subtree: true });
        }
        applyCityPopupTags();
    }

    function applyCityPopupTags() {
        if (!state.settings.ftab.enabled) {
            return;
        }
        document.querySelectorAll("div.territory-dialogue-wrap").forEach((box) => {
            const factionLink = box.querySelector(':scope > .title > a.faction[href*="ID="]');
            if (!factionLink || /loading/i.test(factionLink.textContent || "")) {
                return;
            }
            const match = factionLink.getAttribute("href")?.match(/ID=(\d+)/);
            const factionId = match ? Number(match[1]) : 0;
            const tag = getAllianceTag(factionId);
            const existingBadge = factionLink.parentElement?.querySelector(".ftab-ali-tag");
            if (!tag) {
                if (existingBadge) {
                    existingBadge.remove();
                }
                return;
            }
            const nextText = `[${tag}]`;
            if (existingBadge) {
                if (existingBadge.textContent !== nextText) {
                    existingBadge.textContent = nextText;
                }
                if (existingBadge.previousElementSibling !== factionLink) {
                    factionLink.insertAdjacentElement("afterend", existingBadge);
                }
                return;
            }
            const badge = document.createElement("span");
            badge.className = "ftab-ali-tag";
            badge.textContent = nextText;
            factionLink.insertAdjacentElement("afterend", badge);
        });
    }

    function applyProfileWarning() {
        const profileId = getCurrentProfileId();
        const factionInfo = getProfileFactionInfo();
        if (!state.settings.ftab.enabled || !profileId || !factionInfo?.alliance) {
            const existingBanner = document.querySelector(".ftab-warning-banner");
            if (existingBanner) {
                existingBanner.remove();
            }
            ftabProfileWarningState = { lastProfileId: profileId || "", lastFactionId: factionInfo?.factionId ? String(factionInfo.factionId) : "", applied: false };
            return;
        }

        if (ftabProfileWarningState.applied && ftabProfileWarningState.lastProfileId === profileId && ftabProfileWarningState.lastFactionId === String(factionInfo.factionId)) {
            return;
        }

        const actionsHeader = findProfileActionsHeader();
        const existingBanner = document.querySelector(".ftab-warning-banner");
        if (!actionsHeader) {
            if (existingBanner) {
                existingBanner.remove();
            }
            return;
        }
        const allianceColor = normalizeColor(factionInfo.alliance.color);
        const warningText = getAllianceWarningText(factionInfo.alliance);
        const factionName = cleanText(factionInfo.factionName || factionInfo.alliance.name || "Unknown faction");
        const factionTag = cleanText(factionInfo.alliance.tag || "FTAB");
        const factionMeta = `${factionName} [${factionTag}]${factionInfo.factionId ? ` | Faction ID: ${escapeHtml(String(factionInfo.factionId))}` : ""}`;
        const nextHtml = `<strong>Do Not Attack</strong>${escapeHtml(warningText)}<span class="ftab-warning-meta">${escapeHtml(factionMeta)}</span>`;
        if (existingBanner) {
            if (existingBanner.innerHTML !== nextHtml) {
                existingBanner.innerHTML = nextHtml;
            }
            applyAllianceWarningTheme(existingBanner, allianceColor);
            if (existingBanner.previousElementSibling !== actionsHeader) {
                actionsHeader.insertAdjacentElement("afterend", existingBanner);
            }
            ftabProfileWarningState = { lastProfileId: profileId, lastFactionId: String(factionInfo.factionId), applied: true };
            return;
        }
        const banner = document.createElement("div");
        banner.className = "ftab-warning-banner";
        banner.innerHTML = nextHtml;
        applyAllianceWarningTheme(banner, allianceColor);
        actionsHeader.insertAdjacentElement("afterend", banner);
        ftabProfileWarningState = { lastProfileId: profileId, lastFactionId: String(factionInfo.factionId), applied: true };
    }

    function applyAllianceWarningTheme(element, color) {
        if (!element) {
            return;
        }
        const safeColor = normalizeColor(color);
        const darkTone = mixHexColor(safeColor, "#2B0B0B", 0.48);
        const darkerTone = mixHexColor(safeColor, "#111827", 0.58);
        const borderTone = mixHexColor(safeColor, "#FFFFFF", 0.22);
        const shadowTone = mixHexColor(safeColor, "#000000", 0.45);
        element.style.background = `linear-gradient(135deg, ${darkTone}, ${darkerTone})`;
        element.style.borderColor = borderTone;
        element.style.boxShadow = `0 10px 24px ${shadowTone}`;
    }

    function getAllianceWarningText(alliance) {
        const allianceName = cleanText(alliance?.name || "") || cleanText(alliance?.tag || "") || "FTAB";
        const allianceTag = cleanText(alliance?.tag || "") || "FTAB";
        const customText = cleanText(alliance?.customWarning || "");
        const legacyDefaults = new Set([
            `Allied faction member in ${allianceTag}. Don't attack/mug unless approved.`,
            `Allied Faction Member in ${allianceTag}. Do not attack/mug unless approved.`,
            `Allied faction member in ${allianceTag}. Do not attack/mug unless approved.`
        ]);
        if (customText && !legacyDefaults.has(customText)) {
            return customText;
        }
        return `Allied Faction Member in ${allianceName}. Do not attack/mug unless approved.`;
    }

    function findProfileActionsHeader() {
        const titleCandidates = document.querySelectorAll('.title-black, .title, .module-title, .cont-gray h4, .info-msg-cont h5, .sortable-box h4');
        for (const element of titleCandidates) {
            if (cleanText(element.textContent || "").toLowerCase() === "actions") {
                return element;
            }
        }
        return null;
    }

    function getCurrentProfileId() {
        const params = new URLSearchParams(window.location.search);
        const xid = params.get("XID") || params.get("ID");
        if (xid && !Number.isNaN(Number(xid))) {
            return String(Number(xid));
        }
        const matchedHref = document.querySelector('a[href*="user2ID="]')?.getAttribute("href") || "";
        const match = matchedHref.match(/user2ID=(\d+)/i);
        return match ? match[1] : "";
    }

    function getProfileFactionInfo() {
        const links = Array.from(document.querySelectorAll('a[href*="factions.php"][href*="ID="]'));
        for (const link of links) {
            const href = link.getAttribute("href") || "";
            const match = href.match(/ID=(\d+)/i);
            if (!match) {
                continue;
            }
            const factionId = Number(match[1]);
            const alliance = getAllianceByFactionId(factionId);
            if (!alliance) {
                continue;
            }
            return {
                factionId,
                factionName: cleanText(link.textContent || ""),
                alliance
            };
        }
        return null;
    }

    function removeFtabDecorations() {
        document.querySelectorAll(".ftab-ali-tag, .ftab-page-tag, .ftab-warning-banner").forEach((node) => node.remove());
        const map = document.getElementById("map-cont");
        const drawingRoot = document.querySelector(".d");
        if (map) {
            map.classList.remove("ftab-hideMarkers", "ftab-hideDefaultTerts");
        }
        if (drawingRoot) {
            drawingRoot.classList.remove("ftab-fullbright");
        }
    }

    function applyFtabVisualToggles() {
        const map = document.getElementById("map-cont");
        const drawingRoot = document.querySelector(".d");
        if (map) {
            map.classList.toggle("ftab-hideMarkers", !!state.settings.ftab.enabled && !!state.settings.ftab.hideMarkers);
            map.classList.toggle("ftab-hideDefaultTerts", !!state.settings.ftab.enabled && !!state.settings.ftab.hideDefaultTerts);
        }
        if (drawingRoot) {
            drawingRoot.classList.toggle("ftab-fullbright", !!state.settings.ftab.enabled && !!state.settings.ftab.fullbright);
        }
    }

    function installFtabAjaxHooks() {
        if (ftabAjaxHookInstalled) {
            return;
        }
        const attemptInstall = () => {
            if (ftabAjaxHookInstalled) {
                return;
            }
            if (!window.jQuery || typeof window.jQuery.ajaxSetup !== "function") {
                window.setTimeout(attemptInstall, 50);
                return;
            }
            ftabAjaxHookInstalled = true;
            window.jQuery.ajaxSetup({
                dataFilter(data) {
                    return filterFtabMapData(data);
                }
            });
        };
        attemptInstall();
    }

    function filterFtabMapData(data) {
        try {
            const json = JSON.parse(data);
            if (!json?.factionOwnTerritories) {
                return data;
            }
            if (json?.standardMapObjects) {
                injectStandardTerritoryStyles(Object.values(json.standardMapObjects).map((entry) => entry?.map_territory_id).filter(Boolean));
            }
            if (!state.settings.ftab.enabled || !state.settings.ftab.territoryColors) {
                return data;
            }
            const colors = buildFtabColorAssignments();
            json.factionOwnTerritories.colours[Number(FTAB_OTHER_COLOR_ID) - 1] = {
                ID: FTAB_OTHER_COLOR_ID,
                colour: FTAB_OTHER_COLOR
            };
            colors.forEach((assignment) => {
                json.factionOwnTerritories.colours[Number(assignment.colorId) - 1] = {
                    ID: assignment.colorId,
                    colour: assignment.color
                };
            });
            Object.keys(json.factionOwnTerritories.factionData || {}).forEach((factionId) => {
                applyFactionColor(json.factionOwnTerritories.factionData, factionId, FTAB_OTHER_COLOR, FTAB_OTHER_COLOR_ID);
                applyFactionColor(json.factionOwnTerritories.factionInWarData, factionId, FTAB_OTHER_COLOR, FTAB_OTHER_COLOR_ID);
                const assignment = colors.find((item) => item.factionIds.includes(Number(factionId)));
                if (!assignment) {
                    return;
                }
                applyFactionColor(json.factionOwnTerritories.factionData, factionId, assignment.color, assignment.colorId);
                applyFactionColor(json.factionOwnTerritories.factionInWarData, factionId, assignment.color, assignment.colorId);
            });
            return JSON.stringify(json);
        } catch (error) {
            return data;
        }
    }

    function injectStandardTerritoryStyles(territoryIds) {
        const selectors = territoryIds.map((dbId) => `.d #map-cont.ftab-hideDefaultTerts g.territories > path.shape[db_id="${dbId}"]`);
        if (!selectors.length) {
            return;
        }
        let style = document.getElementById("twet-ftab-standard-territories");
        if (!style) {
            style = document.createElement("style");
            style.id = "twet-ftab-standard-territories";
            document.head.appendChild(style);
        }
        style.textContent = `
            ${selectors.join(",\n")} {
                fill: #000 !important;
                fill-opacity: 0 !important;
            }
            ${selectors.map((selector) => `${selector}:not(.selected)`).join(",\n")} {
                stroke-width: 0 !important;
            }
        `;
    }

    function buildFtabColorAssignments() {
        return normalizeFtabSettings(state.settings.ftab || {}).alliances.map((alliance, index) => ({
            colorId: String(index + 15),
            color: normalizeColor(alliance.color),
            factionIds: parseFactionIds(alliance.factionIds)
        }));
    }

    function applyFactionColor(collection, factionId, color, colorId) {
        if (!collection || !collection[factionId]) {
            return;
        }
        collection[factionId].colour = color;
        collection[factionId].colourID = colorId;
    }

    function getAllianceTag(factionId) {
        return getAllianceByFactionId(factionId)?.tag || null;
    }

    function getAllianceByFactionId(factionId) {
        if (!state.settings.ftab.enabled) {
            return null;
        }
        const numericFactionId = Number(factionId);
        if (!Number.isInteger(numericFactionId)) {
            return null;
        }
        return normalizeFtabSettings(state.settings.ftab || {}).alliances.find((entry) => parseFactionIds(entry.factionIds).includes(numericFactionId)) || null;
    }

    function getFactionIDFromFactionPage() {
        const queryFactionId = new URLSearchParams(window.location.search).get("ID");
        if (queryFactionId && !Number.isNaN(Number(queryFactionId))) {
            return Number(queryFactionId);
        }
        const warsHref = document.querySelector("#top-page-links-list .view-wars")?.getAttribute("href") || "";
        const warsMatch = warsHref.match(/\/ranked\/(\d+)/);
        if (warsMatch) {
            return Number(warsMatch[1]);
        }
        const cityHref = document.querySelector('.faction-info .f-info a[href*="city.php#factionID="]')?.getAttribute("href") || "";
        const cityMatch = cityHref.match(/factionID=(\d+)/);
        return cityMatch ? Number(cityMatch[1]) : null;
    }

    function buildWarSummary(enemies) {
        const summary = { availableNow: 0, hospitalized: 0, blocked: 0, availableSoon: 0, nextHospitalRelease: "none" };
        let nextHospital = null;

        enemies.forEach((enemy) => {
            const availability = getAvailability(enemy);
            if (availability.kind === "available") {
                summary.availableNow += 1;
            } else if (availability.kind === "soon") {
                summary.availableSoon += 1;
            } else if (availability.kind === "hospital") {
                summary.hospitalized += 1;
                const ts = getAvailabilityTimestamp(enemy);
                if (ts && ts !== Number.MAX_SAFE_INTEGER && (!nextHospital || ts < nextHospital)) {
                    nextHospital = ts;
                }
            } else {
                summary.blocked += 1;
            }
        });

        if (nextHospital) {
            summary.nextHospitalRelease = formatCountdown(nextHospital - Date.now());
        }

        return summary;
    }

    function buildMatchmakerSummary(enemies) {
        const summary = { greenlights: 0, playable: 0, caution: 0, avoid: 0 };
        enemies.forEach((enemy) => {
            const decision = getMatchmakerAssessment(enemy).decision;
            if (decision === "Greenlight") {
                summary.greenlights += 1;
            } else if (decision === "Playable") {
                summary.playable += 1;
            } else if (decision === "Caution") {
                summary.caution += 1;
            } else {
                summary.avoid += 1;
            }
        });
        return summary;
    }

    function getTabEnemies(enemies) {
        if (state.ui.activeTab === "revenge") {
            return enemies.filter((enemy) => enemy.sourceType === "revenge");
        }
        return enemies.filter((enemy) => enemy.sourceType !== "revenge");
    }

    function getNextAvailableTargets(enemies) {
        const immediate = [];
        const soon = [];
        const rest = [];
        enemies.forEach((enemy) => {
            const availability = getAvailability(enemy);
            if (availability.kind === "available") {
                immediate.push(enemy);
            } else if (availability.kind === "soon") {
                soon.push(enemy);
            } else {
                rest.push(enemy);
            }
        });
        return immediate.concat(soon, rest);
    }

    function getAvailability(enemy) {
        const stateText = String(enemy.statusState || "").toLowerCase();
        const statusText = String(enemy.manualStatus || enemy.status || "").toLowerCase();
        const until = Number(enemy.until) || 0;
        const msRemaining = until > 0 ? (until * 1000) - Date.now() : 0;
        const travelLocation = getTravelLocation(enemy);

        if (stateText.includes("hospital") || statusText.includes("hospital")) {
            if (msRemaining > 0 && msRemaining <= 15 * 60 * 1000) {
                return { kind: "soon", className: "soon", label: `Hosp ${formatCountdown(msRemaining)}` };
            }
            return { kind: "hospital", className: "blocked", label: msRemaining > 0 ? `Hosp ${formatCountdown(msRemaining)}` : "Hospital" };
        }
        if (stateText.includes("travel") || statusText.includes("travel") || statusText.includes("flying")) {
            return { kind: "blocked", className: "blocked", label: travelLocation ? `Travel: ${travelLocation}` : "Travel" };
        }
        if (statusText.includes("abroad")) {
            if (!isSameLocationAsSelf(enemy)) {
                return { kind: "blocked", className: "blocked", label: travelLocation ? `Abroad: ${travelLocation}` : "Abroad" };
            }
            return { kind: "available", className: "available", label: travelLocation ? `Abroad: ${travelLocation}` : "Abroad" };
        }
        if (stateText.includes("jail") || statusText.includes("jail")) {
            return { kind: "blocked", className: "blocked", label: "Jail" };
        }
        if (stateText.includes("federal") || statusText.includes("federal")) {
            return { kind: "blocked", className: "blocked", label: "Federal" };
        }
        if (!isSameLocationAsSelf(enemy)) {
            return { kind: "blocked", className: "blocked", label: "Different location" };
        }
        if (stateText.includes("okay") || statusText.includes("okay") || stateText.includes("online")) {
            return { kind: "available", className: "available", label: "Ready" };
        }
        if (enemy.lastAction?.status && String(enemy.lastAction.status).toLowerCase().includes("online")) {
            return { kind: "available", className: "available", label: "Online" };
        }
        return { kind: "available", className: "available", label: "Likely up" };
    }

    function getHospitalTimeText(enemy) {
        const until = Number(enemy.until) || 0;
        const remaining = until > 0 ? (until * 1000) - Date.now() : 0;
        if (remaining > 0) {
            return formatCountdown(remaining);
        }
        if (String(enemy.statusState || enemy.status || "").toLowerCase().includes("hospital")) {
            return enemy.hospitalUntil || "unknown";
        }
        return "none";
    }

    function availabilityRank(enemy) {
        const kind = getAvailability(enemy).kind;
        if (kind === "available") {
            return 0;
        }
        if (kind === "soon") {
            return 1;
        }
        if (kind === "hospital") {
            return 2;
        }
        return 3;
    }

    function getAvailabilityTimestamp(enemy) {
        const availability = getAvailability(enemy);
        if (availability.kind === "available") {
            return 0;
        }
        const until = Number(enemy.until) || 0;
        return until > 0 ? until * 1000 : Number.MAX_SAFE_INTEGER;
    }

    function availabilityScore(enemy) {
        if (enemy.sourceType === "revenge") {
            return 24;
        }
        const availability = getAvailability(enemy);
        if (availability.kind === "available") {
            return 18;
        }
        if (availability.kind === "soon") {
            return 8;
        }
        if (availability.kind === "hospital") {
            return -18;
        }
        return -22;
    }

    function formatCountdown(ms) {
        if (!ms || ms <= 0) {
            return "now";
        }
        const totalSeconds = Math.floor(ms / 1000);
        const hours = Math.floor(totalSeconds / 3600);
        const minutes = Math.floor((totalSeconds % 3600) / 60);
        const seconds = totalSeconds % 60;
        if (hours > 0) {
            return `${hours}h ${minutes}m`;
        }
        if (minutes > 0) {
            return `${minutes}m ${seconds}s`;
        }
        return `${seconds}s`;
    }

    function normalizeName(value) {
        const cleaned = cleanText(value);
        return cleaned.replace(/\[\d+\]/g, "").trim();
    }

    function sanitizeFactionId(value) {
        const match = String(value || "").match(/\d+/);
        return match ? match[0] : "";
    }

    function sanitizeIdList(value) {
        return [...new Set(String(value || "").match(/\d+/g) || [])].join(",");
    }

    function removeIdFromList(value, targetId) {
        const normalizedTargetId = String(targetId || "").trim();
        if (!normalizedTargetId) {
            return sanitizeIdList(value);
        }
        return (sanitizeIdList(value) || "")
            .split(",")
            .filter((id) => id && id !== normalizedTargetId)
            .join(",");
    }

    function addRevengeTargets() {
        syncSettingsInputsForActions();
        const apiKey = getApiKey();
        if (!apiKey) {
            alert("Enter your YATA API key first.");
            return;
        }

        const ids = (sanitizeIdList(revengeIdInput?.value || state.settings.revengeIds || "") || "")
            .split(",")
            .filter(Boolean);

        state.settings.revengeIds = ids.join(",");
        saveState();

        if (!ids.length) {
            alert("Enter at least one player ID for revenge targets.");
            return;
        }

        ids.forEach((id) => {
            const existing = state.enemies[id] || {};
            state.enemies[id] = {
                ...existing,
                id,
                sourceType: "revenge",
                name: existing.name || `Player [${id}]`,
                tags: [...new Set([...(existing.tags || []), "revenge"])],
                notes: existing.notes || "",
                contexts: existing.contexts || [],
                createdAt: existing.createdAt || Date.now(),
                updatedAt: Date.now(),
                lastSeenAt: existing.lastSeenAt || Date.now(),
                wins: existing.wins || 0,
                losses: existing.losses || 0,
                escapes: existing.escapes || 0
            };
            queueEnemyForApiRefresh(id, true);
        });

        saveState();
        refreshPanel();
        flushApiQueue();
    }

    function resetKeepApi() {
        syncSettingsInputsForActions();
        const apiKey = state.settings.apiKey || "";
        const language = getLanguage();
        const revengeIds = sanitizeIdList(state.settings.revengeIds || "");
        const revengeEnemies = Object.fromEntries(
            Object.entries(state.enemies).filter(([, enemy]) => enemy.sourceType === "revenge")
        );
        state.enemies = revengeEnemies;
        state.ui = {
            collapsed: !!state.ui.collapsed,
            activeTab: "war",
            mobileToolbarCollapsed: !!state.ui.mobileToolbarCollapsed,
            mobileControlsCollapsed: !!state.ui.mobileControlsCollapsed,
            orderCache: {},
            focusEnemyId: "",
            pendingFocusRestore: false,
            scrollByTab: {},
            ftabBulkDraft: "",
            hideAvoids: false,
            hideCautions: false,
            hideGreenlights: false
        };
        state.settings = {
            ...getDefaultSettings(),
            ...state.settings,
            apiKey,
            targetFactionId: "",
            revengeIds,
            language,
            scale: getPanelScale(),
            pinned: !!state.settings.pinned,
            position: state.settings.position || null,
            ftab: normalizeFtabSettings(state.settings.ftab || {})
        };
        pendingApiIds.clear();
        inflightApiIds.clear();
        saveState();
        refreshPanel();
    }

    function sanitizeApiKey(value) {
        return String(value || "").replace(/[^A-Za-z0-9]/g, "").trim();
    }

    function syncLiveSettingInputs() {
        if (apiKeyInput?.value) {
            state.settings.apiKey = sanitizeApiKey(apiKeyInput.value);
        }
        if (factionIdInput && document.activeElement === factionIdInput) {
            state.settings.targetFactionId = sanitizeFactionId(factionIdInput.value || "");
        }
        if (revengeIdInput && document.activeElement === revengeIdInput) {
            state.settings.revengeIds = sanitizeIdList(revengeIdInput.value || "");
        }
    }

    function syncSettingsInputsForActions() {
        if (apiKeyInput?.value) {
            state.settings.apiKey = sanitizeApiKey(apiKeyInput.value);
        }
        if (factionIdInput) {
            state.settings.targetFactionId = sanitizeFactionId(factionIdInput.value || "");
        }
        if (revengeIdInput) {
            state.settings.revengeIds = sanitizeIdList(revengeIdInput.value || "");
        }
        saveState();
    }

    function normalizeLanguage(value) {
        return TRANSLATIONS[value] ? value : "en";
    }

    function normalizeScale(value) {
        const numeric = Number(value);
        if (!Number.isFinite(numeric)) {
            return 1;
        }
        return Math.min(1.4, Math.max(0.8, numeric / 100));
    }

    function normalizePosition(position) {
        if (!position || !Number.isFinite(Number(position.left)) || !Number.isFinite(Number(position.top))) {
            return null;
        }
        return {
            left: Math.max(0, Math.round(Number(position.left))),
            top: Math.max(0, Math.round(Number(position.top)))
        };
    }

    function getPanelScale() {
        const scale = normalizeScale((Number(state.settings.scale) || 1) * 100);
        return state.settings.mobileMode ? Math.min(scale, 1) : scale;
    }

    function syncScaleLabel() {
        const scaleValue = panel?.querySelector('[data-role="scale-value"]');
        if (scaleValue) {
            scaleValue.textContent = `${Math.round(getPanelScale() * 100)}%`;
        }
    }

    function updatePanelScale() {
        if (!panel) {
            return;
        }
        if (state.ui.collapsed) {
            panel.style.transform = "";
            panel.style.transformOrigin = "";
            return;
        }
        panel.style.transform = `scale(${getPanelScale()})`;
        panel.style.transformOrigin = state.settings.pinned ? "top left" : "top right";
    }

    function updatePanelPosition() {
        if (!panel) {
            return;
        }
        const position = normalizePosition(state.settings.position);
        if (state.settings.pinned && position) {
            const scale = state.ui.collapsed ? 1 : getPanelScale();
            const width = panel.offsetWidth * scale;
            const height = panel.offsetHeight * scale;
            const maxLeft = Math.max(0, window.innerWidth - width);
            const maxTop = Math.max(0, window.innerHeight - height);
            const clampedLeft = Math.min(maxLeft, Math.max(0, position.left));
            const clampedTop = Math.min(maxTop, Math.max(0, position.top));
            if (clampedLeft !== position.left || clampedTop !== position.top) {
                state.settings.position = { left: Math.round(clampedLeft), top: Math.round(clampedTop) };
                saveState();
            }
            panel.style.left = `${clampedLeft}px`;
            panel.style.top = `${clampedTop}px`;
            panel.style.right = "auto";
        } else {
            panel.style.left = "auto";
            panel.style.top = state.settings.mobileMode ? "12px" : "80px";
            panel.style.right = state.settings.mobileMode ? "12px" : "16px";
        }
    }

    function togglePin() {
        if (!panel) {
            return;
        }
        const nextPinned = !state.settings.pinned;
        state.settings.pinned = nextPinned;
        if (nextPinned) {
            const rect = panel.getBoundingClientRect();
            state.settings.position = normalizePosition({ left: rect.left, top: rect.top }) || { left: 16, top: 80 };
        } else {
            state.settings.position = null;
        }
        saveState();
        syncPanelCollapsed();
    }

    function handleHeaderMouseDown(event) {
        if (!state.settings.pinned || state.ui.collapsed) {
            return;
        }
        if (event.button !== 0) {
            return;
        }
        if (event.target.closest("button, input, select, textarea, a, .twet-tabs")) {
            return;
        }
        const rect = panel.getBoundingClientRect();
        dragState = {
            offsetX: event.clientX - rect.left,
            offsetY: event.clientY - rect.top
        };
        document.addEventListener("mousemove", handleHeaderMouseMove);
        document.addEventListener("mouseup", handleHeaderMouseUp);
        event.preventDefault();
    }

    function handleHeaderMouseMove(event) {
        if (!dragState || !panel) {
            return;
        }
        const scale = getPanelScale();
        const width = panel.offsetWidth * (state.ui.collapsed ? 1 : scale);
        const height = panel.offsetHeight * (state.ui.collapsed ? 1 : scale);
        const maxLeft = Math.max(0, window.innerWidth - width);
        const maxTop = Math.max(0, window.innerHeight - height);
        const nextLeft = Math.min(maxLeft, Math.max(0, event.clientX - dragState.offsetX));
        const nextTop = Math.min(maxTop, Math.max(0, event.clientY - dragState.offsetY));
        state.settings.position = { left: Math.round(nextLeft), top: Math.round(nextTop) };
        updatePanelPosition();
    }

    function handleHeaderMouseUp() {
        if (!dragState) {
            return;
        }
        dragState = null;
        saveState();
        document.removeEventListener("mousemove", handleHeaderMouseMove);
        document.removeEventListener("mouseup", handleHeaderMouseUp);
    }

    function getLanguage() {
        return normalizeLanguage(state.settings.language || "en");
    }

    function t(key) {
        const language = getLanguage();
        return TRANSLATIONS[language]?.[key] || TRANSLATIONS.en[key] || key;
    }

    function formatTranslation(key, replacements) {
        let text = t(key);
        Object.entries(replacements || {}).forEach(([replacementKey, replacementValue]) => {
            text = text.replace(`{${replacementKey}}`, String(replacementValue));
        });
        return text;
    }

    function capitalize(value) {
        const text = String(value || "");
        return text ? text.charAt(0).toUpperCase() + text.slice(1) : text;
    }

    function getApiKey() {
        return sanitizeApiKey(state.settings.apiKey || getPdaInjectedApiKey() || "");
    }

    function attackUrl(id) {
        return `https://www.torn.com/loader.php?sid=attack&user2ID=${id}`;
    }

    function copyText(value, successMessage = t("copiedProfile")) {
        if (typeof GM_setClipboard === "function") {
            GM_setClipboard(String(value), "text");
            alert(successMessage);
            return;
        }
        if (copyTextWithExecCommand(String(value))) {
            alert(successMessage);
            return;
        }
        if (navigator.clipboard?.writeText) {
            navigator.clipboard.writeText(String(value)).then(() => {
                alert(successMessage);
            }).catch(() => {
                window.prompt("Copy this value:", String(value));
            });
            return;
        }
        window.prompt("Copy this value:", String(value));
    }

    function getPdaInjectedApiKey() {
        const raw = String(PDA_APIKEY_PLACEHOLDER || "").trim();
        if (!raw || raw.startsWith("#")) {
            return "";
        }
        return sanitizeApiKey(raw);
    }

    function isPdaEnvironment() {
        return typeof window.PDA_httpGet === "function" || typeof window.PDA_httpPost === "function" || !!getPdaInjectedApiKey();
    }

    function canMakeHttpRequests() {
        return typeof GM_xmlhttpRequest === "function" || typeof window.PDA_httpGet === "function" || typeof fetch === "function";
    }

    function sendHttpGet(url, handlers = {}) {
        const timeout = Number(handlers.timeout) || 15000;
        if (typeof GM_xmlhttpRequest === "function") {
            GM_xmlhttpRequest({
                method: "GET",
                url,
                timeout,
                onload: (response) => handlers.onload?.(normalizeHttpResponse(response)),
                onerror: (error) => handlers.onerror?.(error),
                ontimeout: (error) => handlers.ontimeout?.(error)
            });
            return;
        }
        if (typeof window.PDA_httpGet === "function") {
            Promise.resolve(window.PDA_httpGet(url)).then((response) => {
                handlers.onload?.(normalizeHttpResponse(response));
            }).catch((error) => {
                handlers.onerror?.(error);
            });
            return;
        }
        if (typeof fetch === "function") {
            const controller = typeof AbortController === "function" ? new AbortController() : null;
            const timeoutId = controller
                ? window.setTimeout(() => {
                    controller.abort();
                    handlers.ontimeout?.({ message: "timeout" });
                }, timeout)
                : 0;
            fetch(url, {
                method: "GET",
                credentials: "omit",
                signal: controller?.signal
            }).then(async (response) => {
                if (timeoutId) {
                    window.clearTimeout(timeoutId);
                }
                handlers.onload?.(normalizeHttpResponse({
                    status: response.status,
                    responseText: await response.text(),
                    responseHeaders: Array.from(response.headers.entries()).map(([key, value]) => `${key}: ${value}`).join("\n")
                }));
            }).catch((error) => {
                if (timeoutId) {
                    window.clearTimeout(timeoutId);
                }
                if (error?.name === "AbortError") {
                    return;
                }
                handlers.onerror?.(error);
            });
            return;
        }
        handlers.onerror?.(new Error("No HTTP client available"));
    }

    function normalizeHttpResponse(response) {
        if (!response || typeof response !== "object") {
            return { status: 0, responseText: "", responseHeaders: "" };
        }
        if (typeof response.responseText === "string") {
            return {
                status: Number(response.status) || 0,
                responseText: response.responseText,
                responseHeaders: typeof response.responseHeaders === "string" ? response.responseHeaders : ""
            };
        }
        if (typeof response.data === "string") {
            return {
                status: Number(response.status) || 200,
                responseText: response.data,
                responseHeaders: ""
            };
        }
        if (typeof response.body === "string") {
            return {
                status: Number(response.status) || 200,
                responseText: response.body,
                responseHeaders: ""
            };
        }
        return {
            status: Number(response.status) || 200,
            responseText: typeof response === "string" ? response : JSON.stringify(response),
            responseHeaders: ""
        };
    }

    function copyTextWithExecCommand(value) {
        try {
            const area = document.createElement("textarea");
            area.value = value;
            area.setAttribute("readonly", "");
            area.style.position = "fixed";
            area.style.opacity = "0";
            area.style.pointerEvents = "none";
            document.body.appendChild(area);
            area.focus();
            area.select();
            area.setSelectionRange(0, area.value.length);
            const copied = document.execCommand("copy");
            area.remove();
            return !!copied;
        } catch (error) {
            return false;
        }
    }

    function normalizeChainMinutes(value) {
        const numeric = Number(value);
        if (!Number.isFinite(numeric)) {
            return 15;
        }
        return Math.max(1, Math.min(180, Math.round(numeric)));
    }

    function startChainTimer() {
        const minutes = normalizeChainMinutes(chainMinutesInput?.value || state.settings.chainMinutesPreset || 15);
        state.settings.chainMinutesPreset = minutes;
        state.settings.chainTimerEndsAt = Date.now() + (minutes * 60 * 1000);
        chainAlertStages.clear();
        saveState();
        refreshPanel();
    }

    function clearChainTimer() {
        state.settings.chainTimerEndsAt = 0;
        chainAlertStages.clear();
        saveState();
        refreshPanel();
    }

    function getChainStatusText() {
        const endsAt = Number(state.settings.chainTimerEndsAt) || 0;
        if (!endsAt || endsAt <= Date.now()) {
            return t("chainInactive");
        }
        return formatTranslation("chainEndsIn", { time: formatCountdown(endsAt - Date.now()) });
    }

    function ensureNotificationPermission() {
        if (!("Notification" in window)) {
            return;
        }
        if (Notification.permission === "default") {
            Notification.requestPermission().catch(() => {});
        }
    }

    function notifyUser(title, body, options = {}) {
        if ("Notification" in window && Notification.permission === "granted") {
            try {
                new Notification(title, { body });
            } catch (error) {
                console.warn("TWET: notification failed", error);
            }
        }
        if (options.forceSound || state.settings.soundAlerts) {
            playAlertTone();
        }
    }

    async function playAlertTone() {
        try {
            const AudioContextClass = window.AudioContext || window.webkitAudioContext;
            if (!AudioContextClass) {
                return;
            }
            const context = new AudioContextClass();
            if (context.state === "suspended") {
                await context.resume().catch(() => {});
            }
            const pattern = [
                { frequency: 880, duration: 0.18, gap: 0.08 },
                { frequency: 988, duration: 0.18, gap: 0.08 },
                { frequency: 880, duration: 0.26, gap: 0 }
            ];
            let cursor = context.currentTime + 0.02;
            pattern.forEach((tone) => {
                const oscillator = context.createOscillator();
                const gain = context.createGain();
                oscillator.type = "sine";
                oscillator.frequency.setValueAtTime(tone.frequency, cursor);
                gain.gain.setValueAtTime(0.0001, cursor);
                gain.gain.linearRampToValueAtTime(0.1, cursor + 0.02);
                gain.gain.linearRampToValueAtTime(0.0001, cursor + tone.duration);
                oscillator.connect(gain);
                gain.connect(context.destination);
                oscillator.start(cursor);
                oscillator.stop(cursor + tone.duration + 0.02);
                cursor += tone.duration + tone.gap;
            });
            const closeAt = cursor + 0.1;
            window.setTimeout(() => {
                context.close().catch(() => {});
            }, Math.max(400, Math.ceil((closeAt - context.currentTime) * 1000)));
            if (navigator.vibrate) {
                navigator.vibrate([160, 90, 160]);
            }
        } catch (error) {
            console.warn("TWET: sound alert failed", error);
        }
    }

    function checkAlertsAndReminders() {
        checkAvailabilityAlerts();
        checkChainReminder();
    }

    function checkAvailabilityAlerts() {
        Object.values(state.enemies).forEach((enemy) => {
            if (!enemy) {
                return;
            }
            const currentTraveling = isTravelingEnemy(enemy);
            const previousTraveling = !!travelStateById.get(enemy.id);
            const travelAlertKey = getTravelAlertKey(enemy);
            const trackedTravel = travelAlertStageById.get(enemy.id);
            const travelRemainingMs = getTravelRemainingMs(enemy);
            if (enemy.sourceType === "revenge" && enemy.travelAlarmEnabled) {
                if (currentTraveling && (!trackedTravel || trackedTravel.key !== travelAlertKey)) {
                    travelAlertStageById.set(enemy.id, { key: travelAlertKey, warnedOneMinute: false });
                    enemy.travelAlarmSilenced = false;
                    saveState();
                }
                if (currentTraveling && !enemy.travelAlarmSilenced && travelRemainingMs > 0 && travelRemainingMs <= 60 * 1000) {
                    const stateForEnemy = travelAlertStageById.get(enemy.id) || { key: travelAlertKey, warnedOneMinute: false };
                    if (!stateForEnemy.warnedOneMinute) {
                        const place = getTravelLocation(enemy) || enemy.travel?.destination || "their destination";
                        notifyUser(
                            t("notificationTravelSoonTitle"),
                            formatTranslation("notificationTravelSoonBody", {
                                name: enemy.name || `Player [${enemy.id}]`,
                                time: formatCountdown(travelRemainingMs),
                                place
                            }),
                            { forceSound: true }
                        );
                        stateForEnemy.warnedOneMinute = true;
                        travelAlertStageById.set(enemy.id, stateForEnemy);
                    }
                }
            }
            if (enemy.sourceType === "revenge" && enemy.travelAlarmEnabled && previousTraveling && !currentTraveling && state.settings.readyAlerts !== false && !enemy.travelAlarmSilenced) {
                const place = getTravelLocation(enemy) || enemy.travel?.destination || "their destination";
                notifyUser(
                    t("notificationTravelTitle"),
                    formatTranslation("notificationTravelBody", { name: enemy.name || `Player [${enemy.id}]`, place }),
                    { forceSound: true }
                );
                travelAlertStageById.delete(enemy.id);
            } else if (!currentTraveling) {
                travelAlertStageById.delete(enemy.id);
            }
            travelStateById.set(enemy.id, currentTraveling);
            if (enemy.sourceType === "revenge") {
                return;
            }
            const availability = getAvailability(enemy);
            const previous = availabilityStateById.get(enemy.id);
            availabilityStateById.set(enemy.id, availability.kind);
            if (!previous || previous === availability.kind) {
                return;
            }
            if (availability.kind === "available" && state.settings.readyAlerts !== false) {
                notifyUser(t("notificationReadyTitle"), formatTranslation("notificationReadyBody", { name: enemy.name || `Player [${enemy.id}]` }));
            } else if (availability.kind === "soon" && state.settings.soonAlerts !== false) {
                notifyUser(t("notificationSoonTitle"), formatTranslation("notificationSoonBody", { name: enemy.name || `Player [${enemy.id}]` }));
            }
        });
    }

    function checkChainReminder() {
        const endsAt = Number(state.settings.chainTimerEndsAt) || 0;
        if (!endsAt) {
            return;
        }
        const remaining = endsAt - Date.now();
        if (remaining <= 0) {
            if (!chainAlertStages.has("expired")) {
                chainAlertStages.add("expired");
                notifyUser(t("notificationChainTitle"), formatTranslation("notificationChainBody", { time: "now" }));
            }
            return;
        }
        const stages = [
            { key: "5m", threshold: 5 * 60 * 1000 },
            { key: "2m", threshold: 2 * 60 * 1000 },
            { key: "1m", threshold: 60 * 1000 }
        ];
        stages.forEach((stage) => {
            if (remaining <= stage.threshold && !chainAlertStages.has(stage.key)) {
                chainAlertStages.add(stage.key);
                notifyUser(t("notificationChainTitle"), formatTranslation("notificationChainBody", { time: formatCountdown(remaining) }));
            }
        });
    }

    function cleanText(value) {
        return String(value || "").replace(/\s+/g, " ").trim();
    }

    function decodeHtmlEntities(value) {
        const text = String(value || "");
        if (!text || typeof document === "undefined") {
            return text;
        }
        const textarea = document.createElement("textarea");
        textarea.innerHTML = text;
        return textarea.value;
    }

    function escapeHtml(value) {
        return String(value || "")
            .replace(/&/g, "&amp;")
            .replace(/</g, "&lt;")
            .replace(/>/g, "&gt;")
            .replace(/"/g, "&quot;")
            .replace(/'/g, "&#39;");
    }

    function escapeAttribute(value) {
        return escapeHtml(value).replace(/`/g, "&#96;");
    }

    function formatTime(timestamp) {
        if (!timestamp) {
            return "never";
        }
        return new Date(timestamp).toLocaleString();
    }

    function formatNumber(value) {
        const number = Number(value) || 0;
        return number.toLocaleString();
    }

    function formatRelativeAge(timestamp) {
        const ageMs = Date.now() - Number(timestamp || 0);
        if (!Number.isFinite(ageMs) || ageMs < 0) {
            return "recently";
        }
        const minutes = Math.floor(ageMs / 60000);
        if (minutes < 1) {
            return "just now";
        }
        if (minutes < 60) {
            return `${minutes}m ago`;
        }
        const hours = Math.floor(minutes / 60);
        if (hours < 24) {
            return `${hours}h ago`;
        }
        const days = Math.floor(hours / 24);
        return `${days}d ago`;
    }

    function captureBattleMemoryFromPage() {
        const targetId = getBattleTargetIdFromUrl();
        if (!targetId) {
            battleCaptureState = { targetId: "", signature: "" };
            return;
        }

        const targetName = cleanText(state.enemies[targetId]?.name || "");
        const snapshot = extractBattleSnapshot(document, targetName);
        if (!snapshot) {
            return;
        }

        const signature = `${targetId}|${snapshot.outcome || ""}|${snapshot.weaponHints.join(",")}|${snapshot.summary || ""}`;
        if (battleCaptureState.targetId === targetId && battleCaptureState.signature === signature) {
            return;
        }

        battleCaptureState = { targetId, signature };

        const existing = state.enemies[targetId] || {
            id: targetId,
            name: `Player [${targetId}]`,
            tags: [],
            notes: "",
            contexts: [],
            createdAt: Date.now(),
            updatedAt: Date.now(),
            lastSeenAt: Date.now(),
            wins: 0,
            losses: 0,
            escapes: 0
        };

        state.enemies[targetId] = {
            ...existing,
            id: targetId,
            updatedAt: Date.now(),
            lastSeenAt: Date.now(),
            contexts: mergeContexts(existing.contexts, "battle"),
            lastBattle: {
                outcome: snapshot.outcome,
                summary: snapshot.summary,
                weaponHints: snapshot.weaponHints,
                capturedAt: Date.now(),
                sourceUrl: window.location.href
            },
            battleHistory: buildBattleHistory(existing.battleHistory, {
                outcome: snapshot.outcome,
                summary: snapshot.summary,
                weaponHints: snapshot.weaponHints,
                capturedAt: Date.now(),
                sourceUrl: window.location.href
            })
        };
        if (shouldAutoTallyBattleForTarget(document, targetId, targetName)) {
            applyAutoBattleOutcomeTallies(state.enemies[targetId], snapshot, signature);
        }
        saveState();
    }

    function buildBattleHistory(existingHistory, entry) {
        const history = Array.isArray(existingHistory) ? existingHistory.slice() : [];
        const normalizedEntry = {
            outcome: cleanText(entry?.outcome || ""),
            summary: cleanText(entry?.summary || ""),
            weaponHints: Array.isArray(entry?.weaponHints) ? entry.weaponHints.slice(0, 5).map(cleanText).filter(Boolean) : [],
            capturedAt: Number(entry?.capturedAt) || Date.now(),
            sourceUrl: String(entry?.sourceUrl || "")
        };
        if (!normalizedEntry.outcome && !normalizedEntry.summary && !normalizedEntry.weaponHints.length) {
            return history.slice(0, 5);
        }
        const signature = `${normalizedEntry.outcome}|${normalizedEntry.summary}|${normalizedEntry.weaponHints.join(",")}`;
        const deduped = history.filter((item) => {
            const itemSignature = `${cleanText(item?.outcome || "")}|${cleanText(item?.summary || "")}|${Array.isArray(item?.weaponHints) ? item.weaponHints.map(cleanText).join(",") : ""}`;
            return itemSignature !== signature;
        });
        deduped.unshift(normalizedEntry);
        return deduped.slice(0, 5);
    }

    function getBattleTargetIdFromUrl() {
        const href = window.location.href || "";
        if (!/sid=attack/i.test(href) && !/[?&]user2ID=/i.test(href)) {
            return "";
        }
        const attackMatch = href.match(/[?&]user2ID=(\d+)/i);
        return attackMatch ? attackMatch[1] : "";
    }

    function extractBattleSnapshot(root, targetName = "") {
        const lines = extractVisibleBattleLines(root);
        if (!lines.length) {
            return null;
        }
        const enemyLines = filterBattleLinesForTarget(lines, targetName);
        const outcome = detectBattleOutcome(enemyLines.length ? enemyLines : lines);
        const weaponHints = extractWeaponHints(root, enemyLines.length ? enemyLines : lines);
        const summary = getBattleSummaryLine(enemyLines.length ? enemyLines : lines, outcome);
        if (!outcome && !weaponHints.length && !summary) {
            return null;
        }
        return {
            outcome: outcome || "",
            summary: summary || "",
            weaponHints
        };
    }

    function extractVisibleBattleLines(root) {
        const rawText = String(root?.body?.innerText || root?.documentElement?.innerText || "");
        if (!rawText.trim()) {
            return [];
        }
        return [...new Set(rawText
            .split(/\n+/)
            .map(cleanText)
            .filter((line) => line && line.length >= 3)
            .slice(0, 200))];
    }

    function detectBattleOutcome(lines) {
        const joined = lines.join(" ").toLowerCase();
        if (/\byou were hospitalized\b|\byou were sent to hospital\b/.test(joined)) {
            return "Lost";
        }
        if (/\bhospitali[sz]ed\b|\bput .* hospital\b/.test(joined)) {
            return "Hospitalized";
        }
        if (/\bdefeated\b|\byou lost\b|\byou were beaten\b|\byou were defeated\b/.test(joined)) {
            return "Lost";
        }
        if (/\bmugged\b/.test(joined)) {
            return "Mugged";
        }
        if (/\bescaped\b|\bescape\b/.test(joined)) {
            return "Escaped";
        }
        if (/\bvictory\b|\byou won\b|\byou defeated\b|\byou beat\b/.test(joined)) {
            return "Won";
        }
        if (/\bstalemate\b|\bdraw\b/.test(joined)) {
            return "Stalemate";
        }
        return "";
    }

    function getBattleSummaryLine(lines, outcome) {
        if (!lines.length) {
            return "";
        }
        const outcomeMatcher = outcome ? new RegExp(outcome, "i") : null;
        const line = lines.find((entry) => {
            const lower = entry.toLowerCase();
            return (outcomeMatcher && outcomeMatcher.test(entry))
                || lower.includes("mugged")
                || lower.includes("hospital")
                || lower.includes("escaped")
                || lower.includes("victory")
                || lower.includes("defeated");
        });
        return line ? line.slice(0, 140) : "";
    }

    function filterBattleLinesForTarget(lines, targetName) {
        const cleanTargetName = cleanText(targetName);
        if (!cleanTargetName) {
            return [];
        }
        const escapedName = cleanTargetName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
        const nameMatcher = new RegExp(`\\b${escapedName}\\b`, "i");
        const leadingNameMatcher = new RegExp(`^${escapedName}\\b`, "i");
        const directLines = lines.filter((line) => leadingNameMatcher.test(line));
        if (directLines.length) {
            return directLines;
        }
        return lines.filter((line) => nameMatcher.test(line) && !/^\s*you\b/i.test(line));
    }

    function shouldAutoTallyBattleForTarget(root, targetId, targetName) {
        if (!targetId || !targetName) {
            return false;
        }
        const lines = extractVisibleBattleLines(root);
        if (!lines.length) {
            return false;
        }
        const targetLines = filterBattleLinesForTarget(lines, targetName);
        if (!targetLines.length) {
            return false;
        }
        const href = String(window.location.href || "");
        const idInUrl = getBattleTargetIdFromUrl();
        return idInUrl === String(targetId);
    }

    function extractWeaponHints(root, lines) {
        const hints = new Set();
        const weaponPattern = /\b(primary|secondary|melee|temporary|fists|shotgun|rifle|pistol|smg|grenade|spray|knife|bat|hammer|axe|sword|claw|launcher|carbine|cutlass|qsz|usp)\b/i;
        const extractionPatterns = [
            /threw\s+(?:a|an)\s+([^,.!?]{2,48})/i,
            /sprayed\s+([^,.!?]{2,48})\s+in/i,
            /fired\s+\d+\s+rounds?\s+of\s+(?:his|her|their)\s+([^,.!?]{2,48})/i,
            /hit\s+.+?\s+with\s+(?:his|her|their)\s+([^,.!?]{2,48})/i,
            /using\s+([^,.!?]{2,48})/i,
            /used\s+([^,.!?]{2,48})/i,
            /with\s+(?:his|her|their)\s+([^,.!?]{2,48})/i
        ];
        const normalizeWeaponHint = (value) => cleanText(String(value || "").replace(/\s+(?:critically hitting|hitting|near|in the|on the|but it was|critically).*$/i, ""));

        const attrNodes = Array.from(root.querySelectorAll("[title],[aria-label],[alt],[data-tooltip]"));
        attrNodes.forEach((node) => {
            ["title", "aria-label", "alt", "data-tooltip"].forEach((attr) => {
                const value = cleanText(node.getAttribute?.(attr) || "");
                if (value && value.length <= 48 && weaponPattern.test(value)) {
                    hints.add(value);
                }
            });
        });

        const filteredLines = lines.filter((line) => !/^\s*you\b/i.test(line));

        filteredLines.forEach((line) => {
            if (line.length <= 64 && weaponPattern.test(line)) {
                hints.add(normalizeWeaponHint(line));
            }
            extractionPatterns.forEach((pattern) => {
                const match = line.match(pattern);
                if (match && weaponPattern.test(match[1])) {
                    hints.add(normalizeWeaponHint(match[1]));
                }
            });
        });

        return Array.from(hints).slice(0, 4);
    }
})();