Valhalla: Warrior's Hall

Norse Mythology Edition — Single API Key Engine, Tactical Cards, FF Scouter, War Room SOS, TornPDA Touch Support

このスクリプトは単体で利用できません。右のようなメタデータを含むスクリプトから、ライブラリとして読み込まれます: // @require https://update.greatest.deepsurf.us/scripts/571894/1786926/Valhalla%3A%20Warrior%27s%20Hall.js

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

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

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

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

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

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

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

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

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

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

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

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

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

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

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name         Valhalla: Warrior's Hall
// @namespace    http://tampermonkey.net/
// @version      12.0
// @description  Norse Mythology Edition — Single API Key Engine, Tactical Cards, FF Scouter, War Room SOS, TornPDA Touch Support
// @author       You
// @match        https://www.torn.com/*
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_xmlhttpRequest
// @connect      api.torn.com
// @connect      www.tornstats.com
// @connect      ffscouter.com
// @connect      pythonanywhere.com
// ==/UserScript==

(function() {
    'use strict';

    const BACKEND_URL = "https://surajsharma128.pythonanywhere.com";

    // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    //  ᚢ  NORSE MYTHOLOGY STYLING  ᚢ
    // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    GM_addStyle(`
        @import url('https://fonts.googleapis.com/css2?family=MedievalSharp&display=swap');

        /* ── Scrollbars ── */
        #vwh-wrap ::-webkit-scrollbar { width: 5px; height: 5px; }
        #vwh-wrap ::-webkit-scrollbar-track { background: #0a0a12; }
        #vwh-wrap ::-webkit-scrollbar-thumb { background: linear-gradient(#3a2800, #c9a84c, #3a2800); border-radius: 2px; }
        #vwh-wrap ::-webkit-scrollbar-thumb:hover { background: #c9a84c; }

        /* ── Root wrapper ── */
        #vwh-wrap {
            position: fixed; top: 10px; right: 10px; z-index: 99999;
            width: 310px; max-height: 92vh;
            display: flex; flex-direction: column; overflow: hidden;

            background:
                linear-gradient(160deg, #0f0d1a 0%, #110e1f 40%, #0d0b16 100%);
            border: 2px solid #4a3800;
            border-radius: 4px;
            outline: 1px solid #1a1530;
            box-shadow:
                0 0 0 1px #c9a84c22,
                inset 0 0 60px rgba(0,0,0,0.9),
                0 20px 60px rgba(0,0,0,0.95),
                0 0 30px rgba(139,26,26,0.15);

            font-family: 'Georgia', 'Palatino Linotype', serif;
            color: #c8b89a;
            font-size: 13px;
            transition: width 0.3s ease, box-shadow 0.3s;
        }
        @media (min-width: 600px) { #vwh-wrap { width: 460px; top: 55px; right: 18px; } }
        @media (min-width: 900px) { #vwh-wrap { width: 530px; } }
        #vwh-wrap.compact { width: 280px !important; font-size: 12px; }
        #vwh-wrap.minimized #vwh-body { display: none !important; }

        /* ── Corner rune decorations ── */
        #vwh-wrap::before, #vwh-wrap::after {
            content: '᛭'; position: absolute; color: #c9a84c44;
            font-size: 22px; line-height: 1; pointer-events: none; z-index: 1;
        }
        #vwh-wrap::before { top: 3px; left: 5px; }
        #vwh-wrap::after  { bottom: 3px; right: 5px; }

        /* ── Header ── */
        .vwh-header {
            background:
                linear-gradient(180deg, #1e1500 0%, #130f00 50%, #0d0a00 100%);
            border-bottom: 1px solid #4a3800;
            box-shadow: 0 2px 12px rgba(0,0,0,0.8), inset 0 1px 0 #c9a84c33;
            padding: 11px 13px 9px;
            display: flex; justify-content: space-between; align-items: center;
            cursor: grab; user-select: none; -webkit-user-select: none;
            touch-action: none;
            position: relative;
        }
        .vwh-header:active { cursor: grabbing; }
        .vwh-header-title {
            display: flex; align-items: center; gap: 7px;
            font-size: 12px; text-transform: uppercase; letter-spacing: 2.5px;
            color: #c9a84c; font-weight: bold;
            text-shadow: 0 0 12px #c9a84c88, 0 2px 3px #000;
        }
        .vwh-header-rune {
            font-size: 18px; color: #c9a84c; opacity: 0.9;
            text-shadow: 0 0 8px #c9a84caa;
            animation: vwh-runeflicker 4s ease-in-out infinite;
        }
        @keyframes vwh-runeflicker {
            0%,100% { opacity: 0.9; text-shadow: 0 0 8px #c9a84caa; }
            50% { opacity: 0.6; text-shadow: 0 0 4px #c9a84c66; }
        }
        .vwh-header-sub {
            font-size: 9px; color: #6a5530; letter-spacing: 1px;
            text-transform: uppercase; margin-top: 1px;
        }

        /* ── Header buttons ── */
        .vwh-ctrl { display: flex; gap: 3px; cursor: default; }
        .vwh-hbtn {
            background: linear-gradient(180deg, #1e1500, #0d0900);
            color: #7a6040; border: 1px solid #3a2800;
            width: 28px; height: 28px; border-radius: 3px;
            cursor: pointer; font-size: 12px; font-weight: bold;
            display: flex; align-items: center; justify-content: center;
            transition: all 0.2s; -webkit-tap-highlight-color: transparent;
            touch-action: manipulation;
            text-shadow: none; box-shadow: inset 0 1px 0 rgba(255,255,255,0.05);
        }
        .vwh-hbtn:hover, .vwh-hbtn:active { background: linear-gradient(180deg, #3a2800, #1e1500); color: #c9a84c; border-color: #c9a84c; box-shadow: 0 0 6px #c9a84c44; }
        .vwh-hbtn.pinned { background: linear-gradient(180deg, #4a3000, #2a1800); color: #c9a84c; border-color: #c9a84c; box-shadow: 0 0 8px #c9a84c66; }

        /* ── Divider ── */
        .vwh-rune-divider {
            text-align: center; font-size: 10px; color: #3a2800;
            letter-spacing: 3px; padding: 4px 0;
            background: #0d0a00;
            border-bottom: 1px solid #1a1200;
            user-select: none;
        }

        /* ── Tabs ── */
        .vwh-tabs {
            display: flex; overflow-x: auto; white-space: nowrap;
            background: #080610; border-bottom: 1px solid #2a1e00;
            -webkit-overflow-scrolling: touch;
            scrollbar-width: none;
        }
        .vwh-tabs::-webkit-scrollbar { display: none; }
        .vwh-tab {
            flex: 0 0 auto; padding: 9px 13px;
            font-size: 10px; text-transform: uppercase; letter-spacing: 1px;
            font-weight: bold; color: #4a3820; cursor: pointer;
            border-bottom: 2px solid transparent;
            transition: all 0.2s; background: transparent;
            min-height: 38px; display: flex; align-items: center;
            -webkit-tap-highlight-color: transparent; touch-action: manipulation;
        }
        .vwh-tab:hover, .vwh-tab:active { color: #a08040; background: #0f0b00; }
        .vwh-tab.active {
            color: #c9a84c;
            background: linear-gradient(180deg, #1a1200 0%, #0d0900 100%);
            border-bottom: 2px solid #c9a84c;
            text-shadow: 0 0 8px #c9a84c66;
        }
        .vwh-tab-dot { margin-right: 4px; font-size: 12px; }

        /* ── Content area ── */
        .vwh-content { padding: 13px; overflow-y: auto; flex-grow: 1; position: relative; -webkit-overflow-scrolling: touch; }
        .vwh-pane { display: none; }
        .vwh-pane.active { display: block; animation: vwh-fadepane 0.2s ease; }
        @keyframes vwh-fadepane { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: translateY(0); } }

        /* ── Norse buttons ── */
        .vwh-btn {
            background: linear-gradient(180deg, #2a2010 0%, #181008 50%, #0e0a04 100%);
            color: #c8b89a; border: 1px solid #3a2800;
            padding: 7px 12px; border-radius: 3px; cursor: pointer;
            font-family: 'Georgia', serif; font-weight: bold;
            text-transform: uppercase; font-size: 11px; letter-spacing: 1px;
            text-shadow: 0 1px 2px #000;
            box-shadow: inset 0 1px 0 rgba(201,168,76,0.1), inset 0 -1px 0 rgba(0,0,0,0.5);
            transition: all 0.18s; min-height: 34px;
            -webkit-tap-highlight-color: transparent; touch-action: manipulation;
        }
        .vwh-btn:hover:not(:disabled), .vwh-btn:active:not(:disabled) {
            background: linear-gradient(180deg, #3d3018, #221808);
            color: #e8d8a0; border-color: #6a5020;
            box-shadow: inset 0 1px 0 rgba(201,168,76,0.2), 0 0 8px rgba(201,168,76,0.1);
        }
        .vwh-btn:disabled { opacity: 0.4; cursor: not-allowed; }

        .vwh-btn-gold {
            background: linear-gradient(180deg, #4a3800 0%, #2e2200 50%, #1e1600 100%);
            border-color: #c9a84c; color: #c9a84c;
            box-shadow: inset 0 1px 0 rgba(201,168,76,0.2), 0 0 6px rgba(201,168,76,0.1);
        }
        .vwh-btn-gold:hover:not(:disabled), .vwh-btn-gold:active:not(:disabled) {
            background: linear-gradient(180deg, #6a5010, #3a2800);
            color: #ffd700; box-shadow: 0 0 14px rgba(201,168,76,0.3);
        }
        .vwh-btn-blood {
            background: linear-gradient(180deg, #3a0a0a 0%, #220606 50%, #140404 100%);
            border-color: #8b1a1a; color: #ff7070;
        }
        .vwh-btn-blood:hover:not(:disabled), .vwh-btn-blood:active:not(:disabled) {
            background: linear-gradient(180deg, #541010, #2e0808);
            box-shadow: 0 0 10px rgba(139,26,26,0.4);
        }
        .vwh-btn-amber {
            background: linear-gradient(180deg, #3d2800, #1e1200);
            border-color: #b86820; color: #e88830;
        }
        .vwh-btn-amber:hover:not(:disabled), .vwh-btn-amber:active:not(:disabled) {
            background: linear-gradient(180deg, #5a3800, #2e1800);
            box-shadow: 0 0 10px rgba(184,104,32,0.3);
        }
        .vwh-btn-forest {
            background: linear-gradient(180deg, #0a2810, #060f08);
            border-color: #2a7a2a; color: #5ad65a;
        }
        .vwh-btn-forest:hover:not(:disabled), .vwh-btn-forest:active:not(:disabled) {
            box-shadow: 0 0 10px rgba(90,214,90,0.2);
        }

        /* ── Inputs ── */
        .vwh-input {
            background: linear-gradient(180deg, #06040e, #090712);
            color: #c8b89a; border: 1px solid #2e2200;
            padding: 8px 10px; width: 100%; border-radius: 3px;
            font-family: 'Georgia', serif; font-size: 12px;
            box-shadow: inset 0 2px 6px rgba(0,0,0,0.7);
            transition: border-color 0.2s; -webkit-appearance: none;
        }
        .vwh-input:focus { border-color: #c9a84c; outline: none; box-shadow: inset 0 2px 6px rgba(0,0,0,0.7), 0 0 6px rgba(201,168,76,0.15); }
        .vwh-input::placeholder { color: #3a2e1a; }

        /* ── Respect/score bar ── */
        .vwh-score-bar {
            height: 26px; border-radius: 3px; overflow: hidden; position: relative;
            margin: 10px 0; display: flex;
            border: 1px solid #2a1e00;
            box-shadow: inset 0 2px 8px rgba(0,0,0,0.8), 0 0 8px rgba(0,0,0,0.5);
        }
        .vwh-score-blue {
            background: linear-gradient(90deg, #0a1e3a, #1a3a6e);
            height: 100%; display: flex; align-items: center; padding-left: 8px;
            font-weight: bold; font-size: 11px; transition: width 0.6s ease;
            text-shadow: 0 1px 3px #000; border-right: 1px solid #0a0814;
        }
        .vwh-score-red {
            background: linear-gradient(270deg, #3a0a0a, #6e1a1a);
            height: 100%; flex: 1; display: flex; align-items: center;
            justify-content: flex-end; padding-right: 8px;
            font-weight: bold; font-size: 11px; transition: width 0.6s ease;
            text-shadow: 0 1px 3px #000; border-left: 1px solid #0a0814;
        }
        .vwh-score-label {
            position: absolute; width: 100%; text-align: center;
            line-height: 26px; font-size: 11px; font-weight: bold; z-index: 2;
            pointer-events: none; text-transform: uppercase; letter-spacing: 1px;
            color: #e8d090; text-shadow: 0 0 6px #000, 1px 1px 2px #000;
        }

        /* ── Parchment section header ── */
        .vwh-section-head {
            font-size: 10px; text-transform: uppercase; letter-spacing: 2px;
            color: #6a5020; border-bottom: 1px solid #2a1e00;
            padding-bottom: 5px; margin-bottom: 10px;
            display: flex; justify-content: space-between; align-items: center;
        }
        .vwh-section-head span { color: #c9a84c; }

        /* ── Player cards ── */
        .vwh-cards { display: flex; flex-direction: column; gap: 7px; max-height: 320px; overflow-y: auto; padding-right: 3px; -webkit-overflow-scrolling: touch; }
        .vwh-card {
            background: linear-gradient(160deg, #0e0c18 0%, #0b0918 100%);
            border: 1px solid #2a1e00;
            border-left: 3px solid #3a2800;
            border-radius: 3px; padding: 9px 10px;
            box-shadow: inset 0 1px 0 rgba(201,168,76,0.04), 0 2px 8px rgba(0,0,0,0.5);
            transition: border-left-color 0.2s;
        }
        .vwh-card:hover { border-left-color: #c9a84c88; }
        .vwh-card-top {
            display: flex; justify-content: space-between; align-items: flex-start;
            margin-bottom: 7px;
        }
        .vwh-card-name {
            color: #d4c090; text-decoration: none; font-weight: bold;
            font-size: 13px; text-shadow: 0 1px 2px #000;
        }
        .vwh-card-name:hover { color: #ffd700; text-shadow: 0 0 8px #c9a84c88; }
        .vwh-status-pill {
            font-size: 9px; font-weight: bold; text-transform: uppercase; letter-spacing: 1px;
            padding: 2px 6px; border-radius: 2px;
            border: 1px solid currentColor; background: rgba(0,0,0,0.5);
        }
        .vwh-card-acts { display: flex; gap: 4px; margin-bottom: 7px; flex-wrap: wrap; }
        .vwh-card-stats {
            font-size: 11px; color: #7a6848;
            background: linear-gradient(180deg, #080612, #060410);
            padding: 5px 8px; border-radius: 2px;
            border: 1px solid #1a1200; min-height: 20px;
        }

        /* ── Norse box / parchment panel ── */
        .vwh-panel {
            background: linear-gradient(160deg, #0b0918 0%, #080614 100%);
            border: 1px solid #2a1e00;
            border-radius: 3px; padding: 13px;
            margin-bottom: 12px;
            box-shadow: inset 0 0 20px rgba(0,0,0,0.6);
            position: relative;
        }
        .vwh-panel::before {
            content: ''; position: absolute; top: 0; left: 0; right: 0;
            height: 1px;
            background: linear-gradient(90deg, transparent, #c9a84c22, transparent);
        }
        .vwh-panel-title {
            color: #c9a84c; font-size: 13px; font-weight: bold;
            text-transform: uppercase; letter-spacing: 2px;
            text-shadow: 0 0 10px #c9a84c44;
            margin-bottom: 10px;
        }

        /* ── War Room card ── */
        .vwh-wr-card {
            background: linear-gradient(160deg, #120818 0%, #0e0614 100%);
            border: 1px solid #3a0a0a; border-left: 3px solid #8b1a1a;
            border-radius: 3px; padding: 10px 11px; margin-bottom: 9px;
            box-shadow: inset 0 0 15px rgba(139,26,26,0.05), 0 2px 8px rgba(0,0,0,0.5);
            transition: opacity 0.3s;
        }

        /* ── Setup / API screen ── */
        .vwh-setup { padding: 25px 20px; text-align: center; }
        .vwh-setup-rune {
            font-size: 36px; color: #c9a84c; display: block; margin-bottom: 8px;
            text-shadow: 0 0 20px #c9a84c66;
            animation: vwh-runeflicker 3s ease-in-out infinite;
        }
        .vwh-big-title {
            color: #c9a84c; font-size: 16px; font-weight: bold;
            text-transform: uppercase; letter-spacing: 3px;
            text-shadow: 0 0 15px #c9a84c44, 0 2px 4px #000;
            margin-bottom: 6px;
        }
        .vwh-subtitle { color: #5a4830; font-size: 11px; letter-spacing: 1px; margin-bottom: 20px; }
        .vwh-field-label { font-size: 10px; text-transform: uppercase; letter-spacing: 1.5px; color: #6a5020; margin-bottom: 4px; text-align: left; }

        /* ── Message/loading box ── */
        #vwh-msg { text-align: center; padding: 35px 20px; display: none; }
        .vwh-msg-rune { font-size: 32px; color: #c9a84c44; margin-bottom: 10px; animation: vwh-runeflicker 2s infinite; }
        .vwh-msg-title { color: #c9a84c; text-transform: uppercase; letter-spacing: 2px; font-size: 15px; margin-bottom: 8px; text-shadow: 0 0 10px #c9a84c33; }
        .vwh-msg-body { font-size: 12px; color: #5a4830; line-height: 1.6; }
        .vwh-error-box { color: #ff7070; background: #12040a; padding: 10px; border: 1px solid #5a1010; border-radius: 3px; margin-top: 12px; text-align: left; font-family: monospace; font-size: 11px; }

        /* ── Toast notifications ── */
        #vwh-toasts { position: absolute; bottom: 12px; left: 50%; transform: translateX(-50%); z-index: 99999; display: flex; flex-direction: column; gap: 4px; width: 88%; align-items: center; pointer-events: none; }
        .vwh-toast {
            background: linear-gradient(180deg, #1a2e0a, #0e1a06);
            color: #8ad65a; border: 1px solid #2a4a10;
            padding: 6px 14px; border-radius: 2px; font-size: 10px; font-weight: bold;
            text-transform: uppercase; letter-spacing: 1.5px;
            box-shadow: 0 4px 12px rgba(0,0,0,0.8), 0 0 8px rgba(90,214,90,0.1);
            animation: vwh-toastin 0.3s ease forwards;
        }
        .vwh-toast.err { background: linear-gradient(180deg, #2e0a0a, #180606); color: #ff7070; border-color: #5a1010; box-shadow: 0 0 8px rgba(255,80,80,0.1); }
        @keyframes vwh-toastin { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } }

        /* ── Misc ── */
        .vwh-dim { color: #3a2e1a; font-style: italic; font-size: 11px; }
        .vwh-gold { color: #c9a84c; }
        .vwh-blood { color: #ff7070; }
        .vwh-rune-hr { border: none; border-top: 1px solid #1e1600; margin: 10px 0; position: relative; text-align: center; }
        .vwh-rune-hr::after { content: '᛭'; position: absolute; top: -7px; left: 50%; transform: translateX(-50%); background: #0e0c18; padding: 0 6px; color: #2a1e00; font-size: 11px; }

        .vwh-stat-badge { color: #8a7040; font-weight: bold; font-size: 11px; }
        .vwh-highlight { color: #c9a84c; font-size: 15px; font-weight: bold; text-shadow: 0 0 8px #c9a84c44; }
        .vwh-w-sm { width: 65px !important; }
        .vwh-w-md { width: 105px !important; }

        /* ── Knotwork top border on cards ── */
        .vwh-card-knotwork {
            height: 3px; margin: -13px -13px 11px;
            background: repeating-linear-gradient(90deg, #c9a84c11 0px, #c9a84c22 4px, transparent 4px, transparent 8px);
            border-radius: 3px 3px 0 0;
        }
    `);

    // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    //  HTML STRUCTURE
    // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    const html = `
    <div id="vwh-wrap" style="display:none;">

        <div class="vwh-header" id="vwh-drag">
            <div>
                <div class="vwh-header-title">
                    <span class="vwh-header-rune">ᚢ</span>
                    VALHALLA: WARRIOR'S HALL
                    <span class="vwh-header-rune">ᚢ</span>
                </div>
                <div class="vwh-header-sub">ᚠ ᚢ ᚦ ᚨ ᚱ ᚲ · MEMBER EDITION · ᚠ ᚢ ᚦ ᚨ ᚱ ᚲ</div>
            </div>
            <div class="vwh-ctrl">
                <button id="vwh-pin"  class="vwh-hbtn" title="Pin to all pages">📌</button>
                <button id="vwh-help" class="vwh-hbtn" title="Odin's Codex">📜</button>
                <button id="vwh-cfg"  class="vwh-hbtn" title="Forge Settings">⚙️</button>
                <button id="vwh-size" class="vwh-hbtn" title="Toggle size">◱</button>
                <button id="vwh-min"  class="vwh-hbtn" title="Minimize">—</button>
            </div>
        </div>

        <div class="vwh-rune-divider">ᛟ ᛜ ᛝ ᛟ ᛜ ᛝ ᛟ ᛜ ᛝ ᛟ</div>

        <div id="vwh-body">

            <!-- HELP SCREEN -->
            <div id="vwh-screen-help" class="vwh-setup" style="display:none; text-align:left;">
                <span class="vwh-setup-rune">ᚷ</span>
                <div class="vwh-big-title">Odin's Codex</div>
                <div class="vwh-subtitle">The sacred knowledge of Valhalla</div>
                <ul style="color:#7a6040; font-size:12px; line-height:1.9; padding-left:18px; margin-bottom:18px;">
                    <li><span class="vwh-gold">📌 Global Pin</span> — Manifests the Hall on every Torn page.</li>
                    <li><span class="vwh-gold">⚔️ Battlefield</span> — Live war with FF Scouter intelligence. Auto-scouts every 15s.</li>
                    <li><span class="vwh-gold">📜 Sagas</span> — Load past war reports by ID.</li>
                    <li><span class="vwh-gold">🎯 Hitlist</span> — Mark enemies for priority slaying.</li>
                    <li><span class="vwh-gold">🚨 War Room</span> — Real-time SOS signals. Call your shield-brothers.</li>
                    <li><span class="vwh-blood">⚔️ SOS</span> — Sends a target to the War Room instantly.</li>
                </ul>
                <button id="vwh-help-close" class="vwh-btn vwh-btn-gold" style="width:100%;">CLOSE THE CODEX</button>
            </div>

            <!-- API SETUP SCREEN -->
            <div id="vwh-screen-api" class="vwh-setup">
                <span class="vwh-setup-rune">ᚠ</span>
                <div class="vwh-big-title">Prove Your Worth</div>
                <div class="vwh-subtitle">Only the worthy enter Valhalla</div>
                <div class="vwh-field-label">⚔ Primary API Key (FF Scouter-registered)</div>
                <div style="margin-bottom:5px; font-size:10px; color:#4a3010; line-height:1.5;">
                    This key powers Torn data + FF Scouter stats.<br>
                    <span class="vwh-blood">✦ Register at FF Scouter first!</span><br>
                    <a href="https://ffscouter.com" target="_blank" style="color:#8a6020; text-decoration:none; border-bottom:1px dashed #8a6020;">→ ffscouter.com</a>
                </div>
                <input type="password" id="vwh-key-torn" class="vwh-input" placeholder="Torn API Key — 16 runes" style="margin-bottom:13px; text-align:center;">
                <div class="vwh-field-label">🛡 TornStats API (Optional)</div>
                <input type="password" id="vwh-key-ts" class="vwh-input" placeholder="TornStats Key" style="margin-bottom:15px; text-align:center;">
                <div style="display:flex; gap:8px; justify-content:center;">
                    <button id="vwh-api-save" class="vwh-btn vwh-btn-gold" style="padding:9px 20px;">ENTER VALHALLA</button>
                    <button id="vwh-api-cancel" class="vwh-btn" style="display:none; padding:9px 16px;">RETREAT</button>
                </div>
            </div>

            <!-- MAIN APP -->
            <div id="vwh-screen-app" style="display:none; flex-direction:column; height:100%;">
                <div class="vwh-tabs">
                    <div class="vwh-tab active" data-pane="p-live"><span class="vwh-tab-dot">⚔️</span>Battle</div>
                    <div class="vwh-tab" data-pane="p-past"><span class="vwh-tab-dot">📜</span>Sagas</div>
                    <div class="vwh-tab" data-pane="p-hitlist"><span class="vwh-tab-dot">🎯</span>Hitlist</div>
                    <div class="vwh-tab" data-pane="p-warroom" style="color:#8b1a1a;"><span class="vwh-tab-dot">🚨</span>War Room</div>
                </div>

                <div class="vwh-content">
                    <div id="vwh-toasts"></div>

                    <div id="vwh-msg">
                        <div class="vwh-msg-rune">ᚱ</div>
                        <div id="vwh-msg-title" class="vwh-msg-title">Consulting the Norns…</div>
                        <div id="vwh-msg-body" class="vwh-msg-body">The threads of fate are being read.</div>
                    </div>

                    <div id="vwh-data" style="display:none;">

                        <!-- ⚔️ BATTLEFIELD -->
                        <div id="p-live" class="vwh-pane active">
                            <div id="live-empty" class="vwh-dim" style="text-align:center; padding:20px; display:none;">
                                ᚹ Peace upon Midgard. No battle rages. ᚹ
                            </div>
                            <div id="live-wrap">
                                <div class="vwh-section-head">
                                    <span>⚔ Battle Log</span>
                                    <span>RSP: <strong id="live-my-rsp" class="vwh-gold">—</strong> &nbsp;|&nbsp; ATK: <strong id="live-my-atk" class="vwh-gold">—</strong></span>
                                </div>
                                <div class="vwh-score-bar">
                                    <div class="vwh-score-label" id="live-bar-lbl">Clan A vs Clan B</div>
                                    <div class="vwh-score-blue" id="live-bar-l" style="width:50%;">0</div>
                                    <div class="vwh-score-red"  id="live-bar-r" style="width:50%;">0</div>
                                </div>
                                <div id="live-cards" class="vwh-cards"></div>
                            </div>
                        </div>

                        <!-- 📜 SAGAS -->
                        <div id="p-past" class="vwh-pane">
                            <div class="vwh-panel">
                                <div class="vwh-panel-title">ᚱ Recall a Saga</div>
                                <p style="font-size:11px; color:#5a4030; margin-bottom:11px;">Enter a Ranked War Report ID to summon a past battle.</p>
                                <div style="display:flex; gap:6px;">
                                    <input type="number" id="saga-id-input" class="vwh-input" placeholder="Report ID">
                                    <button id="saga-load" class="vwh-btn vwh-btn-gold">SUMMON</button>
                                </div>
                                <div id="saga-status" style="font-size:11px; margin-top:7px; color:#c9a84c;"></div>
                            </div>
                            <div id="past-empty" class="vwh-dim" style="text-align:center; padding:15px;">No saga summoned.</div>
                            <div id="past-wrap" style="display:none;">
                                <div class="vwh-section-head">
                                    <span>📜 Archived Saga</span>
                                    <span>RSP: <strong id="past-my-rsp" class="vwh-gold">—</strong> &nbsp;|&nbsp; ATK: <strong id="past-my-atk" class="vwh-gold">—</strong></span>
                                </div>
                                <div class="vwh-score-bar">
                                    <div class="vwh-score-label" id="past-bar-lbl">Clan A vs Clan B</div>
                                    <div class="vwh-score-blue" id="past-bar-l" style="width:50%;">0</div>
                                    <div class="vwh-score-red"  id="past-bar-r" style="width:50%;">0</div>
                                </div>
                                <div id="past-cards" class="vwh-cards"></div>
                            </div>
                        </div>

                        <!-- 🎯 HITLIST -->
                        <div id="p-hitlist" class="vwh-pane">
                            <div class="vwh-section-head"><span>🎯 Marked for Valhalla</span></div>
                            <div id="hitlist-cards" class="vwh-cards" style="max-height:420px;"></div>
                            <div id="hitlist-empty" class="vwh-dim" style="text-align:center; padding:20px;">No enemies marked.</div>
                        </div>

                        <!-- 🚨 WAR ROOM -->
                        <div id="p-warroom" class="vwh-pane">
                            <div class="vwh-panel" style="border-color:#3a0a0a;">
                                <div class="vwh-panel-title" style="color:#ff7070;">🚨 Summon Shield-Brothers</div>
                                <div style="display:flex; gap:6px;">
                                    <input type="number" id="wr-manual-id" class="vwh-input" placeholder="Enemy ID — manual SOS">
                                    <button id="wr-manual-btn" class="vwh-btn vwh-btn-blood" style="width:80px;">SOS</button>
                                </div>
                            </div>
                            <div id="wr-list">
                                <div class="vwh-dim" style="text-align:center; padding:18px;">ᚹ No battle-calls echo in the Hall. ᚹ</div>
                            </div>
                        </div>

                    </div><!-- /vwh-data -->
                </div><!-- /vwh-content -->
            </div><!-- /vwh-screen-app -->

        </div><!-- /vwh-body -->
    </div>
    `;

    document.body.appendChild(Object.assign(document.createElement('div'), {innerHTML: html}));

    // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    //  STATE
    // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    let KEYS = { torn: GM_getValue('vwh_key_torn',''), ts: GM_getValue('vwh_key_ts','') };
    let MEM  = { lastReport: GM_getValue('vwh_mem_report', null) };
    let IS_PINNED = GM_getValue('vwh_is_pinned', false);
    let APP  = { uid: null, enemyFacId: null };
    let radarTimer = null, syncTimer = null;

    // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    //  PIN & VISIBILITY
    // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    const wrap = document.getElementById('vwh-wrap');
    if (IS_PINNED || /factions\.php|warfare\.php/.test(location.href)) {
        wrap.style.display = 'flex';
        if (IS_PINNED) document.getElementById('vwh-pin').classList.add('pinned');
    }
    document.getElementById('vwh-pin').addEventListener('click', function(e) {
        IS_PINNED = !IS_PINNED;
        GM_setValue('vwh_is_pinned', IS_PINNED);
        e.currentTarget.classList.toggle('pinned', IS_PINNED);
    });

    // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    //  DRAG — MOUSE + TOUCH
    // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    const dragHandle = document.getElementById('vwh-drag');
    let dragging = false, dX = 0, dY = 0;

    function dragStart(cx, cy) {
        dragging = true;
        const r = wrap.getBoundingClientRect();
        dX = cx - r.left; dY = cy - r.top;
        wrap.style.right = 'auto';
    }
    function dragMove(cx, cy) {
        if (!dragging) return;
        wrap.style.left = Math.max(0, Math.min(cx - dX, window.innerWidth  - wrap.offsetWidth))  + 'px';
        wrap.style.top  = Math.max(0, Math.min(cy - dY, window.innerHeight - wrap.offsetHeight)) + 'px';
    }
    dragHandle.addEventListener('mousedown', function(e) { if (!e.target.closest('.vwh-ctrl')) dragStart(e.clientX, e.clientY); });
    document.addEventListener('mousemove',   function(e) { dragMove(e.clientX, e.clientY); });
    document.addEventListener('mouseup',     function()  { dragging = false; });
    dragHandle.addEventListener('touchstart', function(e) { if (!e.target.closest('.vwh-ctrl')) { const t=e.touches[0]; dragStart(t.clientX, t.clientY); } }, {passive:true});
    document.addEventListener('touchmove',   function(e) { if (!dragging) return; e.preventDefault(); const t=e.touches[0]; dragMove(t.clientX, t.clientY); }, {passive:false});
    document.addEventListener('touchend',    function()  { dragging = false; });

    // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    //  UI CONTROLS
    // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    document.getElementById('vwh-min').addEventListener('click',  function() { wrap.classList.toggle('minimized'); });
    document.getElementById('vwh-size').addEventListener('click', function() { wrap.classList.toggle('compact'); });
    document.getElementById('vwh-help').addEventListener('click', function() {
        document.getElementById('vwh-screen-app').style.display = 'none';
        document.getElementById('vwh-screen-api').style.display = 'none';
        document.getElementById('vwh-screen-help').style.display = 'block';
    });
    document.getElementById('vwh-help-close').addEventListener('click', function() { boot(); });

    document.querySelectorAll('.vwh-tab').forEach(function(tab) {
        tab.addEventListener('click', function(e) {
            document.querySelectorAll('.vwh-tab').forEach(function(t) { t.classList.remove('active'); });
            document.querySelectorAll('.vwh-pane').forEach(function(p) { p.classList.remove('active'); });
            e.currentTarget.classList.add('active');
            document.getElementById(e.currentTarget.dataset.pane).classList.add('active');
        });
    });

    // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    //  TOAST
    // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    function toast(msg, err) {
        var c = document.getElementById('vwh-toasts');
        var t = document.createElement('div');
        t.className = 'vwh-toast' + (err ? ' err' : '');
        t.textContent = msg;
        c.appendChild(t);
        setTimeout(function() { t.remove(); }, 2500);
    }

    // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    //  NETWORK
    // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    function gmGet(url)  { return new Promise(function(ok,fail) { GM_xmlhttpRequest({method:"GET",url:url,onload:function(r){try{ok(JSON.parse(r.responseText));}catch(e){fail(e);}},onerror:fail}); }); }
    function gmPost(url,data) { return new Promise(function(ok,fail) { GM_xmlhttpRequest({method:"POST",url:url,headers:{"Content-Type":"application/json"},data:JSON.stringify(data),onload:function(r){try{ok(JSON.parse(r.responseText));}catch(e){fail(e);}},onerror:fail}); }); }
    function gmDel(url)  { return new Promise(function(ok,fail) { GM_xmlhttpRequest({method:"DELETE",url:url,onload:function(r){try{ok(JSON.parse(r.responseText));}catch(e){fail(e);}},onerror:fail}); }); }

    // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    //  FF SCOUTER HELPERS
    // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    function ffDifficulty(ff) {
        if (ff<=1) return 'Prey'; if (ff<=2) return 'Easy'; if (ff<=3.5) return 'Worthy';
        if (ff<=4.5) return 'Dangerous'; return 'Valkyrie-Level';
    }
    function ffColor(v) {
        var r,g,b;
        if(v<=1){r=0x28;g=0x28;b=0xc6;}
        else if(v<=3){var t=(v-1)/2;r=0x28;g=Math.round(0x28+(0xc6-0x28)*t);b=Math.round(0xc6-(0xc6-0x28)*t);}
        else if(v<=5){var t=(v-3)/2;r=Math.round(0x28+(0xc6-0x28)*t);g=Math.round(0xc6-(0xc6-0x28)*t);b=0x28;}
        else{r=0xc6;g=0x28;b=0x28;}
        return '#'+((1<<24)+(r<<16)+(g<<8)+b).toString(16).slice(1).toUpperCase();
    }
    function ffContrast(hex) {
        var r=parseInt(hex.slice(1,3),16),g=parseInt(hex.slice(3,5),16),b=parseInt(hex.slice(5,7),16);
        return (r*.299+g*.587+b*.114)>126?'#000':'#fff';
    }
    function ffHTML(ff, est) {
        var bg=ffColor(ff),tc=ffContrast(bg),d=ffDifficulty(ff);
        return '<span style="color:#7a6040;font-weight:bold;margin-right:5px;">ᚠF:</span>'
             + '<span style="background:'+bg+';color:'+tc+';font-weight:bold;padding:1px 6px;border-radius:2px;">'+ff.toFixed(2)+' — '+d+'</span>'
             + '<span style="font-size:10px;color:#5a4828;margin-left:7px;">Est: <strong style="color:#b09050;">'+( est||'Unknown')+'</strong></span>';
    }
    function fmtStats(raw) {
        if(raw===null||raw===undefined) return 'N/A';
        var n=parseInt(raw.toString().replace(/,/g,''));
        if(isNaN(n)) return 'N/A';
        if(n>=1e9) return (n/1e9).toFixed(2)+'b';
        if(n>=1e6) return (n/1e6).toFixed(2)+'m';
        if(n>=1e3) return (n/1e3).toFixed(2)+'k';
        return n.toString();
    }

    // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    //  BOOT / SETTINGS
    // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    function boot() {
        document.getElementById('vwh-screen-help').style.display = 'none';
        if (KEYS.torn && KEYS.torn.length===16) {
            document.getElementById('vwh-screen-api').style.display  = 'none';
            document.getElementById('vwh-screen-app').style.display  = 'flex';
            document.getElementById('vwh-cfg').style.display         = 'inline-flex';
            dataSync();
            if (!radarTimer) radarTimer = setInterval(syncWarRoom, 1500);
            if (!syncTimer)  syncTimer  = setInterval(liveAutoSync, 15000);
        } else {
            openCfg(false);
        }
    }
    function openCfg(canCancel) {
        document.getElementById('vwh-screen-api').style.display = 'block';
        document.getElementById('vwh-screen-app').style.display = 'none';
        document.getElementById('vwh-api-cancel').style.display = canCancel ? 'inline-block' : 'none';
        document.getElementById('vwh-key-torn').value = KEYS.torn||'';
        document.getElementById('vwh-key-ts').value   = KEYS.ts||'';
    }
    document.getElementById('vwh-api-save').addEventListener('click', function() {
        var t = document.getElementById('vwh-key-torn').value.trim();
        if (t.length!==16) { alert('⚔ Torn API Key must be exactly 16 runes (characters).'); return; }
        KEYS.torn = t; KEYS.ts = document.getElementById('vwh-key-ts').value.trim();
        GM_setValue('vwh_key_torn', KEYS.torn); GM_setValue('vwh_key_ts', KEYS.ts);
        boot();
    });
    document.getElementById('vwh-cfg').addEventListener('click',        function() { openCfg(true); });
    document.getElementById('vwh-api-cancel').addEventListener('click', function() { boot(); });

    function showMsg(title, body, isErr, rawErr) {
        isErr = isErr||false;
        document.getElementById('vwh-data').style.display = 'none';
        var box = document.getElementById('vwh-msg'); box.style.display = 'block';
        document.getElementById('vwh-msg-title').textContent = title;
        document.getElementById('vwh-msg-title').style.color = isErr ? '#ff7070' : '#c9a84c';
        var html = body;
        if (isErr && rawErr) {
            html += '<div class="vwh-error-box">'+(rawErr.message||rawErr)+'</div>';
            html += '<button class="vwh-btn vwh-btn-gold" style="margin-top:14px;" id="vwh-reboot">ᚱ REBOOT</button>';
        }
        document.getElementById('vwh-msg-body').innerHTML = html;
        if (isErr && rawErr) {
            var rb = document.getElementById('vwh-reboot');
            if (rb) rb.addEventListener('click', function() { location.reload(); });
        }
    }

    // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    //  DATA SYNC
    // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    async function dataSync() {
        showMsg('Consulting the Norns…', 'Reading the threads of fate from Torn API…');
        try {
            var user = await gmGet('https://api.torn.com/user/?selections=profile&key='+KEYS.torn);
            if (user.error) throw new Error('User API: '+user.error.error);
            APP.uid = user.player_id;
            var facId = user.faction?.faction_id;
            if (!facId) throw new Error('You are not in a Faction, Warrior.');

            document.getElementById('vwh-msg').style.display  = 'none';
            document.getElementById('vwh-data').style.display = 'block';

            var hasLive = false;
            try {
                var rw = await gmGet('https://api.torn.com/v2/faction/'+facId+'/ranked_wars?key='+KEYS.torn);
                var wars = rw.ranked_wars || rw.faction?.ranked_wars || {};
                var wk = Object.keys(wars);
                if (wk.length > 0) {
                    var wd = wars[wk[0]];
                    if (Object.keys(wd.factions).length===2 && wd.factions[facId]?.members) {
                        hasLive = true;
                        await buildWar(wd.factions, facId, 'live');
                    }
                }
            } catch(e) { console.warn('[VWH] Live war skipped:', e); }

            if (!hasLive) {
                document.getElementById('live-wrap').style.display  = 'none';
                document.getElementById('live-empty').style.display = 'block';
            }
            if (MEM.lastReport) await loadReport(MEM.lastReport, facId);
        } catch(err) { showMsg('ᚷ Odin Refuses Entry', 'The runes cannot be read.', true, err); }
    }

    // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    //  REPORT LOADER
    // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    async function loadReport(id, facId) {
        try {
            var d;
            try { d = await gmGet('https://api.torn.com/v2/torn/'+id+'/ranked_war_report?key='+KEYS.torn); if(d.error) throw new Error(d.error.error); }
            catch(e) { d = await gmGet('https://api.torn.com/torn/'+id+'?selections=rankedwarreport&key='+KEYS.torn); if(d.error) throw new Error(d.error.error); }
            var rpt = d.ranked_war_report||d.rankedwarreport||d.report||d;
            await buildWar(rpt.factions, facId, 'past');
            document.getElementById('saga-status').textContent = 'ᚱ Saga #'+id+' summoned from the mists.';
        } catch(err) {
            document.getElementById('saga-status').textContent = 'ᚷ Error: '+err.message;
            document.getElementById('saga-status').style.color = '#ff7070';
        }
    }

    document.getElementById('saga-load').addEventListener('click', async function() {
        var id = document.getElementById('saga-id-input').value.trim();
        if (!id) return;
        document.getElementById('saga-status').textContent = 'ᚱ Summoning saga from the mists…';
        document.getElementById('saga-status').style.color = '#c9a84c';
        try {
            var u = await gmGet('https://api.torn.com/user/?selections=profile&key='+KEYS.torn);
            var facId = u.faction?.faction_id;
            GM_setValue('vwh_mem_report', id); MEM.lastReport = id;
            await loadReport(id, facId);
        } catch(err) { document.getElementById('saga-status').textContent='ᚷ Error: '+err.message; document.getElementById('saga-status').style.color='#ff7070'; }
    });

    // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    //  WAR INTERFACE BUILDER
    // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    async function buildWar(factions, myFacId, mode) {
        var px = mode==='live' ? 'live' : 'past';
        document.getElementById(px+'-empty').style.display = 'none';
        document.getElementById(px+'-wrap').style.display  = 'block';

        var fKeys = Object.keys(factions);
        var usId = myFacId, themId = fKeys.find(function(k){return k!=usId;});
        if (!factions[usId]) { usId=fKeys[0]; themId=fKeys[1]; }
        if (mode==='live') APP.enemyFacId = themId;

        var us = factions[usId], them = factions[themId];
        var rspEl = document.getElementById(px+'-my-rsp'), atkEl = document.getElementById(px+'-my-atk');
        if (APP.uid && us.members?.[APP.uid]) {
            rspEl.textContent = us.members[APP.uid].score.toFixed(2);
            atkEl.textContent = us.members[APP.uid].attacks;
        } else { rspEl.textContent='N/A'; atkEl.textContent='N/A'; }

        var sUs=us.score||0, sTh=them.score||0, tot=sUs+sTh||1;
        document.getElementById(px+'-bar-l').style.width   = (sUs/tot*100)+'%';
        document.getElementById(px+'-bar-l').textContent   = Math.floor(sUs);
        document.getElementById(px+'-bar-r').style.width   = (sTh/tot*100)+'%';
        document.getElementById(px+'-bar-r').textContent   = Math.floor(sTh);
        document.getElementById(px+'-bar-lbl').textContent = us.name+' ᚹ '+them.name;

        var liveRoster = {};
        try { var ed=await gmGet('https://api.torn.com/v2/faction/'+themId+'/members?key='+KEYS.torn); liveRoster=ed.members||{}; } catch(e){}

        var eMap = {};
        if (Array.isArray(liveRoster)) { liveRoster.forEach(function(m){eMap[m.id||m.player_id]=m.name||m.player_name||m.username;}); }
        else { for(var k in liveRoster){eMap[k]=liveRoster[k].name||liveRoster[k].player_name||liveRoster[k].username;} }

        var list = mode==='past' ? them.members : (Array.isArray(liveRoster)?eMap:liveRoster);

        // FF Scouter bulk
        var ffData = {};
        if (KEYS.torn && Object.keys(list).length>0) {
            try {
                var fd = await gmGet('https://ffscouter.com/api/v1/get-stats?key='+KEYS.torn+'&targets='+Object.keys(list).join(','));
                if (Array.isArray(fd)) { fd.forEach(function(r){if(r.player_id)ffData[r.player_id]={ff:r.fair_fight,est:r.bs_estimate_human};}); }
            } catch(e) { console.error('[VWH] FFScouter failed:', e); }
        }

        document.getElementById(px+'-cards').innerHTML = '';
        for (var id in list) {
            var nameRaw = list[id].name||list[id].player_name;
            var name = nameRaw || eMap[id] || ('Target ['+id+']');
            var status = {state:'Unknown'};
            if (Array.isArray(liveRoster)) { var m=liveRoster.find(function(x){return x.id==id||x.player_id==id;}); if(m&&m.status)status=m.status; }
            else if (liveRoster[id]?.status) status=liveRoster[id].status;
            injectCard(name, id, status, px, ffData);
        }
    }

    // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    //  ENEMY CARD
    // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    function injectCard(name, id, statusObj, prefix, ffData) {
        var container = document.getElementById(prefix+'-cards');
        var card = document.createElement('div');
        card.className = 'vwh-card';
        card.id = 'card-'+prefix+'-'+id;

        var sColor = '#6a5828';
        var state = statusObj?.state||'Unknown';
        if (state==='Hospital') sColor='#c03030';
        else if (state==='Okay') sColor='#3a8a3a';
        else if (state==='Jail') sColor='#c9a84c';

        var sText = state;
        if (statusObj?.until) sText += ' ('+Math.ceil((statusObj.until-(Date.now()/1000))/60)+'m)';

        card.innerHTML =
            '<div class="vwh-card-knotwork"></div>'+
            '<div class="vwh-card-top">'+
                '<a href="/profiles.php?XID='+id+'" target="_blank" class="vwh-card-name">'+name+' <span style="color:#4a3820;">['+id+']</span></a>'+
                '<span class="vwh-status-pill" id="sp-'+prefix+'-'+id+'" style="color:'+sColor+';">'+sText+'</span>'+
            '</div>'+
            '<div class="vwh-card-acts">'+
                '<button class="vwh-btn vwh-btn-blood btn-atk" style="padding:5px 10px; font-size:10px;">⚔ ATK</button>'+
                '<button class="vwh-btn vwh-btn-amber btn-sos" style="padding:5px 10px; font-size:10px;">🚨 SOS</button>'+
                '<button class="vwh-btn btn-mark" style="padding:5px 10px; font-size:10px;" title="Mark for Hitlist">🎯 MARK</button>'+
            '</div>'+
            '<div class="vwh-card-stats" id="ff-'+prefix+'-'+id+'"><span class="vwh-dim">ᚱ Consulting the Norns…</span></div>';

        container.appendChild(card);

        card.querySelector('.btn-atk').addEventListener('click', function() { window.open('/loader.php?sid=attack&user2ID='+id); });

        card.querySelector('.btn-mark').addEventListener('click', function() {
            var hl = document.getElementById('hitlist-cards');
            var he = document.getElementById('hitlist-empty');
            var clone = card.cloneNode(true);
            clone.id = 'card-hl-'+id;
            clone.querySelector('.btn-mark').remove();
            clone.querySelector('.btn-sos').remove();
            var del = document.createElement('button');
            del.className='vwh-btn vwh-btn-blood'; del.style='padding:5px 10px;font-size:10px;';
            del.textContent='✕ REMOVE';
            del.addEventListener('click', function() { clone.remove(); if(!hl.children.length){he.style.display='block';} });
            clone.querySelector('.vwh-card-acts').appendChild(del);
            var cloneAtk = clone.querySelector('.btn-atk');
            if (cloneAtk) cloneAtk.addEventListener('click', function() { window.open('/loader.php?sid=attack&user2ID='+id); });
            hl.appendChild(clone);
            he.style.display='none';
            toast('ᚷ '+name+' marked for Valhalla!');
        });

        card.querySelector('.btn-sos').addEventListener('click', function() {
            var caller = document.querySelector('.menu-value___3hOM0')?.innerText || 'A Viking';
            toast('🚨 SOS sent to the War Room!');
            document.querySelectorAll('.vwh-tab').forEach(function(t){t.classList.remove('active');});
            document.querySelectorAll('.vwh-pane').forEach(function(p){p.classList.remove('active');});
            document.querySelector('[data-pane="p-warroom"]').classList.add('active');
            document.getElementById('p-warroom').classList.add('active');
            gmPost(BACKEND_URL+'/api/sos', {target_id:id, target_name:name, caller_name:caller})
                .then(function(){syncWarRoom();})
                .catch(function(){ toast('SOS network failure', true); });
        });

        // Populate FF banner
        var banner = card.querySelector('#ff-'+prefix+'-'+id);
        var ffi = ffData?.[id];
        if (ffi && ffi.ff!==null) {
            banner.innerHTML = ffHTML(ffi.ff, ffi.est);
        } else if (KEYS.ts) {
            (async function() {
                var stats=null;
                try { var d=await gmGet('https://www.tornstats.com/api/v2/'+KEYS.ts+'/spy/'+id); if(d.status&&d.spy)stats=d.spy.total; } catch(e){}
                var fs=fmtStats(stats);
                banner.innerHTML = fs!=='N/A'
                    ? '<span style="color:#6a5020;font-weight:bold;margin-right:5px;">ᛏ Stats:</span><span style="color:#a08040;font-weight:bold;">'+fs+'</span>'
                    : '<span class="vwh-dim">ᚷ No intelligence found in the archives.</span>';
            })();
        } else {
            banner.innerHTML = '<span class="vwh-dim">ᚷ No intelligence found.</span>';
        }
    }

    // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    //  LIVE AUTO-SYNC (15s)
    // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    async function liveAutoSync() {
        if (!APP.enemyFacId) return;
        try {
            var d = await gmGet('https://api.torn.com/v2/faction/'+APP.enemyFacId+'/members?key='+KEYS.torn);
            for (var id in (d.members||{})) {
                var m=d.members[id], badge=document.getElementById('sp-live-'+id);
                if (badge && m.status) {
                    var txt=m.status.state;
                    if(m.status.until) txt+=' ('+Math.ceil((m.status.until-(Date.now()/1000))/60)+'m)';
                    badge.textContent=txt;
                    badge.style.color=m.status.state==='Hospital'?'#c03030':m.status.state==='Okay'?'#3a8a3a':'#c9a84c';
                }
            }
        } catch(e) { console.error('[VWH] Auto-sync failed:', e); }
    }

    // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    //  WAR ROOM
    // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    document.getElementById('wr-manual-btn').addEventListener('click', async function() {
        var inp = document.getElementById('wr-manual-id'), id=inp.value.trim(); if(!id)return;
        var btn=this, orig=btn.textContent;
        btn.textContent='SENT!'; btn.disabled=true;
        btn.classList.remove('vwh-btn-blood'); btn.classList.add('vwh-btn-forest');
        inp.value='';
        toast('🚨 SOS dispatched to the Hall!');
        var caller = document.querySelector('.menu-value___3hOM0')?.innerText||'A Viking';
        gmPost(BACKEND_URL+'/api/sos', {target_id:id, target_name:'Enemy ['+id+']', caller_name:caller})
            .then(function(){syncWarRoom();})
            .catch(function(){toast('SOS network error',true);});
        setTimeout(function(){
            btn.classList.remove('vwh-btn-forest'); btn.classList.add('vwh-btn-blood');
            btn.textContent=orig; btn.disabled=false;
        }, 700);
    });

    async function syncWarRoom() {
        try {
            var data = await gmGet(BACKEND_URL+'/api/sos');
            var list = document.getElementById('wr-list');
            if (!data.targets || !data.targets.length) {
                list.innerHTML='<div class="vwh-dim" style="text-align:center;padding:18px;">ᚹ No battle-calls echo in the Hall. ᚹ</div>';
                return;
            }
            var ffData={};
            if (KEYS.torn && data.targets.length) {
                try {
                    var fd=await gmGet('https://ffscouter.com/api/v1/get-stats?key='+KEYS.torn+'&targets='+data.targets.map(function(t){return t.target_id;}).join(','));
                    if(Array.isArray(fd)){fd.forEach(function(r){if(r.player_id)ffData[r.player_id]={ff:r.fair_fight,est:r.bs_estimate_human};});}
                } catch(e){}
            }
            list.innerHTML='';
            data.targets.forEach(function(t) {
                var card=document.createElement('div'); card.className='vwh-wr-card';
                card.innerHTML=
                    '<div style="font-size:10px;color:#4a2010;margin-bottom:5px;">ᚨ Spotted by: <strong style="color:#7a4020;">'+t.called_by+'</strong></div>'+
                    '<div style="margin-bottom:7px;">'+
                        '<a href="/profiles.php?XID='+t.target_id+'" target="_blank" class="vwh-card-name">🎯 '+t.target_name+' <span style="color:#4a3820;">['+t.target_id+']</span></a>'+
                    '</div>'+
                    '<div class="vwh-card-acts">'+
                        '<button class="vwh-btn vwh-btn-blood wr-atk" style="padding:5px 10px;font-size:10px;">⚔ ATTACK</button>'+
                        '<button class="vwh-btn vwh-btn-gold wr-resolve" data-id="'+t.target_id+'" style="padding:5px 10px;font-size:10px;">✓ RESOLVED</button>'+
                    '</div>'+
                    '<div id="wrff-'+t.target_id+'" class="vwh-card-stats" style="margin-top:6px;display:none;"></div>'+
                    '<div class="vwh-rune-hr"></div>'+
                    '<div style="display:flex;gap:6px;align-items:center;flex-wrap:wrap;">'+
                        '<input type="time" step="1" class="vwh-input wr-time" style="width:110px;padding:5px;">'+
                        '<button class="vwh-btn wr-copy" style="padding:5px 10px;font-size:10px;">📋 COPY DISPATCH</button>'+
                    '</div>';
                list.appendChild(card);

                card.querySelector('.wr-atk').addEventListener('click', function() { window.open('/loader.php?sid=attack&user2ID='+t.target_id); });
                card.querySelector('.wr-resolve').addEventListener('click', function(e) {
                    var tid=e.currentTarget.dataset.id;
                    card.style.opacity='0.4'; toast('Target vanquished!');
                    gmDel(BACKEND_URL+'/api/sos/'+tid).then(function(){card.remove();}).catch(function(){card.style.opacity='1';toast('Failed to resolve',true);});
                });
                card.querySelector('.wr-copy').addEventListener('click', function() {
                    var tv=card.querySelector('.wr-time').value;
                    var ts=tv?' — Sync at [b]'+tv+' TCT[/b]':'';
                    var msg='🚨 [b]SHIELD-BROTHERS, TO ARMS:[/b] [url=https://www.torn.com/loader.php?sid=attack&user2ID='+t.target_id+']'+t.target_name+' ['+t.target_id+'][/url]'+ts+' ⚔';
                    navigator.clipboard.writeText(msg).then(function(){toast('Battle-call copied!');});
                });

                var banner=card.querySelector('#wrff-'+t.target_id);
                var ffi=ffData[t.target_id];
                if(ffi&&ffi.ff!==null){banner.style.display='block';banner.innerHTML=ffHTML(ffi.ff,ffi.est);}
                else if(KEYS.ts){
                    (async function(){
                        try{var d=await gmGet('https://www.tornstats.com/api/v2/'+KEYS.ts+'/spy/'+t.target_id);if(d.status&&d.spy){var fs=fmtStats(d.spy.total);if(fs!=='N/A'){banner.style.display='block';banner.innerHTML='<span style="color:#6a5020;font-weight:bold;margin-right:5px;">ᛏ Stats:</span><span style="color:#a08040;font-weight:bold;">'+fs+'</span>';}}}catch(e){}
                    })();
                }
            });
        } catch(e){ console.log('[VWH] War Room offline:', e); }
    }

    // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    //  LAUNCH
    // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    if (document.readyState==='loading') {
        document.addEventListener('DOMContentLoaded', function(){if(wrap.style.display!=='none')boot();});
    } else { if(wrap.style.display!=='none')boot(); }

})();