LabTrack Controller

Immersive Labouchere strategy command center for Torn.com Russian Roulette — V8 redesign with hero layout, revolver visualization, animated sequence cards, and smart game-list integration

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

Advertisement:

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

Advertisement:

// ==UserScript==
// @name         LabTrack Controller
// @namespace    http://tampermonkey.net/
// @version      8.8
// @description  Immersive Labouchere strategy command center for Torn.com Russian Roulette — V8 redesign with hero layout, revolver visualization, animated sequence cards, and smart game-list integration
// @author       Nimo313 (Enhanced by Claude AI)
// @match        https://www.torn.com/page.php?sid=russianRoulette*
// @match        https://www.torn.com/trade.php*
// @icon         https://www.google.com/s2/favicons?domain=torn.com
// @license      MIT
// @homepage     https://greasyfork.org/de/scripts/561167-labtrack-controller-v7-00-enhanced-performance-modern-es6
// @supportURL   https://greasyfork.org/de/scripts/561167-labtrack-controller-v7-00-enhanced-performance-modern-es6/feedback
// @grant        none
// @run-at       document-start
// ==/UserScript==

(function() {
    'use strict';

    // Prevent multiple instances
    if (window.LabTrackRunning) return;
    window.LabTrackRunning = true;

    // =============================================================================
    // CONFIGURATION CONSTANTS - V7.00 Enhancement
    // =============================================================================
    const CONFIG = Object.freeze({
        VERSION: '8.8',
        RACE_LOCK_MS: 3000,              // Race condition protection
        DOM_DELAY_MS: 50,                 // DOM spy delay
        POLL_MS: 500,                     // Polling interval

        TOAST_MS: 2000,                   // Toast notification duration
        MANUAL_BET_TIMEOUT_MS: 30000,     // Manual bet tracking timeout
        STORAGE_SAVE: 'lt_standalone_save',
        STORAGE_SETTINGS: 'lt_settings',
        MAX_UNDO_STACK: 50,
        MAX_STORAGE_MB: 5,
        MULTIPLIERS: { '1x': 1, 'k': 1000, 'm': 1000000, 'b': 1000000000 },
        MIN_BET: 0.1,
        MIN_INT_BET: 1
    });

    // =============================================================================
    // LOGGER UTILITY - V7.00 Enhancement
    // =============================================================================
    class Logger {
        static LEVELS = Object.freeze({ DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3 });
        static currentLevel = Logger.LEVELS.INFO;

        static log(level, tag, msg) {
            if (level < this.currentLevel) return;
            const prefix = `[LabTrack v${CONFIG.VERSION}][${tag}]`;
            const timestamp = new Date().toISOString();
            const fullMsg = `${timestamp} ${prefix} ${msg}`;

            switch(level) {
                case this.LEVELS.DEBUG: console.debug(fullMsg); break;
                case this.LEVELS.INFO: console.log(fullMsg); break;
                case this.LEVELS.WARN: console.warn(fullMsg); break;
                case this.LEVELS.ERROR: console.error(fullMsg); break;
            }

            // Forward to audit log
            if (window.ltAuditLog) {
                const levelName = Object.keys(this.LEVELS).find(k => this.LEVELS[k] === level) || 'INFO';
                window.ltAuditLog.record(levelName, tag, msg);
            }
        }

        static debug(tag, msg) { this.log(this.LEVELS.DEBUG, tag, msg); }
        static info(tag, msg) { this.log(this.LEVELS.INFO, tag, msg); }
        static warn(tag, msg) { this.log(this.LEVELS.WARN, tag, msg); }
        static error(tag, msg) { this.log(this.LEVELS.ERROR, tag, msg); }
    }

    // =============================================================================
    // AUDIT LOG
    // =============================================================================
    class AuditLog {
        constructor() {
            this.entries = [];
            this.MAX_ENTRIES = 5000;
            this.MAX_STORAGE_ENTRIES = 1000;
            this.STORAGE_KEY = 'lt_audit_log';
            this._saveTimer = null;
            this._loadFromStorage();
        }

        _loadFromStorage() {
            try {
                const data = localStorage.getItem(this.STORAGE_KEY);
                if (data) {
                    const saved = JSON.parse(data);
                    if (Array.isArray(saved)) this.entries = saved;
                }
            } catch(e) {}
        }

        _scheduleSave() {
            clearTimeout(this._saveTimer);
            this._saveTimer = setTimeout(() => {
                try {
                    const toSave = this.entries.slice(-this.MAX_STORAGE_ENTRIES);
                    localStorage.setItem(this.STORAGE_KEY, JSON.stringify(toSave));
                } catch(e) {}
            }, 2000);
        }

        record(level, category, message, data = null) {
            const entry = { ts: new Date().toISOString(), level, cat: category, msg: message, data };
            this.entries.push(entry);
            if (this.entries.length > this.MAX_ENTRIES) this.entries.shift();
            this._scheduleSave();
        }

        download() {
            const header = [
                '='.repeat(70),
                `  LabTrack v${CONFIG.VERSION} — Audit Log`,
                `  Generated : ${new Date().toISOString()}`,
                `  Entries   : ${this.entries.length}`,
                '='.repeat(70),
                ''
            ].join('\n');

            const body = this.entries.map(e => {
                const dataStr = e.data ? `\n    >> ${JSON.stringify(e.data)}` : '';
                return `[${e.ts}] [${e.level.padEnd(5)}] [${e.cat.padEnd(16)}] ${e.msg}${dataStr}`;
            }).join('\n');

            const blob = new Blob([header + body], { type: 'text/plain;charset=utf-8' });
            const url = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = `labtrack_audit_${new Date().toISOString().slice(0, 19).replace(/[T:]/g, '-')}.txt`;
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
            URL.revokeObjectURL(url);
            Utils.showToast(`Log downloaded (${this.entries.length} entries)`);
        }
    }

    // =============================================================================
    // VALIDATOR UTILITY - V7.00 Enhancement
    // =============================================================================
    class Validator {
        static isBetValid(bet) {
            return typeof bet === 'number' &&
                   !isNaN(bet) &&
                   isFinite(bet) &&
                   bet > 0 &&
                   bet < Number.MAX_SAFE_INTEGER;
        }

        static isSequenceValid(seq) {
            if (!Array.isArray(seq)) return false;
            return seq.every(item =>
                item &&
                typeof item === 'object' &&
                typeof item.id === 'string' &&
                item.id.length > 0 &&
                typeof item.value === 'number' &&
                item.value > 0
            );
        }

        static isStateValid(state) {
            if (!state || typeof state !== 'object') return false;
            return this.isSequenceValid(state.sequence || []) &&
                   typeof state.totalProfit === 'number' &&
                   typeof state.roundCount === 'number' &&
                   Array.isArray(state.roundHistory);
        }

        static isSettingsValid(settings) {
            if (!settings || typeof settings !== 'object') return false;
            return ['bankroll', 'target'].includes(settings.mode) &&
                   typeof settings.multVal === 'number' &&
                   settings.multVal > 0;
        }
    }

    // --- TORNPDA / MOBILE CSS INJECTOR (Native) ---
    function addCustomStyle(css) {
        const style = document.createElement('style');
        style.id = 'lt-custom-styles';
        style.textContent = css;
        (document.head || document.documentElement).appendChild(style);
    }

    // --- CSS STYLES ---
    addCustomStyle(`
        /* Real UI typography — Inter for text, JetBrains Mono for numbers.
           Loaded from Google Fonts CDN. Falls back to system fonts if blocked. */
        @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;700&display=swap');

        #lt-dashboard {
            position: relative; width: 100%; margin-bottom: 20px;
            background: #181818; border: 1px solid #7c3aed; border-radius: 8px;
            color: #e2e8f0;
            font-family: 'Inter', 'Segoe UI', Tahoma, system-ui, sans-serif;
            -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
            font-feature-settings: 'cv11', 'ss01', 'ss03'; /* Inter stylistic alternates */
            z-index: 10;
            box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1);
            display: none; flex-direction: column; font-size: 13px; box-sizing: border-box;
        }
        #lt-dashboard.visible { display: flex; }

        #lt-dashboard.flash-win { border-color: #4ade80; box-shadow: 0 0 20px rgba(74, 222, 128, 0.2); }
        #lt-dashboard.flash-loss { border-color: #ef4444; box-shadow: 0 0 20px rgba(239, 68, 68, 0.2); }

        /* Hospital = pulsing red border. Other states keep the default calm purple. */
        #lt-dashboard {
            transition: border-color .5s ease, box-shadow .5s ease;
        }
        #lt-dashboard.lt-state-danger {
            border-color: #ef4444;
            animation: lt-glow-danger 1.2s ease-in-out infinite;
        }
        @keyframes lt-glow-danger {
            0%,100% { box-shadow: 0 0 16px rgba(239,68,68,0.40), 0 0 0 1px rgba(239,68,68,0.15) inset; }
            50%     { box-shadow: 0 0 32px 4px rgba(239,68,68,0.75), 0 0 0 1px rgba(239,68,68,0.30) inset; }
        }

        /* Full Screen Flash Overlay */
        #lt-flash-overlay {
            position: fixed; top: 0; left: 0; width: 100vw; height: 100vh;
            pointer-events: none; z-index: 999995; opacity: 0; transition: opacity 0.3s ease-out;
        }
        #lt-flash-overlay.win { background: rgba(74, 222, 128, 0.25); opacity: 1; }
        #lt-flash-overlay.loss { background: rgba(239, 68, 68, 0.25); opacity: 1; }

        /* Celebration particles — bursts from the bet display on each round result.
           Lives inside #lt-flash-overlay (fixed viewport positioning) and uses CSS
           custom properties for per-particle direction/rotation/distance. */
        .lt-particle {
            position: absolute;
            font-size: 22px; font-weight: 900; line-height: 1;
            pointer-events: none; user-select: none; will-change: transform, opacity;
            transform: translate(-50%, -50%);
            animation: lt-particle-burst 950ms cubic-bezier(.18,.79,.41,1) forwards;
        }
        .lt-particle.win  { color: #4ade80; text-shadow: 0 0 10px rgba(74,222,128,0.85), 0 0 22px rgba(34,197,94,0.45); }
        .lt-particle.loss { color: #f87171; text-shadow: 0 0 10px rgba(239,68,68,0.7); }
        @keyframes lt-particle-burst {
            0%   { transform: translate(-50%, -50%) scale(0.35) rotate(0deg); opacity: 0; }
            15%  { opacity: 1; transform: translate(calc(-50% + var(--lt-dx, 0px) * 0.15), calc(-50% + var(--lt-dy, 0px) * 0.15)) scale(1.15) rotate(calc(var(--lt-rot, 0deg) * 0.2)); }
            100% { transform: translate(calc(-50% + var(--lt-dx, 0px)), calc(-50% + var(--lt-dy, 0px))) scale(0.7) rotate(var(--lt-rot, 0deg)); opacity: 0; }
        }

        #lt-header {
            padding: 8px 15px; background: rgba(139, 92, 246, 0.15); display: flex;
            justify-content: space-between; align-items: center; font-weight: bold;
            border-bottom: 1px solid #333; border-radius: 8px 8px 0 0; user-select: none;
        }
        #lt-content { padding: 15px; display: flex; flex-direction: column; gap: 12px; position: relative; }
        /* Hospital warning — floats over the top of the content area so it never
           shifts the create-game buttons (which would risk a misclick). */
        #lt-hospital-warning {
            position: absolute; top: 8px; left: 15px; right: 15px;
            z-index: 6; margin: 0;
        }
        .lt-grid-main { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; align-items: start; }
        .lt-btn {
            border: none; border-radius: 6px; padding: 10px 12px; cursor: pointer; font-weight: bold;
            transition: all 0.2s; font-size: 12px; color: white; display: flex; align-items: center; justify-content: center; gap: 6px; width: 100%;
        }
        .lt-btn:hover { filter: brightness(1.2); transform: translateY(-1px); }
        .lt-btn:active { transform: translateY(0); }
        .lt-btn-primary { background: #7c3aed; }
        .lt-btn-win { background: rgba(34, 197, 94, 0.15); border: 1px solid #22c55e; color: #4ade80; }
        .lt-btn-loss { background: rgba(239, 68, 68, 0.15); border: 1px solid #ef4444; color: #f87171; }
        .lt-btn-action { background: #334155; color: #cbd5e1; border: 1px solid #475569; }
        .lt-btn-active { background: #f97316 !important; color: white !important; border-color: #ea580c !important; }
        .lt-btn-confirm { background: #15803d; color: white; border: 1px solid #22c55e; margin-top: 5px; }
        .lt-btn-play-again { background: #22c55e; color: white; font-size: 14px; padding: 15px; margin-top: 10px; box-shadow: 0 0 15px rgba(34, 197, 94, 0.4); }
        .lt-btn-new-random { background: #334155; color: #e2e8f0; font-size: 11px; padding: 8px; margin-top: 5px; border: 1px solid #475569; }
        .lt-btn-reset { background: #7f1d1d; color: #fecaca; font-size: 11px; padding: 8px; margin-top: 15px; border: 1px solid #ef4444; }

        #lt-btn-auto { background: #334155; color: #cbd5e1; border: 1px solid #475569; }
        #lt-btn-auto.active { background: rgba(34, 197, 94, 0.2); color: #4ade80; border-color: #22c55e; }

        /* Stats Row */
        .lt-stat-row { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; font-size: 11px; color: #94a3b8; margin-bottom: 5px; }
        .lt-stat-item { display: flex; justify-content: space-between; background: rgba(0,0,0,0.2); padding: 4px 8px; border-radius: 4px; }
        .lt-stat-val { font-weight: bold; color: #e2e8f0; }

        .lt-bet-box {
            text-align:center; margin: 10px 0; padding:15px; background:rgba(0,0,0,0.2);
            border-radius:8px; border:1px solid rgba(255,255,255,0.05); cursor: pointer; transition: border-color 0.2s;
        }
        .lt-bet-box:hover { border-color: #7c3aed; }
        .lt-bet-box.override { border-color: #eab308; background: rgba(234, 179, 8, 0.05); }
        .lt-big-val { font-size: 32px; font-weight: 900; color: #fff; text-align: center; margin: 5px 0; text-shadow: 0 0 20px rgba(168, 85, 247, 0.3); }
        .lt-big-val.override { color: #eab308; text-shadow: 0 0 20px rgba(234, 179, 8, 0.3); }

        .lt-seq-container {
            display: flex; flex-wrap: wrap; gap: 8px; padding: 12px; background: rgba(0,0,0,0.3);
            border-radius: 8px; border: 1px solid rgba(255,255,255,0.05); min-height: 50px; justify-content: center; align-items: center;
        }
        .lt-badge {
            background: #1e293b; border: 1px solid rgba(255,255,255,0.1); padding: 6px 10px; border-radius: 6px;
            font-family: monospace; font-size: 13px; font-weight: bold; cursor: pointer; user-select: none; transition: all 0.1s;
        }
        .lt-badge:hover { background: #7c3aed; color: white; border-color: #8b5cf6; }
        .lt-badge.selected { background: #f97316; color: white; border-color: #ea580c; transform: scale(1.05); }
        .lt-badge.dragging { opacity: 0.4; border: 1px dashed #fff; }

        /* Drag Visuals */
        .lt-badge.drop-left { border-left: 3px solid #38bdf8; margin-left: -3px; }
        .lt-badge.drop-right { border-right: 3px solid #38bdf8; margin-right: -3px; }

        .lt-input-group { display: flex; gap: 8px; margin-bottom: 8px; align-items: center; position: relative; max-width: 280px; }
        .lt-input { background: #0f172a; border: 1px solid #334155; color: white; padding: 8px; border-radius: 4px; width: 100%; font-size: 12px; transition: border-color 0.2s; }
        .lt-input.error { border-color: #ef4444; }
        .lt-label { font-size: 11px; color: #94a3b8; width: 70px; flex-shrink: 0; }
        .lt-suggest-btn {
            flex-shrink: 0; cursor: pointer; padding: 6px 10px; border-radius: 4px; font-size: 11px;
            background: rgba(124,58,237,0.15); color: #c4b5fd; border: 1px solid rgba(124,58,237,0.4);
            transition: background .15s, color .15s;
        }
        .lt-suggest-btn:hover { background: rgba(124,58,237,0.35); color: #fff; }
        .lt-input-preview { position: absolute; right: 10px; color: #4ade80; font-size: 10px; font-weight: bold; pointer-events: none; background: rgba(15, 23, 42, 0.9); padding-left: 5px; }

        /* Multiplier Buttons */
        .lt-mult-group { display: flex; gap: 4px; margin-bottom: 12px; justify-content: center; background: #0f172a; padding: 4px; border-radius: 6px; }
        .lt-mult-btn {
            flex: 1; background: #1e293b; border: 1px solid #334155; color: #94a3b8;
            padding: 4px; font-size: 11px; font-weight: bold; cursor: pointer; border-radius: 4px; transition: all 0.2s;
        }
        .lt-mult-btn:hover { background: #334155; }
        .lt-mult-btn.active { background: #7c3aed; color: white; border-color: #8b5cf6; }

        .lt-checkbox-row { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; font-size: 12px; color: #cbd5e1; cursor: pointer; }
        .lt-checkbox { width: 16px; height: 16px; cursor: pointer; accent-color: #7c3aed; }
        .lt-hidden { display: none !important; }
        .lt-tabs { display: flex; gap: 4px; margin-bottom: 12px; background: #0f172a; padding: 3px; border-radius: 6px; }
        .lt-tab { flex: 1; text-align: center; padding: 6px; cursor: pointer; border-radius: 4px; font-size: 12px; color: #94a3b8; transition: all 0.2s; }
        .lt-tab.active { background: #7c3aed; color: white; font-weight: bold; }

        .lt-toast {
            position: absolute; top: 10px; left: 50%; transform: translateX(-50%);
            background: rgba(0,0,0,0.9); color: white; padding: 8px 16px; border-radius: 20px;
            font-size: 12px; font-weight: bold; pointer-events: none; opacity: 0; transition: opacity 0.3s;
            z-index: 1000000; border: 1px solid #7c3aed; white-space: nowrap;
        }
        .lt-toast.show { opacity: 1; }

        /* History list: locked to ~10 rounds (≈350px) — older entries scroll
           instead of growing the panel and shifting everything below it. */
        .lt-history-list { height: 350px; max-height: 350px; overflow-y: auto; display: flex; flex-direction: column; gap: 4px; }
        .lt-history-list::-webkit-scrollbar { width: 8px; }
        .lt-history-list::-webkit-scrollbar-track { background: rgba(0,0,0,0.2); border-radius: 4px; }
        .lt-history-list::-webkit-scrollbar-thumb { background: rgba(124, 58, 237, 0.5); border-radius: 4px; }
        .lt-history-list::-webkit-scrollbar-thumb:hover { background: rgba(124, 58, 237, 0.7); }
        .lt-history-item { display: flex; justify-content: space-between; align-items: center; background: rgba(255,255,255,0.03); padding: 8px; border-radius: 4px; font-size: 12px; }
        .lt-history-win { border-left: 3px solid #4ade80; }
        .lt-history-loss { border-left: 3px solid #ef4444; }
        .lt-history-empty {
            border-left: 3px solid rgba(100,116,139,0.25);
            background: rgba(255,255,255,0.015);
            justify-content: center;
            opacity: 0.45;
        }
        .lt-hist-empty-line {
            color: #475569; font-size: 10px; font-style: italic;
            letter-spacing: 1px;
        }
        .lt-hist-badge { padding: 2px 6px; border-radius: 4px; font-weight: bold; font-size: 10px; }
        .lt-hist-win-bg { background: rgba(74, 222, 128, 0.2); color: #4ade80; }
        .lt-hist-loss-bg { background: rgba(239, 68, 68, 0.2); color: #f87171; }
        .lt-status-bar { font-size: 10px; text-align: center; margin-bottom: 8px; color: #64748b; font-weight: bold; text-transform: uppercase; letter-spacing: 0.5px; }

        /* RR motif glyph — small "chamber cross-section" (outer ring + bullet core).
           Used as a thematic dot in section titles and accent points instead of
           generic ◆/⊕/▣ symbols. Two sizes: default (.lt-glyph-chamber) for inline
           text accents, .lt-glyph-lg for the header logo. */
        .lt-glyph-chamber {
            display: inline-block;
            width: 8px; height: 8px; border-radius: 50%;
            background: radial-gradient(circle at center, #fbbf24 0%, #fbbf24 28%, #181818 32%, #181818 50%, #a78bfa 55%, #7c3aed 100%);
            box-shadow: 0 0 6px rgba(168, 85, 247, 0.55), inset 0 0 0 0.5px rgba(255,255,255,0.1);
            vertical-align: middle; flex-shrink: 0;
            transition: transform .15s ease;
        }
        .lt-glyph-chamber.lt-glyph-lg {
            width: 11px; height: 11px;
            box-shadow: 0 0 10px rgba(168, 85, 247, 0.75), inset 0 0 0 0.5px rgba(255,255,255,0.15);
        }
        .lt-section-title:hover .lt-glyph-chamber,
        .lt-lobby-section-title:hover .lt-glyph-chamber,
        .lt-coll-head:hover .lt-glyph-chamber { transform: rotate(60deg); }


        /* INFO PANEL & TOS - V7.00 Enhanced */
        .lt-info-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin-bottom: 15px; }
        .lt-info-card { background: rgba(124, 58, 237, 0.08); border: 1px solid rgba(124, 58, 237, 0.2); border-radius: 8px; padding: 12px; transition: all 0.2s; }
        .lt-info-card:hover { background: rgba(124, 58, 237, 0.12); border-color: rgba(124, 58, 237, 0.4); transform: translateY(-2px); box-shadow: 0 4px 12px rgba(124, 58, 237, 0.15); }
        .lt-info-head { font-weight: bold; color: #a78bfa; margin-bottom: 10px; font-size: 14px; border-bottom: 2px solid rgba(124, 58, 237, 0.3); padding-bottom: 6px; display: flex; align-items: center; gap: 8px; }
        .lt-info-list li { margin-bottom: 8px; color: #cbd5e1; list-style: none; position: relative; padding-left: 18px; line-height: 1.5; }
        .lt-info-list li::before { content: "▸"; color: #a78bfa; position: absolute; left: 0; font-weight: bold; font-size: 14px; }

        /* ToS Table - V7.00 Enhanced */
        .lt-tos-table { width: 100%; border-collapse: collapse; font-size: 11px; color: #ffffff; background: rgba(0,0,0,0.2); border-radius: 6px; overflow: hidden; }
        .lt-tos-table th { text-align: left; padding: 8px 10px; background: rgba(124, 58, 237, 0.15); color: #ffffff; font-weight: bold; border-bottom: 2px solid #7c3aed; }
        .lt-tos-table td { padding: 8px 10px; border-bottom: 1px solid rgba(124, 58, 237, 0.1); vertical-align: top; color: #e2e8f0; }
        .lt-tos-table td:first-child { color: #ffffff; font-weight: 600; width: 120px; }
        .lt-tos-table tr:hover { background: rgba(124, 58, 237, 0.08); }
        .lt-tos-table tr:last-child td { border-bottom: none; }

        /* Custom Scrollbars - V7.00 */
        #lt-info-panel::-webkit-scrollbar,
        #lt-gen-preview-seq::-webkit-scrollbar { width: 6px; }
        #lt-info-panel::-webkit-scrollbar-track,
        #lt-gen-preview-seq::-webkit-scrollbar-track { background: rgba(0,0,0,0.3); border-radius: 4px; }
        #lt-info-panel::-webkit-scrollbar-thumb,
        #lt-gen-preview-seq::-webkit-scrollbar-thumb { background: rgba(124, 58, 237, 0.5); border-radius: 4px; }
        #lt-info-panel::-webkit-scrollbar-thumb:hover,
        #lt-gen-preview-seq::-webkit-scrollbar-thumb:hover { background: rgba(124, 58, 237, 0.7); }

        /* V8.4: Game History permanently attached to the right edge of the dashboard */
        #lt-history-side {
            position: absolute; top: 0; left: 100%; margin-left: 12px;
            width: 290px; max-height: 85vh;
            background: linear-gradient(180deg, #1a1d27 0%, #14161f 100%);
            border: 1px solid #7c3aed; border-radius: 12px;
            box-shadow: 0 8px 32px rgba(0,0,0,0.5), 0 0 0 1px rgba(124,58,237,0.15) inset;
            display: flex; flex-direction: column; overflow: hidden;
        }
        .lt-hist-side-head {
            display: flex; align-items: center; padding: 12px 14px; flex-shrink: 0;
            background: linear-gradient(90deg, rgba(124,58,237,0.25), rgba(124,58,237,0.08));
            border-bottom: 1px solid rgba(124,58,237,0.2);
            font-size: 11px; font-weight: bold; color: #c4b5fd;
            text-transform: uppercase; letter-spacing: 1px;
        }
        #lt-hist-side-list { height: 360px; max-height: 360px; overflow-y: auto; padding: 10px; display: flex; flex-direction: column; gap: 5px; }
        #lt-hist-side-list::-webkit-scrollbar { width: 8px; }
        #lt-hist-side-list::-webkit-scrollbar-track { background: rgba(0,0,0,0.2); border-radius: 4px; }
        #lt-hist-side-list::-webkit-scrollbar-thumb { background: rgba(124, 58, 237, 0.5); border-radius: 4px; }
        #lt-hist-side-list::-webkit-scrollbar-thumb:hover { background: rgba(124, 58, 237, 0.7); }
        /* Hidden when too narrow to fit beside the dashboard */
        @media (max-width: 1180px) { #lt-history-side { display: none !important; } }
        /* When the wide RR layout is active, keep the dashboard a bit narrower so the
           history panel (absolute at its right edge) stays on-screen. */
        body.lt-rr-wide #lt-dashboard { max-width: calc(100% - 314px); }

        /* V8.3: Mini-Game Popups (draggable, floating over Torn) */
        .lt-popup {
            position: fixed; z-index: 1000000;
            background: #0a0e1a; border: 1px solid #7c3aed; border-radius: 12px;
            box-shadow: 0 16px 56px rgba(0,0,0,0.7), 0 0 0 1px rgba(124,58,237,0.25);
            overflow: hidden;
        }
        .lt-popup-header {
            display: flex; justify-content: space-between; align-items: center;
            padding: 8px 12px; cursor: move; user-select: none;
            background: linear-gradient(90deg, rgba(124,58,237,0.35), rgba(124,58,237,0.1));
            border-bottom: 1px solid rgba(124,58,237,0.25);
        }
        .lt-popup-title { font-size: 12px; font-weight: bold; color: #c4b5fd; letter-spacing: 0.4px; }
        .lt-popup-close {
            cursor: pointer; color: #f87171; font-weight: bold; font-size: 15px;
            line-height: 1; padding: 2px 6px; border-radius: 4px; transition: all .15s;
        }
        .lt-popup-close:hover { color: #fff; background: rgba(239,68,68,0.3); }
        .lt-popup-body { padding: 12px; }
        .lt-popup-body canvas { display: block; border-radius: 6px; cursor: pointer; }

        /* ═══════════════════════════════════════════════════════════
           V8.0 — IMMERSIVE REDESIGN
           ═══════════════════════════════════════════════════════════ */

        /* Dashboard polish */
        #lt-dashboard {
            background: linear-gradient(180deg, #1a1d27 0%, #14161f 100%);
            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4), 0 0 0 1px rgba(124, 58, 237, 0.15) inset;
        }

        /* Header upgrade */
        #lt-header {
            background: linear-gradient(90deg, rgba(124, 58, 237, 0.25) 0%, rgba(124, 58, 237, 0.08) 100%);
            padding: 10px 18px;
        }
        #lt-header-profit {
            font-size: 11px; color: #94a3b8; font-weight: bold; margin-left: 14px;
            padding: 3px 10px; border-radius: 10px; background: rgba(0,0,0,0.3);
        }
        #lt-header-profit.pos { color: #4ade80; background: rgba(34,197,94,0.12); }
        #lt-header-profit.neg { color: #f87171; background: rgba(239,68,68,0.12); }

        /* Hero grid */
        .lt-hero {
            display: grid;
            grid-template-columns: 1fr 1.6fr 1fr;
            gap: 14px;
            padding: 0;
            margin-bottom: 14px;
        }
        @media (max-width: 900px) { .lt-hero { grid-template-columns: 1fr; } }

        .lt-hero-side {
            display: flex; flex-direction: column; gap: 10px;
        }

        /* Stat cards */
        .lt-stat-card {
            background: rgba(0,0,0,0.3);
            border: 1px solid rgba(124, 58, 237, 0.18);
            border-radius: 10px;
            padding: 12px 14px;
            text-align: center;
            transition: border-color .2s, transform .15s;
        }
        .lt-stat-card:hover {
            border-color: rgba(124, 58, 237, 0.45);
            transform: translateY(-1px);
        }
        .lt-stat-card .sc-label {
            font-size: 10px; color: #64748b; font-weight: bold;
            text-transform: uppercase; letter-spacing: 0.7px;
            margin-bottom: 4px;
        }
        .lt-stat-card .sc-value {
            font-size: 22px; font-weight: 800; color: #e2e8f0;
            line-height: 1.1; font-variant-numeric: tabular-nums;
        }
        .lt-stat-card .sc-sub {
            font-size: 10px; color: #94a3b8; margin-top: 2px;
        }

        /* Streak flame — grows hotter with the streak length.
           Three tiers: warm (1-2), hot (3-5), inferno (6+). LOSS streaks of 2+
           get a cold ❄ counterpart so a bad run reads instantly too. */
        #lt-streak-val {
            display: inline-flex; align-items: center; gap: 6px;
            transition: text-shadow .25s ease;
        }
        .lt-streak-icon {
            display: inline-block; line-height: 1;
            transition: transform .3s cubic-bezier(.34,1.56,.64,1), filter .3s ease;
            transform-origin: 50% 80%;
        }
        .lt-streak-num { font-variant-numeric: tabular-nums; }
        /* Fire tier 1: warm — 1-2 WIN */
        #lt-streak-val.lt-streak-fire-1 { color: #fbbf24; }
        #lt-streak-val.lt-streak-fire-1 .lt-streak-icon {
            transform: scale(1.05);
            filter: drop-shadow(0 0 4px rgba(251,191,36,0.55));
        }
        /* Fire tier 2: hot — 3-5 WIN */
        #lt-streak-val.lt-streak-fire-2 {
            color: #fb923c;
            text-shadow: 0 0 10px rgba(251,146,60,0.55);
        }
        #lt-streak-val.lt-streak-fire-2 .lt-streak-icon {
            transform: scale(1.20);
            filter: drop-shadow(0 0 8px rgba(251,146,60,0.85)) drop-shadow(0 0 14px rgba(239,68,68,0.4));
            animation: lt-flame-flicker 1.4s ease-in-out infinite;
        }
        /* Fire tier 3: inferno — 6+ WIN */
        #lt-streak-val.lt-streak-fire-3 {
            color: #f87171;
            text-shadow: 0 0 14px rgba(239,68,68,0.7), 0 0 24px rgba(251,146,60,0.45);
        }
        #lt-streak-val.lt-streak-fire-3 .lt-streak-icon {
            transform: scale(1.35);
            filter: drop-shadow(0 0 10px rgba(239,68,68,0.95)) drop-shadow(0 0 20px rgba(251,146,60,0.7));
            animation: lt-flame-flicker 0.9s ease-in-out infinite;
        }
        @keyframes lt-flame-flicker {
            0%,100% { transform: scale(var(--lt-flame-scale, 1.20)) translateY(0); filter: brightness(1); }
            25%     { transform: scale(calc(var(--lt-flame-scale, 1.20) * 1.06)) translateY(-1px); filter: brightness(1.15); }
            50%     { transform: scale(calc(var(--lt-flame-scale, 1.20) * 0.97)) translateY(1px); filter: brightness(0.95); }
            75%     { transform: scale(calc(var(--lt-flame-scale, 1.20) * 1.04)) translateY(-1px); filter: brightness(1.10); }
        }
        #lt-streak-val.lt-streak-fire-2 .lt-streak-icon { --lt-flame-scale: 1.20; }
        #lt-streak-val.lt-streak-fire-3 .lt-streak-icon { --lt-flame-scale: 1.35; }
        /* Cold streak (2+ losses in a row) */
        #lt-streak-val.lt-streak-cold {
            color: #93c5fd;
            text-shadow: 0 0 10px rgba(147,197,253,0.45);
        }
        #lt-streak-val.lt-streak-cold .lt-streak-icon {
            transform: scale(1.10);
            filter: drop-shadow(0 0 6px rgba(147,197,253,0.85));
        }

        /* Hero center */
        .lt-hero-center {
            background: radial-gradient(ellipse at center, rgba(124, 58, 237, 0.12) 0%, rgba(0,0,0,0.2) 70%);
            border: 1px solid rgba(124, 58, 237, 0.25);
            border-radius: 14px;
            padding: 22px 18px 18px;
            display: flex; flex-direction: column; align-items: center; gap: 14px;
            position: relative; overflow: hidden;
            cursor: default;
        }
        .lt-hero-center.override { border-color: rgba(251, 191, 36, 0.45); }
        .lt-hero-center::before {
            content: ''; position: absolute; top: 0; left: 0; right: 0; height: 1px;
            background: linear-gradient(90deg, transparent, rgba(168, 85, 247, 0.6), transparent);
        }

        .lt-bet-label-v8 {
            font-size: 10px; color: #a78bfa; font-weight: bold;
            text-transform: uppercase; letter-spacing: 2px;
        }
        #lt-bet-display.lt-mega {
            font-size: 56px; font-weight: 900; color: #fff;
            text-shadow: 0 0 32px rgba(168, 85, 247, 0.5), 0 2px 4px rgba(0,0,0,0.5);
            line-height: 1; cursor: pointer; transition: text-shadow .25s, transform .15s;
            font-variant-numeric: tabular-nums;
        }
        #lt-bet-display.lt-mega:hover {
            text-shadow: 0 0 48px rgba(168, 85, 247, 0.9), 0 2px 4px rgba(0,0,0,0.5);
            transform: scale(1.02);
        }
        #lt-bet-display.lt-mega.override {
            color: #fbbf24;
            text-shadow: 0 0 32px rgba(251, 191, 36, 0.5), 0 2px 4px rgba(0,0,0,0.5);
        }

        .lt-action-row-mega {
            display: flex; gap: 12px; width: 100%;
        }
        .lt-btn-mega {
            flex: 1; padding: 16px 12px; border-radius: 10px;
            font-size: 14px; font-weight: 800; letter-spacing: 1px;
            cursor: pointer; transition: all .15s;
            border: 1px solid; text-transform: uppercase;
        }
        .lt-btn-mega-win {
            background: linear-gradient(180deg, rgba(34, 197, 94, 0.25), rgba(34, 197, 94, 0.08));
            border-color: #22c55e; color: #4ade80;
        }
        .lt-btn-mega-win:hover {
            background: linear-gradient(180deg, rgba(34, 197, 94, 0.4), rgba(34, 197, 94, 0.15));
            box-shadow: 0 0 24px rgba(34, 197, 94, 0.35);
            transform: translateY(-1px);
        }
        .lt-btn-mega-loss {
            background: linear-gradient(180deg, rgba(239, 68, 68, 0.25), rgba(239, 68, 68, 0.08));
            border-color: #ef4444; color: #f87171;
        }
        .lt-btn-mega-loss:hover {
            background: linear-gradient(180deg, rgba(239, 68, 68, 0.4), rgba(239, 68, 68, 0.15));
            box-shadow: 0 0 24px rgba(239, 68, 68, 0.35);
            transform: translateY(-1px);
        }
        .lt-btn-mega:active { transform: translateY(0); }
        /* Auto:ON greys out manual WIN/LOSS to prevent double-counting */
        .lt-btn-mega.lt-disabled-auto {
            background: rgba(100, 116, 139, 0.18) !important;
            border-color: rgba(100, 116, 139, 0.35) !important;
            color: #64748b !important;
            cursor: not-allowed; opacity: 0.55;
            box-shadow: none !important; transform: none !important;
            filter: grayscale(0.6);
        }
        .lt-btn-mega.lt-disabled-auto:hover { background: rgba(100, 116, 139, 0.18) !important; box-shadow: none !important; transform: none !important; }

        .lt-mini-row { display: flex; gap: 8px; width: 100%; }
        .lt-mini-btn {
            flex: 1; padding: 7px; border-radius: 6px; font-size: 11px; font-weight: bold;
            cursor: pointer; border: 1px solid #334155; background: rgba(15, 23, 42, 0.6);
            color: #cbd5e1; transition: all .15s;
        }
        .lt-mini-btn:hover { border-color: #7c3aed; color: #fff; background: rgba(124, 58, 237, 0.15); }
        #lt-btn-auto.lt-mini-btn.active { background: rgba(34, 197, 94, 0.15); color: #4ade80; border-color: #22c55e; }

        /* Sequence section */
        .lt-section {
            background: rgba(0,0,0,0.25);
            border: 1px solid rgba(124, 58, 237, 0.15);
            border-radius: 10px;
            padding: 12px 14px;
            margin-bottom: 12px;
        }
        .lt-section-title {
            font-size: 10px; color: #a78bfa; font-weight: bold;
            text-transform: uppercase; letter-spacing: 1.5px;
            margin-bottom: 10px; display: flex; justify-content: space-between; align-items: center;
        }
        .lt-section-title .meta { color: #64748b; letter-spacing: 0.5px; }

        /* Sequence cards (V8 upgrade) */
        .lt-seq-card {
            position: relative;
            display: inline-flex; align-items: center; justify-content: center;
            min-width: 58px; padding: 10px 14px;
            background: linear-gradient(180deg, #1e293b, #0f172a);
            border: 1px solid #334155; border-radius: 8px;
            font-family: 'Segoe UI', monospace; font-size: 15px; font-weight: 700;
            color: #cbd5e1; cursor: pointer; user-select: none;
            transition: all .15s;
            box-shadow: 0 2px 6px rgba(0,0,0,0.3), 0 1px 0 rgba(255,255,255,0.05) inset;
        }
        .lt-seq-card:hover { transform: translateY(-2px); border-color: #7c3aed; }
        /* Delete (×) badge on each card */
        .lt-seq-card-del {
            position: absolute; top: -7px; right: -7px;
            width: 17px; height: 17px; border-radius: 50%;
            background: #ef4444; color: #fff; font-size: 12px; font-weight: bold;
            display: none; align-items: center; justify-content: center; line-height: 1;
            cursor: pointer; border: 1px solid #0f172a; z-index: 2;
        }
        .lt-seq-card:hover .lt-seq-card-del { display: flex; }
        .lt-seq-card-del:hover { background: #dc2626; transform: scale(1.15); }
        /* Add (+) card */
        .lt-seq-add {
            color: #a78bfa; border-style: dashed; border-color: #7c3aed;
            background: rgba(124,58,237,0.08); font-size: 20px; min-width: 42px;
        }
        .lt-seq-add:hover { background: rgba(124,58,237,0.2); color: #fff; transform: translateY(-2px); }
        /* Collapsible sequence section */
        .lt-seq-toggle { cursor: pointer; }
        .lt-seq-toggle .lt-coll-icon { transition: transform .2s; font-size: 10px; margin-left: 4px; }
        .lt-seq-section.collapsed .lt-coll-icon { transform: rotate(-90deg); }
        .lt-seq-collapse-body { overflow: hidden; transition: max-height .25s ease-out; max-height: 1000px; }
        .lt-seq-section.collapsed .lt-seq-collapse-body { max-height: 0; }

        /* Big profit chart — clean stock-style: one dot per round, the connecting
           segment between two rounds is coloured by direction (green = up, red = down).
           Axis labels left + bottom, dashed zero baseline, no fill area. */
        .lt-profit-chart-wrap {
            position: relative; width: 100%; height: 290px;
            background: #1f1f1f;
            border: 1px solid rgba(124,58,237,0.18); border-radius: 10px;
            padding: 12px 12px 26px; box-sizing: border-box;
        }
        .lt-profit-chart { width: 100%; height: 100%; overflow: visible; display: block; }
        .lt-chart-grid line { stroke: rgba(255,255,255,0.06); stroke-width: 1; }
        .lt-chart-axis-y text, .lt-chart-axis-x text { fill: #94a3b8; font-size: 10px; font-family: monospace; }
        .lt-chart-segments line { stroke-width: 2; stroke-linecap: round; }
        .lt-chart-dot {
            stroke: #1f1f1f; stroke-width: 1.5;
            transition: r .15s ease, filter .15s ease;
            cursor: pointer;
        }
        .lt-chart-dot.win  { fill: #4ade80; }
        .lt-chart-dot.loss { fill: #ef4444; }
        .lt-chart-dot:hover { r: 5; filter: drop-shadow(0 0 8px currentColor) brightness(1.25); }
        .lt-chart-legend {
            position: absolute; left: 0; right: 0; bottom: 6px;
            text-align: center; font-size: 11px; color: #94a3b8;
            font-family: 'Segoe UI', sans-serif;
        }
        .lt-chart-legend-dot {
            display: inline-block; width: 9px; height: 9px; border-radius: 50%;
            background: #4ade80; margin-right: 4px; vertical-align: middle;
            box-shadow: 0 0 6px rgba(74,222,128,0.6);
        }
        .lt-chart-empty {
            position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
            color: #64748b; font-size: 12px; font-style: italic; pointer-events: none;
        }
        .lt-chart-empty.hidden { display: none; }
        .lt-chart-tooltip {
            position: absolute; pointer-events: none;
            background: rgba(15,23,42,0.98); border: 1px solid rgba(167,139,250,0.55);
            border-radius: 8px; padding: 10px 14px; font-size: 13px; color: #e2e8f0;
            box-shadow: 0 6px 22px rgba(0,0,0,0.65);
            opacity: 0; transition: opacity .12s ease;
            white-space: nowrap; z-index: 10;
            font-family: 'JetBrains Mono', 'Consolas', monospace; line-height: 1.55;
        }
        .lt-chart-tooltip.show { opacity: 1; }
        .lt-chart-tooltip b {
            font-family: 'Inter', 'Segoe UI', sans-serif;
            font-weight: 700; font-size: 13px;
        }
        .lt-seq-card.edge {
            border-color: #22c55e; color: #4ade80;
            background: linear-gradient(180deg, rgba(34,197,94,0.18), #0f172a);
            box-shadow: 0 0 14px rgba(34, 197, 94, 0.35), 0 1px 0 rgba(255,255,255,0.05) inset;
        }
        .lt-seq-card.selected {
            background: linear-gradient(180deg, #f97316, #ea580c);
            border-color: #ea580c; color: #fff; transform: scale(1.06);
        }
        .lt-seq-card.dragging { opacity: 0.4; border: 1px dashed #fff; }
        .lt-seq-card.drop-left { border-left: 3px solid #38bdf8; }
        .lt-seq-card.drop-right { border-right: 3px solid #38bdf8; }

        /* Stop-loss / sequence-length warning */
        .lt-stop-warning {
            display: flex; align-items: center; justify-content: space-between; gap: 12px;
            padding: 10px 14px; margin: 0 0 12px;
            background: linear-gradient(90deg, rgba(220,38,38,0.22), rgba(220,38,38,0.10));
            border: 1px solid rgba(248,113,113,0.55); border-left: 4px solid #ef4444;
            border-radius: 10px; animation: lt-stop-pulse 1.6s ease-in-out infinite;
        }
        @keyframes lt-stop-pulse {
            0%,100% { box-shadow: 0 0 0 0 rgba(239,68,68,0.0); }
            50% { box-shadow: 0 0 12px 1px rgba(239,68,68,0.35); }
        }
        .lt-stop-warning-text { display: flex; flex-direction: column; gap: 2px; }
        .lt-stop-warning-title { font-size: 13px; font-weight: 700; color: #fecaca; }
        .lt-stop-warning-reason { font-size: 11px; color: #fca5a5; }
        .lt-stop-warning-btn {
            flex-shrink: 0; cursor: pointer; padding: 8px 14px; border-radius: 8px;
            background: #ef4444; color: #fff; border: none; font-size: 12px; font-weight: 600;
            transition: background .15s, transform .1s;
        }
        .lt-stop-warning-btn:hover { background: #dc2626; transform: translateY(-1px); }
        .lt-stop-warning-close {
            flex-shrink: 0; cursor: pointer; width: 24px; height: 24px; border-radius: 50%;
            background: transparent; color: #fca5a5; border: 1px solid rgba(248, 113, 113, 0.4);
            font-size: 11px; line-height: 1; padding: 0;
            display: flex; align-items: center; justify-content: center;
            transition: background .15s, color .15s, transform .1s;
        }
        .lt-stop-warning-close:hover { background: rgba(239, 68, 68, 0.25); color: #fee2e2; transform: scale(1.1); }

        /* Action bar at bottom */
        .lt-action-bar {
            display: flex; gap: 16px; padding: 10px 14px; margin-bottom: 12px;
            background: rgba(0,0,0,0.25); border: 1px solid rgba(124, 58, 237, 0.12);
            border-radius: 10px; flex-wrap: wrap;
        }
        .lt-action-group { display: flex; gap: 6px; flex-wrap: wrap; }
        .lt-action-group + .lt-action-group {
            padding-left: 16px; border-left: 1px solid rgba(124, 58, 237, 0.15);
        }

        /* V8.2: Hide Torn's appContainer off-screen (still clickable for forwarding).
           Synthetic clicks work (START opens the confirmation) — we build our own UI
           and forward user-initiated clicks to Torn's real elements. */
        div[class^="appContainer"] {
            position: absolute !important;
            left: -99999px !important; top: 0 !important;
            width: 1px !important; height: 1px !important;
            overflow: hidden !important; opacity: 0 !important;
        }

        /* ════════════ Custom appContainer (V8.2 Rebuild) ════════════ */
        #lt-app { background: linear-gradient(180deg, rgba(124,58,237,0.04), rgba(0,0,0,0.25)); }
        .lt-app-header {
            background: linear-gradient(90deg, rgba(124,58,237,0.22) 0%, rgba(124,58,237,0.04) 100%);
            padding: 12px 18px; display: flex; justify-content: space-between; align-items: center;
            border-bottom: 1px solid rgba(124,58,237,0.2); flex-wrap: wrap; gap: 10px;
        }
        .lt-app-title { color: #e2e8f0; font-size: 15px; font-weight: 700; letter-spacing: 0.6px; display: flex; align-items: center; gap: 8px; }
        .lt-app-title::before { content: '◉'; color: #a78bfa; font-size: 14px; }
        .lt-app-nav { display: flex; gap: 4px; flex-wrap: wrap; }
        .lt-nav-btn {
            padding: 5px 12px; color: #94a3b8; font-size: 11px; font-weight: bold; cursor: pointer;
            border-radius: 4px; border: 1px solid transparent; background: rgba(0,0,0,0.2); transition: all .15s;
        }
        .lt-nav-btn:hover { color: #fff; background: rgba(124,58,237,0.18); border-color: rgba(124,58,237,0.4); }

        /* IN-GAME */
        .lt-igs { padding: 18px; }
        .lt-igs-top {
            display: grid; grid-template-columns: 1fr auto 1fr; gap: 16px; align-items: center;
            margin-bottom: 16px; padding: 14px 18px; background: rgba(0,0,0,0.3);
            border-radius: 10px; border: 1px solid rgba(124,58,237,0.15);
        }
        .lt-igs-block { display: flex; flex-direction: column; gap: 3px; }
        .lt-igs-block.right { align-items: flex-end; text-align: right; }
        .lt-igs-label { font-size: 10px; color: #64748b; font-weight: bold; text-transform: uppercase; letter-spacing: 1px; }
        .lt-igs-timer { font-size: 28px; font-weight: 900; color: #fbbf24; font-variant-numeric: tabular-nums; line-height: 1; text-shadow: 0 0 16px rgba(251,191,36,0.3); }
        .lt-igs-pot { font-size: 32px; font-weight: 900; color: #4ade80; line-height: 1; text-shadow: 0 0 16px rgba(74,222,128,0.3); }
        .lt-igs-sound { background: rgba(124,58,237,0.15); color: #a78bfa; border: 1px solid #7c3aed; padding: 8px 14px; border-radius: 6px; font-size: 11px; font-weight: bold; cursor: pointer; transition: all .15s; }
        .lt-igs-sound:hover { background: rgba(124,58,237,0.3); color: #fff; }
        .lt-igs-msg { color: #cbd5e1; font-size: 13px; font-style: italic; padding: 12px 16px; background: rgba(0,0,0,0.25); border-radius: 8px; border-left: 3px solid #7c3aed; margin-bottom: 16px; }
        .lt-igs-players { display: grid; grid-template-columns: 1fr 24px 1fr; gap: 12px; align-items: center; margin-bottom: 16px; }
        .lt-vs { color: #ef4444; font-weight: 900; font-size: 18px; text-align: center; text-shadow: 0 0 12px rgba(239,68,68,0.5); }
        .lt-player { position: relative; background: rgba(0,0,0,0.35); border: 1px solid rgba(124,58,237,0.2); border-radius: 10px; padding: 14px; text-align: center; display: flex; flex-direction: column; align-items: center; gap: 8px; min-height: 100px; justify-content: center; }
        .lt-player.waiting { opacity: 0.6; border-style: dashed; }
        .lt-player-avatar { width: 48px; height: 48px; border-radius: 50%; border: 2px solid #7c3aed; background: #1e293b; object-fit: cover; }
        .lt-player-avatar-ph { width: 48px; height: 48px; border-radius: 50%; border: 2px dashed #475569; background: rgba(0,0,0,0.3); display: flex; align-items: center; justify-content: center; color: #64748b; font-size: 20px; }
        .lt-player-name { color: #e2e8f0; font-weight: bold; font-size: 13px; }
        .lt-player-name.waiting { color: #64748b; font-style: italic; }
        .lt-player-link { color: inherit; text-decoration: none; }
        .lt-player-link:hover { color: #c4b5fd; text-decoration: underline; }
        .lt-igs-controls { display: flex; gap: 10px; justify-content: center; }
        .lt-leave-btn { padding: 10px 28px; border-radius: 6px; background: rgba(239,68,68,0.15); color: #f87171; border: 1px solid #ef4444; font-weight: bold; font-size: 12px; cursor: pointer; letter-spacing: 0.8px; transition: all .15s; text-transform: uppercase; }
        .lt-leave-btn:hover { background: rgba(239,68,68,0.3); color: #fff; box-shadow: 0 0 20px rgba(239,68,68,0.4); }

        /* Shot buttons (shoot / x2 / x3) */
        .lt-igs-shots { display: flex; gap: 10px; justify-content: center; margin-bottom: 16px; flex-wrap: wrap; }
        .lt-shot-btn { padding: 14px 30px; border-radius: 8px; font-weight: 800; font-size: 15px; cursor: pointer; text-transform: uppercase; letter-spacing: 1px; transition: all .15s; border: 1px solid; }
        .lt-shot-btn:active { transform: translateY(1px); }
        .lt-shot-btn.primary { background: linear-gradient(180deg, rgba(239,68,68,0.3), rgba(239,68,68,0.1)); border-color: #ef4444; color: #fca5a5; }
        .lt-shot-btn.primary:hover { background: linear-gradient(180deg, rgba(239,68,68,0.5), rgba(239,68,68,0.2)); box-shadow: 0 0 24px rgba(239,68,68,0.45); color: #fff; }
        .lt-shot-btn.mult { background: linear-gradient(180deg, rgba(251,191,36,0.25), rgba(251,191,36,0.08)); border-color: #fbbf24; color: #fbbf24; }
        .lt-shot-btn.mult:hover { background: linear-gradient(180deg, rgba(251,191,36,0.4), rgba(251,191,36,0.15)); box-shadow: 0 0 24px rgba(251,191,36,0.4); color: #fff; }
        .lt-shot-btn.armed {
            background: linear-gradient(180deg, rgba(239,68,68,0.55), rgba(239,68,68,0.25));
            border-color: #ef4444; color: #fff;
            animation: lt-shot-armed-pulse 0.7s ease-in-out infinite;
        }
        @keyframes lt-shot-armed-pulse {
            0%,100% { box-shadow: 0 0 0 0 rgba(239,68,68,0.0); }
            50%     { box-shadow: 0 0 28px 4px rgba(239,68,68,0.65); }
        }
        .lt-shot-btn.take-action { background: linear-gradient(180deg, rgba(168,85,247,0.3), rgba(168,85,247,0.1)); border-color: #a855f7; color: #c4b5fd; }
        .lt-shot-btn.take-action:hover { background: linear-gradient(180deg, rgba(168,85,247,0.5), rgba(168,85,247,0.2)); box-shadow: 0 0 24px rgba(168,85,247,0.45); color: #fff; }

        /* Revolver cylinder (top view), sound-synced */
        .lt-revolver { display: flex; justify-content: center; align-items: center; margin: 6px 0 18px; }
        .lt-revolver.fire { animation: lt-rev-recoil 0.5s cubic-bezier(0.2, 0.8, 0.3, 1); }
        @keyframes lt-rev-recoil {
            0%   { transform: translateY(0) scale(1); }
            10%  { transform: translateY(-13px) scale(1.11); }  /* sharp kick */
            28%  { transform: translateY(5px) scale(0.97); }    /* bounce */
            55%  { transform: translateY(-2px) scale(1.01); }
            100% { transform: translateY(0) scale(1); }
        }
        .lt-rev-cyl {
            position: relative; width: 104px; height: 104px; border-radius: 50%;
            background:
                radial-gradient(circle at 50% 50%, transparent 53%, rgba(0,0,0,0.45) 55%, transparent 57%),
                radial-gradient(circle at 38% 30%, #535a76, #1e222d 74%);
            border: 3px solid #5d6486;
            box-shadow: 0 0 0 4px #23283c, 0 0 0 6px #363c56, 0 8px 22px rgba(0,0,0,0.6),
                        inset 0 2px 14px rgba(0,0,0,0.7), inset 0 0 0 1px rgba(255,255,255,0.07);
            /* snappy mechanical click into place (strong overshoot, short duration) */
            transition: transform 0.25s cubic-bezier(0.18, 1.7, 0.4, 1);
        }
        .lt-rev-cyl.click { animation: lt-rev-click 0.22s ease-out; }
        @keyframes lt-rev-click { 0% { filter: brightness(1); } 35% { filter: brightness(1.55); } 100% { filter: brightness(1); } }
        .lt-rev-chamber {
            position: absolute; width: 22px; height: 22px; border-radius: 50%;
            background: radial-gradient(circle at 42% 35%, #12151f 28%, #000 86%);
            border: 2px solid #6c7398;
            box-shadow: inset 0 2px 6px rgba(0,0,0,0.95), inset 0 -1px 2px rgba(255,255,255,0.09);
            top: 50%; left: 50%;
        }
        .lt-rev-hub {
            position: absolute; top: 50%; left: 50%; width: 22px; height: 22px;
            transform: translate(-50%,-50%); border-radius: 50%;
            background: radial-gradient(circle at 38% 32%, #868eb2, #2c3146);
            border: 1px solid #8b92b8; z-index: 2;
            box-shadow: 0 1px 3px rgba(0,0,0,0.6), inset 0 1px 2px rgba(255,255,255,0.35);
        }
        .lt-rev-flash {
            position: absolute; top: 50%; left: 50%; width: 10px; height: 10px;
            transform: translate(-50%,-50%); border-radius: 50%; opacity: 0;
            background: radial-gradient(circle, #fff 0%, #fff 14%, #fde68a 36%, #f97316 60%, rgba(239,68,68,0.45) 80%, transparent 88%);
            pointer-events: none; z-index: 4; mix-blend-mode: screen;
        }
        .lt-rev-flash.flash { animation: lt-rev-flash 0.38s ease-out; }
        @keyframes lt-rev-flash {
            0%   { width: 12px; height: 12px; opacity: 1; }
            30%  { opacity: 1; }
            100% { width: 210px; height: 210px; opacity: 0; }
        }

        /* Death cross over a player */
        .lt-player.dead { border-color: #ef4444 !important; box-shadow: 0 0 22px rgba(239,68,68,0.5); }
        .lt-player-cross { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; pointer-events: none; z-index: 5; }
        .lt-player-cross::before {
            content: '✕'; color: #ef4444; font-size: 60px; font-weight: 900; line-height: 1;
            text-shadow: 0 0 22px rgba(239,68,68,0.9); animation: lt-cross-in 0.35s ease-out;
        }
        @keyframes lt-cross-in { 0% { transform: scale(2.2); opacity: 0; } 60% { transform: scale(0.9); } 100% { transform: scale(1); opacity: 1; } }

        /* CONFIRM (game create/join) */
        /* Constant top padding reserves a zone for the hospital warning so the YES/NO
           buttons never shift — you can spam-click YES without the layout moving. */
        .lt-confirm { position: relative; padding: 52px 20px 24px; display: flex; flex-direction: column; align-items: center; gap: 18px; }
        .lt-confirm-text { color: #e2e8f0; font-size: 15px; text-align: center; line-height: 1.5; }
        .lt-confirm-text b { color: #fbbf24; }
        .lt-confirm-btns { display: flex; gap: 14px; }
        .lt-confirm-yes { background: linear-gradient(180deg, rgba(34,197,94,0.25), rgba(34,197,94,0.08)); border: 1px solid #22c55e; color: #4ade80; padding: 11px 36px; border-radius: 6px; font-weight: bold; font-size: 13px; cursor: pointer; transition: all .15s; text-transform: uppercase; letter-spacing: 0.6px; }
        .lt-confirm-yes:hover { background: linear-gradient(180deg, rgba(34,197,94,0.4), rgba(34,197,94,0.15)); box-shadow: 0 0 16px rgba(34,197,94,0.35); }
        .lt-confirm-no { background: rgba(239,68,68,0.12); border: 1px solid #ef4444; color: #f87171; padding: 11px 36px; border-radius: 6px; font-weight: bold; font-size: 13px; cursor: pointer; transition: all .15s; text-transform: uppercase; letter-spacing: 0.6px; }
        .lt-confirm-no:hover { background: rgba(239,68,68,0.25); color: #fff; }
        /* Absolutely positioned in the reserved top zone → does NOT push the buttons. */
        .lt-confirm-warn {
            position: absolute; top: 10px; left: 16px; right: 16px;
            background: rgba(239,68,68,0.18); border: 1px solid #ef4444; color: #fca5a5;
            padding: 8px 14px; border-radius: 8px; font-size: 13px; font-weight: bold;
            text-align: center; letter-spacing: 0.3px;
            box-shadow: 0 0 16px rgba(239,68,68,0.25);
            visibility: hidden;
            animation: lt-warn-pulse 1.5s ease-in-out infinite;
        }
        .lt-confirm-warn.show { visibility: visible; }
        @keyframes lt-warn-pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.65; } }

        /* LOBBY */
        .lt-lobby { padding: 18px; }
        .lt-lobby-section { margin-bottom: 14px; }
        .lt-lobby-section:last-child { margin-bottom: 0; }
        .lt-lobby-section-title { font-size: 10px; color: #a78bfa; font-weight: bold; text-transform: uppercase; letter-spacing: 1.5px; margin-bottom: 10px; display: flex; align-items: center; gap: 6px; justify-content: space-between; }
        .lt-quick-toggle {
            cursor: pointer; padding: 4px 10px; border-radius: 999px; font-size: 10px;
            background: rgba(100, 116, 139, 0.18); color: #94a3b8;
            border: 1px solid rgba(100, 116, 139, 0.35); letter-spacing: 0.5px;
            transition: background .15s, color .15s, border-color .15s;
        }
        .lt-quick-toggle:hover { background: rgba(100, 116, 139, 0.30); color: #cbd5e1; }
        .lt-quick-toggle.on {
            background: rgba(250, 204, 21, 0.18); color: #fde68a;
            border-color: rgba(250, 204, 21, 0.55); box-shadow: 0 0 8px rgba(250, 204, 21, 0.25);
        }
        .lt-quick-toggle.on:hover { background: rgba(250, 204, 21, 0.30); }
        .lt-quick-toggle b { font-weight: 800; }
        .lt-start-form { display: flex; gap: 8px; align-items: center; padding: 12px; background: rgba(0,0,0,0.25); border-radius: 8px; border: 1px solid rgba(124,58,237,0.15); flex-wrap: wrap; }
        .lt-start-label { color: #94a3b8; font-size: 12px; }
        .lt-start-input { background: #0f172a; border: 1px solid #334155; color: #fff; padding: 8px 12px; border-radius: 6px; font-size: 13px; font-family: monospace; width: 150px; transition: border-color .15s; }
        .lt-start-input:focus { outline: none; border-color: #7c3aed; }
        .lt-start-input.error { border-color: #ef4444; box-shadow: 0 0 0 2px rgba(239,68,68,0.25); }
        .lt-start-prefill { padding: 8px 12px; background: rgba(124,58,237,0.15); color: #a78bfa; border: 1px solid #7c3aed; border-radius: 6px; font-size: 11px; font-weight: bold; cursor: pointer; transition: all .15s; }
        .lt-start-prefill:hover { background: rgba(124,58,237,0.3); color: #fff; }
        .lt-start-go { padding: 8px 22px; background: linear-gradient(180deg, #7c3aed, #6d28d9); color: #fff; border: 1px solid #8b5cf6; border-radius: 6px; font-size: 12px; font-weight: bold; cursor: pointer; letter-spacing: 0.8px; transition: all .15s; text-transform: uppercase; margin-left: auto; }
        .lt-start-go:hover { background: linear-gradient(180deg, #8b5cf6, #7c3aed); box-shadow: 0 0 16px rgba(124,58,237,0.4); }

        /* Collapsible game list */
        .lt-coll { background: rgba(0,0,0,0.25); border: 1px solid rgba(124,58,237,0.15); border-radius: 8px; overflow: hidden; }
        .lt-coll-head { padding: 10px 14px; cursor: pointer; display: flex; justify-content: space-between; align-items: center; background: rgba(124,58,237,0.08); transition: background .15s; border-bottom: 1px solid rgba(124,58,237,0.12); }
        .lt-coll.collapsed .lt-coll-head { border-bottom-color: transparent; }
        .lt-coll-head:hover { background: rgba(124,58,237,0.16); }
        .lt-coll-title { font-size: 12px; font-weight: bold; color: #e2e8f0; }
        .lt-coll-count { color: #a78bfa; margin-left: 4px; }
        .lt-coll-icon { color: #a78bfa; transition: transform .2s; font-size: 10px; }
        .lt-coll.collapsed .lt-coll-icon { transform: rotate(-90deg); }
        .lt-coll-body { max-height: 420px; overflow-y: auto; transition: max-height .25s ease-out; }
        .lt-coll.collapsed .lt-coll-body { max-height: 0; }
        .lt-game-row { display: grid; grid-template-columns: auto 1fr auto auto; gap: 10px; align-items: center; padding: 10px 14px; border-bottom: 1px solid rgba(255,255,255,0.04); transition: background .15s; }
        .lt-game-row:hover { background: rgba(124,58,237,0.06); }
        .lt-game-row.match { background: linear-gradient(90deg, rgba(34,197,94,0.15), transparent 70%) !important; box-shadow: 0 0 0 1px rgba(34,197,94,0.4) inset; }
        .lt-game-row.close { background: linear-gradient(90deg, rgba(251,191,36,0.1), transparent 70%) !important; }
        .lt-game-row-status { width: 8px; height: 8px; border-radius: 50%; background: #4ade80; flex-shrink: 0; }
        .lt-game-row-name { color: #cbd5e1; font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
        .lt-game-row-bet { color: #fbbf24; font-weight: bold; font-size: 13px; font-variant-numeric: tabular-nums; }
        .lt-game-row-bet.match-bet { color: #4ade80; }
        .lt-game-row-join { padding: 5px 12px; background: rgba(124,58,237,0.2); color: #a78bfa; border: 1px solid #7c3aed; border-radius: 4px; font-size: 10px; font-weight: bold; cursor: pointer; transition: all .15s; }
        .lt-game-row-join:hover { background: #7c3aed; color: #fff; box-shadow: 0 0 8px rgba(124,58,237,0.4); }
        .lt-coll-empty { padding: 18px; text-align: center; color: #64748b; font-size: 12px; font-style: italic; }
        .lt-game-row-join:disabled { opacity: 0.4; cursor: not-allowed; background: rgba(100,116,139,0.2); border-color: #475569; color: #64748b; }
        /* Reserved empty slot — same height as a real row, faded so it doesn't draw the eye */
        .lt-game-row.lt-game-row-empty {
            display: flex; align-items: center; justify-content: center;
            padding: 10px 14px; opacity: 0.35;
            background: transparent !important; box-shadow: none !important;
        }
        .lt-game-row.lt-game-row-empty:hover { background: transparent !important; }
        .lt-game-row-empty-line {
            color: #475569; font-size: 10px; font-style: italic; letter-spacing: 1px;
        }

        /* In-game alert banner (shown in lobby while a game of yours is waiting) */
        .lt-ingame-banner {
            display: flex; justify-content: space-between; align-items: center; gap: 12px;
            padding: 12px 16px; margin-bottom: 14px;
            background: rgba(239,68,68,0.12); border: 1px solid #ef4444; border-radius: 8px;
        }
        .lt-ingame-msg { color: #fca5a5; font-size: 12px; line-height: 1.4; }
        .lt-ingame-msg b { color: #fff; }
        .lt-ingame-btns { display: flex; gap: 8px; flex-shrink: 0; }
        .lt-ingame-return {
            background: linear-gradient(180deg, rgba(34,197,94,0.25), rgba(34,197,94,0.08));
            border: 1px solid #22c55e; color: #4ade80; padding: 8px 16px; border-radius: 6px;
            font-weight: bold; font-size: 12px; cursor: pointer; white-space: nowrap; transition: all .15s;
        }
        .lt-ingame-return:hover { background: rgba(34,197,94,0.4); color: #fff; box-shadow: 0 0 12px rgba(34,197,94,0.3); }
        .lt-ingame-leave {
            background: rgba(239,68,68,0.15); border: 1px solid #ef4444; color: #f87171;
            padding: 8px 14px; border-radius: 6px; font-weight: bold; font-size: 12px; cursor: pointer; white-space: nowrap; transition: all .15s;
        }
        .lt-ingame-leave:hover { background: rgba(239,68,68,0.3); color: #fff; }

        /* V8: Embedded Torn Game Window (legacy placeholder — hidden in v8.1) */
        #lt-game-frame {
            margin-bottom: 12px;
            border: 1px solid rgba(124, 58, 237, 0.3);
            border-radius: 12px;
            overflow: hidden;
            background: linear-gradient(180deg, rgba(124, 58, 237, 0.05), rgba(0, 0, 0, 0.3));
            box-shadow: 0 0 24px rgba(124, 58, 237, 0.08), 0 4px 16px rgba(0, 0, 0, 0.4);
            display: none;
        }
        #lt-game-frame.active { display: block; }
        .lt-frame-title {
            font-size: 10px; color: #a78bfa; font-weight: bold;
            text-transform: uppercase; letter-spacing: 1.8px;
            padding: 10px 16px;
            background: linear-gradient(90deg, rgba(124, 58, 237, 0.18), rgba(124, 58, 237, 0.04));
            border-bottom: 1px solid rgba(124, 58, 237, 0.2);
            display: flex; justify-content: space-between; align-items: center;
        }
        .lt-frame-title .lt-frame-meta { color: #64748b; letter-spacing: 0.6px; font-size: 9px; }
        #lt-game-frame-slot { position: relative; }
        /* Tone down Torn's app header inside our frame (we already have a title) */
        #lt-game-frame-slot div[class^="appHeaderWrapper"] {
            background: rgba(0,0,0,0.15) !important;
            border-bottom: 1px solid rgba(124, 58, 237, 0.1);
        }

        /* Make the whole dashboard breathe wider on big screens */
        @media (min-width: 1200px) {
            #lt-dashboard { max-width: 100%; }
        }

        /* Smart Game-List highlight (V8) */
        .lt-rr-game-match {
            background: linear-gradient(90deg, rgba(34, 197, 94, 0.18), transparent 60%) !important;
            box-shadow: 0 0 0 2px rgba(34, 197, 94, 0.4) inset, 0 0 12px rgba(34, 197, 94, 0.2);
            position: relative;
        }
        .lt-rr-game-match::before {
            content: '🎯 MATCH'; position: absolute; top: 50%; right: 8px;
            transform: translateY(-50%); font-size: 10px; font-weight: bold;
            color: #4ade80; background: rgba(0,0,0,0.7); padding: 3px 8px;
            border-radius: 10px; border: 1px solid #22c55e; pointer-events: none;
            z-index: 5;
        }
        .lt-rr-game-close {
            background: linear-gradient(90deg, rgba(251, 191, 36, 0.12), transparent 60%) !important;
            box-shadow: 0 0 0 1px rgba(251, 191, 36, 0.3) inset;
        }

        /* ──────────────────────────────────────────────────────────────────
           IN-GAME ANIMATIONS — make a round feel alive.
           Timer urgency, pot golden flash, shot-button breathing, dashboard shake
           on bang, player card entrance. */

        /* Timer: pulse orange under 10s, faster pulsing red under 5s */
        .lt-igs-timer.urgent {
            color: #f97316 !important;
            text-shadow: 0 0 18px rgba(249,115,22,0.65) !important;
            animation: lt-timer-pulse 1.0s ease-in-out infinite;
        }
        .lt-igs-timer.critical {
            color: #ef4444 !important;
            text-shadow: 0 0 22px rgba(239,68,68,0.85), 0 0 40px rgba(239,68,68,0.45) !important;
            animation: lt-timer-pulse 0.55s ease-in-out infinite;
        }
        @keyframes lt-timer-pulse {
            0%,100% { transform: scale(1); }
            50%     { transform: scale(1.10); }
        }

        /* Pot: golden flash + scale on every increase */
        .lt-igs-pot.lt-pot-flash { animation: lt-pot-flash 0.65s ease-out; }
        @keyframes lt-pot-flash {
            0%   { transform: scale(1); color: #4ade80; text-shadow: 0 0 16px rgba(74,222,128,0.3); }
            30%  { transform: scale(1.18); color: #fbbf24; text-shadow: 0 0 30px rgba(251,191,36,0.95), 0 0 60px rgba(251,191,36,0.5); }
            100% { transform: scale(1); color: #4ade80; text-shadow: 0 0 16px rgba(74,222,128,0.3); }
        }

        /* Shot buttons: gentle breathing glow so your eye is drawn when it's your turn */
        .lt-shot-btn.primary:not(.armed) { animation: lt-breath-red 1.8s ease-in-out infinite; }
        .lt-shot-btn.mult:not(.armed)    { animation: lt-breath-gold 2.2s ease-in-out infinite; }
        @keyframes lt-breath-red {
            0%,100% { box-shadow: 0 0 0 0 rgba(239,68,68,0); }
            50%     { box-shadow: 0 0 22px rgba(239,68,68,0.55); }
        }
        @keyframes lt-breath-gold {
            0%,100% { box-shadow: 0 0 0 0 rgba(251,191,36,0); }
            50%     { box-shadow: 0 0 22px rgba(251,191,36,0.45); }
        }

        /* Dashboard shake on bang — sharp horizontal jolt, then back */
        #lt-dashboard.lt-shake { animation: lt-shake 0.42s cubic-bezier(.36,.07,.19,.97); }
        @keyframes lt-shake {
            10%, 90% { transform: translate3d(-2px, 0, 0); }
            20%, 80% { transform: translate3d( 4px, 0, 0); }
            30%, 50%, 70% { transform: translate3d(-6px, 0, 0); }
            40%, 60%      { transform: translate3d( 6px, 0, 0); }
        }

        /* Player cards slide in when a round starts (staggered) */
        .lt-igs-players .lt-player { animation: lt-player-enter 0.45s cubic-bezier(.34,1.56,.64,1) both; }
        .lt-igs-players .lt-player:nth-child(1) { animation-delay: 0.00s; }
        .lt-igs-players .lt-player:nth-child(3) { animation-delay: 0.10s; }
        @keyframes lt-player-enter {
            0%   { opacity: 0; transform: translateY(18px) scale(0.94); }
            100% { opacity: 1; transform: translateY(0)    scale(1.00); }
        }
        /* The VS divider mirrors the entrance */
        .lt-igs-players .lt-vs { animation: lt-vs-enter 0.45s cubic-bezier(.34,1.56,.64,1) both; animation-delay: 0.05s; }
        @keyframes lt-vs-enter {
            0%   { opacity: 0; transform: scale(0.4) rotate(-30deg); }
            100% { opacity: 1; transform: scale(1)   rotate(0deg); }
        }

        /* Death flash: card desaturates + flickers when the death-cross appears */
        .lt-player.dead { animation: lt-death-flicker 0.6s ease-out forwards; }
        @keyframes lt-death-flicker {
            0%   { filter: none; }
            20%  { filter: brightness(2.4) saturate(0.5); }
            40%  { filter: brightness(0.4); }
            60%  { filter: brightness(1.7); }
            100% { filter: grayscale(0.85) brightness(0.7); }
        }
        .lt-player-cross { animation: lt-cross-draw 0.4s cubic-bezier(.5,1.8,.5,1) both; }
        @keyframes lt-cross-draw {
            0%   { opacity: 0; transform: scale(2.5) rotate(-25deg); }
            100% { opacity: 1; transform: scale(1)   rotate(0deg); }
        }

        /* ──────────────────────────────────────────────────────────────────
           RISK RING — circular progress around the bet display.
           Fill arc grows + tweens green→yellow→red with current loss vs stop. */
        .lt-bet-ring-wrap {
            position: relative; width: 220px; height: 220px;
            display: flex; align-items: center; justify-content: center;
        }
        .lt-bet-ring {
            position: absolute; inset: 0; width: 100%; height: 100%;
            pointer-events: none;
        }
        .lt-bet-ring-fill {
            transition: stroke-dashoffset .55s cubic-bezier(.22,.61,.36,1),
                        stroke .35s ease,
                        filter .35s ease;
        }
        .lt-bet-ring-fill.danger {
            animation: lt-ring-pulse 1.2s ease-in-out infinite;
        }
        @keyframes lt-ring-pulse {
            0%,100% { filter: drop-shadow(0 0 6px rgba(239,68,68,0.5)); }
            50%     { filter: drop-shadow(0 0 18px rgba(239,68,68,0.95)) brightness(1.2); }
        }
        .lt-bet-ring-wrap #lt-bet-display { position: relative; z-index: 2; }
        .lt-bet-ring-val {
            position: absolute; bottom: 6px; left: 50%; transform: translateX(-50%);
            font-size: 10px; font-weight: 700; letter-spacing: 0.15em;
            font-family: 'JetBrains Mono', 'Consolas', monospace;
            color: #94a3b8; text-transform: uppercase;
            transition: color .25s ease;
            text-shadow: 0 1px 2px rgba(0,0,0,0.6);
            pointer-events: none;
        }

        /* ──────────────────────────────────────────────────────────────────
           GLASSMORPHISM REFRESH — translucent surfaces + blurred backdrops on
           stat cards, hero, lobby, and the various inset panels. Layered
           shadows give depth without bloating the markup. */
        .lt-stat-card {
            background: linear-gradient(180deg, rgba(255,255,255,0.05), rgba(255,255,255,0.02)) !important;
            border: 1px solid rgba(255,255,255,0.07) !important;
            backdrop-filter: blur(10px) saturate(140%);
            -webkit-backdrop-filter: blur(10px) saturate(140%);
            box-shadow:
                0 1px 0 rgba(255,255,255,0.05) inset,
                0 8px 24px -8px rgba(0,0,0,0.55);
            transition: transform .2s ease, box-shadow .25s ease, border-color .25s ease;
        }
        .lt-stat-card:hover {
            transform: translateY(-1px);
            border-color: rgba(167,139,250,0.35) !important;
            box-shadow:
                0 1px 0 rgba(255,255,255,0.08) inset,
                0 12px 28px -8px rgba(124,58,237,0.30);
        }
        /* Context-coloured halos behind specific stats */
        .lt-stat-card:has(#lt-prof-val)    { box-shadow: 0 1px 0 rgba(255,255,255,0.05) inset, 0 8px 24px -10px rgba(74,222,128,0.30); }
        .lt-stat-card:has(#lt-streak-val)  { box-shadow: 0 1px 0 rgba(255,255,255,0.05) inset, 0 8px 24px -10px rgba(251,146,60,0.25); }
        .lt-stat-card:has(#lt-winrate-val) { box-shadow: 0 1px 0 rgba(255,255,255,0.05) inset, 0 8px 24px -10px rgba(96,165,250,0.25); }

        .lt-hero-center {
            backdrop-filter: blur(8px) saturate(130%);
            -webkit-backdrop-filter: blur(8px) saturate(130%);
            box-shadow:
                0 1px 0 rgba(255,255,255,0.06) inset,
                0 16px 36px -10px rgba(124,58,237,0.25);
        }
        .lt-lobby-section, .lt-profit-chart-wrap, .lt-action-bar {
            backdrop-filter: blur(6px) saturate(120%);
            -webkit-backdrop-filter: blur(6px) saturate(120%);
        }

        /* ──────────────────────────────────────────────────────────────────
           TYPOGRAPHY REFRESH — applied last so it overrides earlier rules.
           • Inter for UI text, JetBrains Mono for numeric / monetary values.
           • Tightened tracking on hero numbers, widened on uppercase labels.
           • Tabular figures everywhere a number can change in place. */

        /* All number-style values get sharp tabular Mono → no width jitter on tween */
        #lt-bet-display,
        .lt-stat-card .sc-value,
        #lt-rem-val, #lt-prof-val, #lt-rounds-val, #lt-winrate-val,
        #lt-header-profit,
        .lt-igs-pot, #lt-igs-pot,
        #lt-igs-timer,
        .lt-game-row-bet,
        .lt-input-preview,
        .lt-seq-card,
        .lt-history-item b,
        .lt-chart-axis-y text, .lt-chart-axis-x text,
        .lt-streak-num {
            font-family: 'JetBrains Mono', ui-monospace, 'Consolas', 'Menlo', monospace;
            font-variant-numeric: tabular-nums;
            font-feature-settings: 'tnum', 'zero', 'ss01';
        }

        /* Big hero numbers: tighter tracking + slightly heavier weight */
        #lt-bet-display { letter-spacing: -0.02em; font-weight: 800; }
        .lt-igs-pot, #lt-igs-pot { letter-spacing: -0.01em; font-weight: 800; }

        /* Section / hero labels: cleaner uppercase rhythm */
        .lt-section-title,
        .lt-lobby-section-title,
        .lt-hist-side-head,
        .lt-coll-head,
        .lt-stat-card .sc-label,
        .lt-bet-label-v8,
        .lt-status-bar {
            font-family: 'Inter', 'Segoe UI', system-ui, sans-serif;
            font-weight: 700; letter-spacing: 0.10em;
        }
        .lt-stat-card .sc-label { font-size: 9px; letter-spacing: 0.14em; color: #7d8aa3; }
        .lt-bet-label-v8 { letter-spacing: 0.20em; font-weight: 600; }

        /* Header (LabTrack logo / version) */
        #lt-header { font-family: 'Inter', system-ui, sans-serif; letter-spacing: 0.08em; font-weight: 700; }
        #lt-header-profit { font-family: 'JetBrains Mono', 'Consolas', monospace; letter-spacing: 0; font-weight: 700; }

        /* Buttons: cleaner button weight */
        .lt-btn, .lt-btn-mega, .lt-shot-btn, .lt-start-go, .lt-start-prefill,
        .lt-game-row-join, .lt-stop-warning-btn, .lt-quick-toggle, .lt-suggest-btn {
            font-family: 'Inter', 'Segoe UI', sans-serif; font-weight: 700;
            letter-spacing: 0.04em;
        }
        .lt-btn-mega { letter-spacing: 0.12em; }

        /* Inputs use Mono so 0/1/. don't visually jitter */
        .lt-input, .lt-start-input {
            font-family: 'JetBrains Mono', 'Consolas', monospace;
            font-variant-numeric: tabular-nums;
        }

    `);

    // --- CORE UTILS ---
    const Utils = {
        formatNumber: (n) => {
            if (!n && n !== 0) return "0";
            const sign = n < 0 ? '-' : '';
            const abs = Math.abs(n);
            if (abs >= 1_000_000_000) return `${sign}${(abs / 1_000_000_000).toFixed(2).replace(/\.00$/, '')}b`;
            if (abs >= 1_000_000) return `${sign}${(abs / 1_000_000).toFixed(2).replace(/\.00$/, '')}m`;
            if (abs >= 1_000) return `${sign}${(abs / 1_000).toFixed(1).replace(/\.0$/, '')}k`;
            return `${sign}${Math.round(abs * 10) / 10}`;
        },
        generateSequence: (target, parts, uniform, integers) => {
            if (parts <= 0) {
                Logger.warn('Utils', `Invalid parts: ${parts}`);
                return [];
            }
            const minVal = integers ? CONFIG.MIN_INT_BET : CONFIG.MIN_BET;
            const step = integers ? 1 : 0.1;
            const r = (n) => integers ? Math.round(n) : Math.round(n * 10) / 10; // kill float noise

            // 1) Build ideal (float) values that sum to `target`.
            let ideal;
            if (uniform) {
                ideal = new Array(parts).fill(target / parts);
            } else {
                // Gentle ascending gradient (smallest → largest) with mild jitter —
                // a clean Labouchere line that still varies between generations.
                const weights = [];
                for (let i = 0; i < parts; i++) {
                    const ramp = parts === 1 ? 1 : 0.55 + (i / (parts - 1)) * 0.9; // 0.55 → 1.45
                    weights.push(ramp * (0.85 + Math.random() * 0.30));            // ±15% jitter
                }
                weights.sort((a, b) => a - b);
                const wsum = weights.reduce((a, b) => a + b, 0) || 1;
                ideal = weights.map(w => (w / wsum) * target);
            }

            // 2) Largest-remainder rounding to `step` → sum hits `target` EXACTLY,
            //    every element stays ≥ minVal (0% deviation by construction).
            const vals = ideal.map(v => Math.max(minVal, r(Math.floor(v / step) * step)));
            let used = vals.reduce((a, b) => a + b, 0);
            let leftover = Math.round((target - used) / step); // steps left to assign

            if (leftover > 0) {
                // Hand remaining steps to the parts with the largest fractional remainder.
                const order = ideal
                    .map((v, i) => ({ i, frac: (v / step) - Math.floor(v / step) }))
                    .sort((a, b) => b.frac - a.frac);
                for (let k = 0; leftover > 0; k++, leftover--) {
                    const idx = order[k % parts].i;
                    vals[idx] = r(vals[idx] + step);
                }
            } else if (leftover < 0) {
                // Min-clamp overshot the target — trim from the largest parts (never below min).
                let over = -leftover, guard = 0;
                const order = vals.map((v, i) => ({ i, v })).sort((a, b) => b.v - a.v);
                for (let k = 0; over > 0 && guard < parts * 5000; k++, guard++) {
                    const idx = order[k % parts].i;
                    if (vals[idx] - step >= minVal - 1e-9) { vals[idx] = r(vals[idx] - step); over--; }
                }
            }
            return vals.map(v => r(Math.max(minVal, v)));
        },

        makeId: () => Math.random().toString(36).substr(2, 9) + Date.now().toString(36),

        showToast: (msg) => {
            let toast = document.getElementById('lt-toast');
            if (!toast) {
                toast = document.createElement('div');
                toast.id = 'lt-toast';
                toast.className = 'lt-toast';
                document.getElementById('lt-dashboard')?.appendChild(toast);
            }
            if (toast) {
                toast.innerText = msg;
                toast.classList.add('show');
                setTimeout(() => toast.classList.remove('show'), CONFIG.TOAST_MS);
            }
        }
    };

    const auditLog = new AuditLog();
    window.ltAuditLog = auditLog;


    // --- GAME ENGINE ---
    class GameEngine {
        constructor() {
            this.state = this.loadState() || this.getDefaultState();
            this.listeners = [];
            this.pendingBet = null;
            this.history = [];
            this.lastActionTime = 0; // V6.31: Hard lock for race conditions

            if (this.state.autoDetect === undefined) this.state.autoDetect = true;
            if (this.state.customBet === undefined) this.state.customBet = null;
            if (this.state.roundCount === undefined) this.state.roundCount = 0;
            if (this.state.roundHistory === undefined) this.state.roundHistory = [];
            if (this.state.initialSequence === undefined) this.state.initialSequence = [];
            if (this.state.multiplier === undefined) this.state.multiplier = 1; // Base internal multiplier

            window.addEventListener('storage', (e) => {
                if (e.key === 'lt_standalone_save') {
                    this.state = this.loadState() || this.getDefaultState();
                    this.notify();
                }
            });
        }
        getDefaultState() {
            // First-time players start with a ready-to-go sequence: 10M target, 10 parts.
            const defSeq = Utils.generateSequence(10000000, 10, true, false)
                .map(v => ({ id: Utils.makeId(), value: v }));
            return {
                sequence: JSON.parse(JSON.stringify(defSeq)),
                multiplier: 1,
                totalProfit: 0,
                autoDetect: true,
                customBet: null,
                roundCount: 0,
                roundHistory: [],
                initialSequence: defSeq
            };
        }

        loadState() {
            try {
                const data = localStorage.getItem(CONFIG.STORAGE_SAVE);
                if (!data) return null;
                const state = JSON.parse(data);
                if (!Validator.isStateValid(state)) {
                    Logger.warn('GameEngine', 'Invalid state loaded, using default');
                    return null;
                }
                return state;
            } catch(e) {
                Logger.error('GameEngine', `Load failed: ${e.message}`);
                return null;
            }
        }

        saveState() {
            try {
                const data = JSON.stringify(this.state);
                if (data.length > CONFIG.MAX_STORAGE_MB * 1000000) {
                    Logger.warn('GameEngine', 'State too large, compacting history');
                    this.compactHistory();
                }
                localStorage.setItem(CONFIG.STORAGE_SAVE, data);
                this.notify();
            } catch(e) {
                if (e.name === 'QuotaExceededError') {
                    Logger.error('GameEngine', 'Storage quota exceeded');
                    Utils.showToast("Storage full - clearing old data");
                    this.compactHistory();
                    try {
                        localStorage.setItem(CONFIG.STORAGE_SAVE, JSON.stringify(this.state));
                        this.notify();
                    } catch(retryErr) {
                        Logger.error('GameEngine', 'Retry save failed');
                    }
                } else {
                    Logger.error('GameEngine', `Save failed: ${e.message}`);
                }
            }
        }

        compactHistory() {
            if (this.state.roundHistory.length > 20) {
                this.state.roundHistory = this.state.roundHistory.slice(0, 20);
            }
            if (this.history.length > 10) {
                this.history = this.history.slice(-10);
            }
        }
        subscribe(fn) { this.listeners.push(fn); }
        notify() { this.listeners.forEach(fn => fn(this.state)); }

        resetData() {
            localStorage.removeItem('lt_standalone_save');
            localStorage.removeItem('lt_settings');
            this.state = this.getDefaultState();
            this.notify();
            Utils.showToast("Factory Reset Complete");
        }

        pushHistory() {
            try {
                this.history.push(JSON.stringify(this.state));
                if (this.history.length > CONFIG.MAX_UNDO_STACK) {
                    this.history.shift();
                }
            } catch(e) {
                Logger.error('GameEngine', `Push history failed: ${e.message}`);
            }
        }

        undo() {
            if (!this.history.length) {
                Utils.showToast("Nothing to undo");
                return;
            }
            try {
                this.state = JSON.parse(this.history.pop());
                this.saveState();
                Utils.showToast("Undo: Last Round");
                Logger.info('GameEngine', 'Undo successful');
            } catch(e) {
                Logger.error('GameEngine', `Undo failed: ${e.message}`);
                Utils.showToast("Undo failed");
            }
        }

        toggleAutoDetect() { this.state.autoDetect = !this.state.autoDetect; this.saveState(); }
        setCustomBet(amount) { this.state.customBet = amount; this.saveState(); }
        clearCustomBet() { this.state.customBet = null; this.saveState(); }
        resetBet() {
            this.state.customBet = null;
            this.pendingBet = null;
            this.saveState();
            Logger.info('GameEngine', 'Bet reset - customBet and pendingBet cleared');
        }

        getEffectiveBet() {
            if (this.state.customBet !== null && this.state.customBet > 0) return this.state.customBet;

            // Calculate base bet from sequence
            const baseBet = this.getNextBaseBet();

            // Apply selected multiplier (default 1 if not set)
            let mult = 1;
            try {
                const s = JSON.parse(localStorage.getItem('lt_settings'));
                if(s && s.multVal) mult = parseFloat(s.multVal);
            } catch(e){}

            return Math.round(baseBet * mult);
        }

        setPendingBet(amount) {
            if (!Validator.isBetValid(amount)) {
                Logger.warn('GameEngine', `Invalid bet: ${amount}`);
                return;
            }
            Logger.info('GameEngine', `Bet captured: ${amount}`);
            this.pendingBet = amount;
            auditLog.record('NET', 'BET', `Pending: ${amount}`);
        }

        generateNew(target, parts, uniform, integers) {
            this.pushHistory();
            const seq = Utils.generateSequence(target, parts, uniform, integers);
            this.state.initialSequence = seq.map(v => ({ id: Utils.makeId(), value: v }));
            this.state.sequence = JSON.parse(JSON.stringify(this.state.initialSequence));
            this.state.totalProfit = 0; this.state.roundCount = 0; this.state.roundHistory = []; this.state.customBet = null;
            this.saveState();
            Utils.showToast("Sequence Generated");
        }

        restartGame() {
            this.pushHistory();
            if(this.state.initialSequence && this.state.initialSequence.length > 0) {
                this.state.sequence = JSON.parse(JSON.stringify(this.state.initialSequence));
                this.state.sequence.forEach(i => i.id = Utils.makeId());
                this.state.totalProfit = 0; this.state.roundCount = 0; this.state.roundHistory = []; this.state.customBet = null;
                this.saveState();
                Utils.showToast("Game Restarted");
            } else { Utils.showToast("No previous sequence"); }
        }

        // V6.29: Helper for raw sequence value
        getNextBaseBet() {
            const seq = this.state.sequence;
            if(seq.length===0) return 0;
            if(seq.length===1) return seq[0].value;
            return seq[0].value + seq[seq.length-1].value;
        }

        // V7.09: Bulletproof deduplication using set-first-check-after
        processWin() {
            const now = Date.now();

            // V7.09 FIX: SET marker FIRST, then check previous value
            // This is the ONLY way to prevent TOCTOU
            const prevMarker = this._winProcessingTime;
            this._winProcessingTime = now;  // SET IMMEDIATELY

            // Race-protection debounce. Only enforced while Auto-Detect is ON —
            // there the risk is auto + manual firing for the same round. In pure
            // manual mode we use a tiny 300 ms button-mash guard instead so you can
            // simulate/log rounds rapidly.
            const guardMs = this.state.autoDetect ? 3000 : 300;
            if (prevMarker && (now - prevMarker) < guardMs) {
                Logger.warn('GameEngine', `Win SKIPPED - duplicate call within ${guardMs}ms`);
                this.pendingBet = null;
                return;
            }

            const bet = this.pendingBet || this.getEffectiveBet();

            // Also check history as backup (same auto-only logic).
            if (this.state.autoDetect && this.state.roundHistory.length > 0) {
                const last = this.state.roundHistory[0];
                if (last.result === 'WIN' && (now - last.time) < 3000) {
                    Logger.warn('GameEngine', 'Win SKIPPED - already in history within 3s');
                    this.pendingBet = null;
                    return;
                }
            }

            this.pushHistory();
            if (this.state.sequence.length === 0) {
                Logger.warn('GameEngine', 'Win processed with empty sequence');
                return;
            }

            let mult = 1;
            try {
                const settings = JSON.parse(localStorage.getItem(CONFIG.STORAGE_SETTINGS));
                mult = parseFloat(settings?.multVal) || 1;
            } catch(e) {
                Logger.debug('GameEngine', 'Using default multiplier');
            }

            const baseProfit = bet / mult;

            this.state.roundHistory.unshift({
                result: 'WIN',
                bet,
                profit: bet,
                time: now  // Use the same 'now' timestamp
            });

            // Capture removed values BEFORE modifying sequence
            const seq = this.state.sequence;
            const seqBefore = seq.map(i => i.value);
            const removedFirst = seq.length > 0 ? seq[0].value : null;
            const removedLast = seq.length > 1 ? seq[seq.length - 1].value : null;

            if (this.state.sequence.length > 1) {
                this.state.sequence.shift();
                this.state.sequence.pop();
            } else {
                this.state.sequence = [];
            }

            this.state.totalProfit += baseProfit;

            // V7.26: Custom bet correction — keep sequence sum invariant intact
            // Invariant: sequence_sum + totalProfit = initial_target
            // If customBet ≠ first+last, the sequence sum is now off by the difference.
            // We correct it by appending a remainder element or trimming from the end.
            if (this.state.customBet !== null) {
                const standardBet = (removedFirst ?? 0) + (removedLast ?? 0);
                const remainder = Math.round((standardBet - baseProfit) * 1e6) / 1e6;
                if (remainder > 0.0001) {
                    // Custom bet was smaller → sequence still "owes" this amount
                    this.state.sequence.push({ id: Utils.makeId(), value: remainder });
                    Logger.info('CustomBet', `Sequence corrected: remainder +${remainder} appended to end`);
                } else if (remainder < -0.0001) {
                    // Custom bet was larger → we over-won; trim that surplus from end
                    let toRemove = Math.abs(remainder);
                    while (toRemove > 0.0001 && this.state.sequence.length > 0) {
                        const tail = this.state.sequence[this.state.sequence.length - 1];
                        if (tail.value <= toRemove + 0.0001) {
                            toRemove = Math.round((toRemove - tail.value) * 1e6) / 1e6;
                            this.state.sequence.pop();
                        } else {
                            tail.value = Math.round((tail.value - toRemove) * 1e6) / 1e6;
                            toRemove = 0;
                        }
                    }
                    Logger.info('CustomBet', `Sequence corrected: -${Math.abs(remainder)} trimmed from end`);
                }
            }

            this.state.roundCount++;
            this.pendingBet = null;

            // V7.03: Clear customBet after use (one-time use only)
            if (this.state.customBet) {
                Logger.info('CustomBet', `Clearing after use: ${Utils.formatNumber(this.state.customBet)}`);
                this.state.customBet = null;
            }

            this.saveState();
            Logger.info('GameEngine', `Win processed: ${Utils.formatNumber(bet)}`);
            const removed = removedLast !== null ? [removedFirst, removedLast] : [removedFirst];
            const seqAfterWin = this.state.sequence.map(i => i.value);
            auditLog.record('EVENT', 'ENGINE/WIN', `WIN — removed [${removed.join(', ')}] | seq: [${seqBefore.join(',')}] → [${seqAfterWin.join(',')}]`, {
                bet, removed, seqBefore, seqAfter: seqAfterWin,
                seqRemaining: this.state.sequence.length,
                totalProfit: Math.round(this.state.totalProfit * mult * 100) / 100,
                round: this.state.roundCount
            });
        }

        processLoss(amount) {
            const now = Date.now();

            // V7.09 FIX: SET marker FIRST, then check previous value
            const prevMarker = this._lossProcessingTime;
            this._lossProcessingTime = now;  // SET IMMEDIATELY

            // Race-protection debounce (auto-only). 300 ms button-mash guard in manual.
            const guardMs = this.state.autoDetect ? 3000 : 300;
            if (prevMarker && (now - prevMarker) < guardMs) {
                Logger.warn('GameEngine', `Loss SKIPPED - duplicate call within ${guardMs}ms`);
                this.pendingBet = null;
                return;
            }

            const bet = amount || this.pendingBet || this.getEffectiveBet();

            // History-backup check only while Auto is ON.
            if (this.state.autoDetect && this.state.roundHistory.length > 0) {
                const last = this.state.roundHistory[0];
                if (last.result === 'LOSS' && (now - last.time) < 3000) {
                    Logger.warn('GameEngine', 'Loss SKIPPED - already in history within 3s');
                    this.pendingBet = null;
                    return;
                }
            }

            this.pushHistory();

            let mult = 1;
            try {
                const settings = JSON.parse(localStorage.getItem(CONFIG.STORAGE_SETTINGS));
                mult = parseFloat(settings?.multVal) || 1;
            } catch(e) {
                Logger.debug('GameEngine', 'Using default multiplier');
            }

            const baseLoss = bet / mult;
            const seqBeforeLoss = this.state.sequence.map(i => i.value);

            this.state.roundHistory.unshift({
                result: 'LOSS',
                bet,
                profit: -bet,
                time: now
            });

            this.state.sequence.push({
                id: Utils.makeId(),
                value: baseLoss
            });

            this.state.totalProfit -= baseLoss;
            this.state.roundCount++;
            this.pendingBet = null;

            // V7.03: Clear customBet after use (one-time use only)
            if (this.state.customBet) {
                Logger.info('CustomBet', `Clearing after use: ${Utils.formatNumber(this.state.customBet)}`);
                this.state.customBet = null;
            }

            this.saveState();
            Logger.info('GameEngine', `Loss processed: ${Utils.formatNumber(bet)}`);
            const seqAfterLoss = this.state.sequence.map(i => i.value);
            auditLog.record('EVENT', 'ENGINE/LOSS', `LOSS — added ${baseLoss} | seq: [${seqBeforeLoss.join(',')}] → [${seqAfterLoss.join(',')}]`, {
                bet, added: baseLoss, mult, seqBefore: seqBeforeLoss, seqAfter: seqAfterLoss,
                seqLength: this.state.sequence.length,
                totalProfit: Math.round(this.state.totalProfit * mult * 100) / 100,
                round: this.state.roundCount
            });
        }
        shuffleSequence() { this.pushHistory(); this.state.sequence.sort(() => Math.random() - 0.5); this.saveState(); Utils.showToast("Shuffled"); }

        loadFromValues(values) {
            this.pushHistory();
            this.state.sequence = values.map(v => ({ id: Utils.makeId(), value: v }));
            this.state.initialSequence = JSON.parse(JSON.stringify(this.state.sequence));
            this.state.totalProfit = 0;
            this.state.roundCount = 0;
            this.state.roundHistory = [];
            this.state.customBet = null;
            this.saveState();
            auditLog.record('EVENT', 'SEQ/PASTE', `Sequence loaded from clipboard`, { values, count: values.length });
        }

        // DRAG & DROP LOGIC
        reorderSequence(fromIdx, toIdx) {
            if(fromIdx === toIdx) return;
            let adjust = 0;
            if (fromIdx < toIdx) adjust = -1;
            const finalTo = toIdx + adjust;
            if (finalTo < 0 || finalTo > this.state.sequence.length) return;
            this.pushHistory();
            const item = this.state.sequence.splice(fromIdx, 1)[0];
            this.state.sequence.splice(finalTo, 0, item);
            this.saveState();
            Utils.showToast("Reordered");
        }

        mergeList(indices) {
            this.pushHistory(); if(indices.length<2) return;
            indices.sort((a,b)=>a-b); let sum=0;
            indices.forEach(i=>sum+=this.state.sequence[i].value);
            const pos = indices[0];
            for(let i=indices.length-1; i>=0; i--) this.state.sequence.splice(indices[i], 1);
            this.state.sequence.splice(pos, 0, { id:Utils.makeId(), value:sum });
            this.saveState();
            Utils.showToast("Merged");
        }
        splitItem(idx, val) {
            this.pushHistory();
            const item = this.state.sequence[idx]; if(!item) return;
            const rem = item.value - val; if(rem<=0) return;
            this.state.sequence.splice(idx, 1, {id:Utils.makeId(), value:val}, {id:Utils.makeId(), value:rem});
            this.saveState();
            Utils.showToast("Split");
        }
        removeItem(idx) {
            if (idx < 0 || idx >= this.state.sequence.length) return;
            this.pushHistory();
            this.state.sequence.splice(idx, 1);
            this.saveState();
            Utils.showToast("Number removed");
        }
        addItem(value) {
            if (!(value > 0)) return;
            this.pushHistory();
            this.state.sequence.push({ id: Utils.makeId(), value });
            this.saveState();
            Utils.showToast("Number added");
        }
    }
    const engine = new GameEngine();

    // --- UI MANAGER ---
    class OverlayUI {
        constructor() {
            this.mergeMode = false; this.selectedForMerge = [];
            this.dragSrcIndex = null;
            let s; try{ s=JSON.parse(localStorage.getItem('lt_settings')); }catch(e){}
            // V6.29: Added multKey ('1x','k','m','b') and multVal (numeric)
            this.savedSettings = s || { mode:'bankroll', bankroll:'400000000', risk:'2.5', target:'', parts:'10', integers:false, uniform:false, multKey:'1x', multVal:1 };
            if(!this.savedSettings.multKey) { this.savedSettings.multKey='1x'; this.savedSettings.multVal=1; }
            this.genMode = this.savedSettings.mode || 'bankroll';
        }
        saveSettingsFromUI() {
            ['bankroll','risk','target','parts'].forEach(k => { const el=document.getElementById('lt-inp-'+k); if(el) this.savedSettings[k]=el.value; });
            ['integers','uniform'].forEach(k => { const el=document.getElementById('lt-chk-'+k); if(el) this.savedSettings[k]=el.checked; });
            this.savedSettings.mode = this.genMode;
            localStorage.setItem('lt_settings', JSON.stringify(this.savedSettings));
            this.updateInputPreviews();
        }
        setMultiplier(key) {
            this.savedSettings.multKey = key;
            if(key === 'k') this.savedSettings.multVal = 1000;
            else if(key === 'm') this.savedSettings.multVal = 1000000;
            else if(key === 'b') this.savedSettings.multVal = 1000000000;
            else this.savedSettings.multVal = 1;

            // Update UI buttons
            ['1x','k','m','b'].forEach(k => {
                const btn = document.getElementById('lt-btn-mult-'+k);
                if(btn) {
                    if(k===key) btn.classList.add('active');
                    else btn.classList.remove('active');
                }
            });
            this.saveSettingsFromUI();
            this.update(); // Refresh display numbers
        }

        // Pick the largest unit (B/M/K/1x) where the displayed target is still
        // ≥ 10 — i.e. a two-digit-or-more number in that unit. So a 20M target
        // stays in M, anything under 10M drops to K, etc. The generator hits the
        // target exactly (largest-remainder), so the unit never drives deviation.
        _pickAutoMult(realTarget) {
            const units = [1e9, 1e6, 1e3, 1];
            for (const m of units) { if (realTarget / m >= 10) return m; }
            return 1;
        }
        _multKeyFor(v) { return v === 1e9 ? 'b' : v === 1e6 ? 'm' : v === 1e3 ? 'k' : '1x'; }
        _fmtUnit(v) { return (Math.round(v * 10) / 10).toString(); }

        // Auto-fit the multiplier to the number the user actually types: the
        // BANKROLL in bankroll mode, the TARGET in target mode. The unit follows
        // that field (kept ≥ 10 in its unit), not the much smaller derived target —
        // so a 400M bankroll stays in M even at low risk. Rescales the field so the
        // real money amount is unchanged.
        autoFitMultiplier() {
            const curMult = this.savedSettings.multVal || 1;
            const fieldId = this.genMode === 'bankroll' ? 'lt-inp-bankroll' : 'lt-inp-target';
            const el = document.getElementById(fieldId);
            const realValue = (parseFloat(el?.value) || 0) * curMult;
            if (realValue <= 0) return;
            const best = this._pickAutoMult(realValue);
            if (best === curMult) return;
            if (el) el.value = this._fmtUnit(realValue / best); // keep real money identical
            this.setMultiplier(this._multKeyFor(best)); // updates buttons, saves, refreshes
        }

        // Suggest a parts count from Risk %. Higher risk = more aggressive = fewer
        // parts (bigger bets); lower risk = more parts (smaller, flatter bets).
        // parts ≈ 25/risk (Risk 2.5% → 10, Risk 5% → 5, Risk 1% → 25).
        // Clamped to a sane 4–40 range. (Target mode has no risk → defaults to 10.)
        suggestParts() {
            let parts = 10;
            if (this.genMode === 'bankroll') {
                const risk = parseFloat(document.getElementById('lt-inp-risk')?.value) || 0;
                if (risk > 0) parts = Math.round(25 / risk);
            }
            parts = Math.max(4, Math.min(40, parts));
            const el = document.getElementById('lt-inp-parts');
            if (el) el.value = parts;
            this.saveSettingsFromUI();
            this.autoFitMultiplier();
            this.update();
            Utils.showToast(`Suggested ${parts} parts`);
        }
        updateInputPreviews() {
            const mult = this.savedSettings.multVal || 1;
            const suffix = this.savedSettings.multKey !== '1x' ? this.savedSettings.multKey.toUpperCase() : '';

            const bindPreview = (id) => {
                const el = document.getElementById(id);
                if(!el) return;
                let prev = el.parentNode.querySelector('.lt-input-preview');
                if(!prev) { prev=document.createElement('span'); prev.className='lt-input-preview'; el.parentNode.appendChild(prev); }
                const val = parseFloat(el.value);
                if(isNaN(val) || val === 0) prev.innerText = '';
                else {
                    const total = val * mult;
                    prev.innerText = Utils.formatNumber(total);
                }
            };
            bindPreview('lt-inp-bankroll');
            bindPreview('lt-inp-target');
        }
        init() {
            this.checkAndMount();
            engine.subscribe(() => { this.update(); this.checkAndMount(); });
            setInterval(() => this.checkAndMount(), 1000);

            // FLASH OVERLAY MOUNT
            if(!document.getElementById('lt-flash-overlay')) {
                const fl = document.createElement('div');
                fl.id = 'lt-flash-overlay';
                document.body.appendChild(fl);
            }
        }
        checkAndMount() {
            if(!window.location.href.includes("russianRoulette")) { const el=document.getElementById('lt-dashboard'); if(el) el.style.display='none'; return; }
            const content = document.querySelector('.content-wrapper') || document.querySelector('#mainContainer');
            if(content) {
                let dash = document.getElementById('lt-dashboard');
                if(!dash) this.render(content);
                else { if(dash.style.display==='none') dash.style.display='flex'; if(content.firstChild!==dash) content.insertBefore(dash, content.firstChild); }
            }
            this.syncHospitalWarning(); // poll every tick — independent of engine events
            this.syncManualButtons();
            this.syncBorderState();
        }
        // Disable the manual WIN / LOSS buttons while Auto: ON — a stray click
        // would double-count the round. Reads autoDetect from engine.state if not
        // passed (so the 1s poll can run it too, not only on engine events).
        syncManualButtons(autoOn) {
            if (autoOn === undefined) autoOn = !!engine.state.autoDetect;
            const winBtn = document.getElementById('lt-btn-win');
            const lossBtn = document.getElementById('lt-btn-loss');
            [winBtn, lossBtn].forEach(b => {
                if (!b) return;
                if (b.disabled !== !!autoOn) b.disabled = !!autoOn;
                b.classList.toggle('lt-disabled-auto', !!autoOn);
                b.title = autoOn ? 'Disabled while Auto: ON — turn Auto off to log a round manually' : '';
            });
        }

        // Toggle the dashboard hospital banner from a live DOM check (Torn's active
        // "Hospital: …" status icon). Runs on the 1s checkAndMount loop so it stays
        // in sync whether or not an engine state change fires.
        syncHospitalWarning() {
            const hospEl = document.getElementById('lt-hospital-warning');
            if (!hospEl) return;
            const inHospital = !!document.querySelector('[aria-label^="Hospital:" i]');
            const want = inHospital ? 'flex' : 'none';
            if (hospEl.style.display !== want) hospEl.style.display = want;
        }

        flash(type) {
            const el = document.getElementById('lt-flash-overlay');
            if(el) {
                el.className = type === 'win' ? 'win' : 'loss';
                void el.offsetWidth; // Force Reflow
                setTimeout(() => el.className = '', 300);
            }
            const db = document.getElementById('lt-dashboard');
            if(db) {
                db.classList.add(type==='win'?'flash-win':'flash-loss');
                setTimeout(()=>db.classList.remove('flash-win','flash-loss'), 600);
            }
        }
        // Full celebration on each new round result: dashboard glow + particle burst
        // from the bet display. WIN sprays $/✦ upward, LOSS drops ✕ downward.
        celebrate(type) {
            this.flash(type);
            const overlay = document.getElementById('lt-flash-overlay');
            const host = document.getElementById('lt-bet-box') || document.getElementById('lt-dashboard');
            if (!overlay || !host) return;
            const rect = host.getBoundingClientRect();
            const cx = rect.left + rect.width / 2;
            const cy = rect.top + rect.height / 2;
            const isWin = type === 'win';
            const count = isWin ? 16 : 12;
            const symbols = isWin ? ['$', '$', '✦', '✦', '★'] : ['✕', '✕', '✕', '☠'];
            for (let i = 0; i < count; i++) {
                const p = document.createElement('span');
                p.className = `lt-particle ${type}`;
                // WIN: upward sweep (angles 180°–360° → sin < 0); LOSS: downward (0°–180° → sin > 0)
                const angle = isWin
                    ? Math.PI + Math.random() * Math.PI
                    : Math.random() * Math.PI;
                const dist = 70 + Math.random() * 110;
                const dx = Math.cos(angle) * dist;
                const dy = Math.sin(angle) * dist;
                p.style.left = cx + 'px';
                p.style.top  = cy + 'px';
                p.style.setProperty('--lt-dx', dx + 'px');
                p.style.setProperty('--lt-dy', dy + 'px');
                p.style.setProperty('--lt-rot', (Math.random() * 720 - 360) + 'deg');
                p.textContent = symbols[Math.floor(Math.random() * symbols.length)];
                overlay.appendChild(p);
                setTimeout(() => p.remove(), 1000);
            }
        }
        render(container) {
            const div = document.createElement('div'); div.id = 'lt-dashboard'; div.className = 'visible';
            div.innerHTML = `
                <div id="lt-header">
                    <span style="display:flex;align-items:center;gap:8px;">
                        <span class="lt-glyph-chamber lt-glyph-lg" title="LabTrack"></span>
                        LabTrack <span style="color:#a78bfa;font-weight:400;">V${CONFIG.VERSION}</span>
                        <span id="lt-header-profit">0</span>
                    </span>
                    <div style="display:flex;gap:10px;align-items:center;">
                        <button id="lt-btn-dl-log" class="lt-btn lt-btn-action" style="padding:4px 8px;width:auto;" title="Audit Log herunterladen">📥 Log</button>
                        <button id="lt-btn-info" class="lt-btn lt-btn-action" style="padding:4px 8px;width:auto;">ℹ️ Info</button>
                        <span id="lt-toggle-ui" style="cursor:pointer;padding:4px 8px;background:rgba(0,0,0,0.2);border-radius:4px;">_</span>
                    </div>
                </div>
                <div id="lt-content">
                    <div id="lt-status-bar" class="lt-status-bar">WAITING FOR LOBBY...</div>

                    <!-- V8: HERO LAYOUT -->
                    <div class="lt-hero">
                        <!-- LEFT STATS COLUMN -->
                        <div class="lt-hero-side">
                            <div class="lt-stat-card">
                                <div class="sc-label">Rounds</div>
                                <div class="sc-value" id="lt-rounds-val">0</div>
                            </div>
                            <div class="lt-stat-card">
                                <div class="sc-label">Winrate</div>
                                <div class="sc-value" id="lt-winrate-val">0%</div>
                            </div>
                            <div class="lt-stat-card">
                                <div class="sc-label">Remaining</div>
                                <div class="sc-value" id="lt-rem-val">0</div>
                            </div>
                        </div>

                        <!-- CENTER HERO -->
                        <div class="lt-hero-center" id="lt-bet-box">
                            <div class="lt-bet-label-v8"><span class="lt-glyph-chamber"></span> Next Bet <span class="lt-glyph-chamber"></span></div>
                            <div class="lt-bet-ring-wrap">
                                <svg class="lt-bet-ring" viewBox="0 0 200 200" aria-hidden="true">
                                    <circle class="lt-bet-ring-track" cx="100" cy="100" r="92" fill="none" stroke="rgba(255,255,255,0.06)" stroke-width="6"/>
                                    <circle class="lt-bet-ring-fill"  cx="100" cy="100" r="92" fill="none" stroke="rgba(124,58,237,0.35)" stroke-width="6" stroke-linecap="round"
                                            id="lt-bet-ring-fill" data-circumference="578.05" stroke-dasharray="578.05" stroke-dashoffset="578.05"
                                            transform="rotate(-90 100 100)"/>
                                </svg>
                                <div id="lt-bet-display" class="lt-mega">0</div>
                                <div id="lt-bet-ring-val" class="lt-bet-ring-val"></div>
                            </div>
                            <div class="lt-action-row-mega">
                                <button id="lt-btn-win" class="lt-btn-mega lt-btn-mega-win">WIN</button>
                                <button id="lt-btn-loss" class="lt-btn-mega lt-btn-mega-loss">LOSS</button>
                            </div>
                            <div class="lt-mini-row">
                                <button id="lt-btn-auto" class="lt-mini-btn">Auto: ON</button>
                                <button id="lt-btn-reset-bet" class="lt-mini-btn">🔄 Reset Bet</button>
                                <button id="lt-btn-undo-hero" class="lt-mini-btn" title="Undo last result">↩ Undo</button>
                            </div>
                        </div>

                        <!-- RIGHT STATS COLUMN -->
                        <div class="lt-hero-side">
                            <div class="lt-stat-card">
                                <div class="sc-label">Streak</div>
                                <div class="sc-value" id="lt-streak-val">0</div>
                            </div>
                            <div class="lt-stat-card">
                                <div class="sc-label">Profit</div>
                                <div class="sc-value" id="lt-prof-val">0</div>
                            </div>
                            <div class="lt-stat-card">
                                <div class="sc-label">Status</div>
                                <div class="sc-value" id="lt-status-mini" style="font-size:13px;color:#a78bfa;">READY</div>
                            </div>
                        </div>
                    </div>

                    <!-- HOSPITAL WARNING — always visible on the dashboard while hospitalized -->
                    <div id="lt-hospital-warning" class="lt-stop-warning" style="display:none;">
                        <div class="lt-stop-warning-text">
                            <span class="lt-stop-warning-title">🏥 You are in the hospital</span>
                            <span class="lt-stop-warning-reason">You can't join or create RR games until you're out.</span>
                        </div>
                    </div>

                    <!-- STOP-LOSS / SEQUENCE-LENGTH WARNING (Labouchere discipline) -->
                    <div id="lt-stop-warning" class="lt-stop-warning" style="display:none;">
                        <div class="lt-stop-warning-text">
                            <span class="lt-stop-warning-title">⚠️ Abort recommended</span>
                            <span id="lt-stop-warning-reason" class="lt-stop-warning-reason"></span>
                        </div>
                        <button id="lt-stop-warning-reset" class="lt-stop-warning-btn">↺ New standard line</button>
                        <button id="lt-stop-warning-close" class="lt-stop-warning-close" title="Dismiss — reappears if the situation gets worse">✕</button>
                    </div>

                    <!-- V8.x: CUSTOM REBUILD OF TORN'S APPCONTAINER -->
                    <div id="lt-game-frame">
                        <div id="lt-game-frame-slot"></div>
                    </div>

                    <!-- PROFIT CHART (collapsible) — every round as a dot, line through them -->
                    <div class="lt-section lt-seq-section" id="lt-chart-section">
                        <div class="lt-section-title lt-seq-toggle" id="lt-chart-toggle">
                            <span><span class="lt-glyph-chamber"></span> Profit History <span class="lt-coll-icon">▼</span></span>
                            <span class="meta" id="lt-chart-meta">— rounds</span>
                        </div>
                        <div class="lt-seq-collapse-body" id="lt-chart-body">
                            <div class="lt-profit-chart-wrap">
                                <svg id="lt-profit-chart" class="lt-profit-chart" preserveAspectRatio="none" viewBox="0 0 600 240">
                                    <g class="lt-chart-grid"   id="lt-chart-grid"></g>
                                    <g class="lt-chart-axis-y" id="lt-chart-axis-y"></g>
                                    <g class="lt-chart-axis-x" id="lt-chart-axis-x"></g>
                                    <text class="lt-chart-axis-title" x="10" y="14" fill="#94a3b8" font-size="10" font-family="monospace">$</text>
                                    <line class="lt-chart-zero" id="lt-chart-zero" x1="0" x2="600" y1="0" y2="0" stroke="rgba(255,255,255,0.20)" stroke-dasharray="3,3"/>
                                    <g id="lt-chart-segments"></g>
                                    <g id="lt-chart-dots"></g>
                                </svg>
                                <div class="lt-chart-empty" id="lt-chart-empty">No rounds yet — play to see your profit chart.</div>
                                <div class="lt-chart-tooltip" id="lt-chart-tooltip"></div>
                                <div class="lt-chart-legend"><span class="lt-chart-legend-dot"></span> Total</div>
                            </div>
                        </div>
                    </div>

                    <!-- SEQUENCE SECTION (collapsible) -->
                    <div class="lt-section lt-seq-section" id="lt-seq-section">
                        <div class="lt-section-title lt-seq-toggle" id="lt-seq-toggle">
                            <span><span class="lt-glyph-chamber"></span> Sequence <span class="lt-coll-icon">▼</span></span>
                            <span class="meta" id="lt-seq-meta">— numbers</span>
                        </div>
                        <div class="lt-seq-collapse-body" id="lt-seq-body">
                            <div id="lt-sequence" class="lt-seq-container" style="min-height:60px;"></div>
                            <div id="lt-play-again-container" style="display:none;flex-direction:column;justify-content:center;">
                                <div class="lt-finished-msg" style="text-align:center;color:#4ade80;font-weight:bold;margin-bottom:10px;">✓ Sequence Complete!</div>
                                <button id="lt-btn-play-again" class="lt-btn lt-btn-play-again">🔄 Play Again (Same)</button>
                                <button id="lt-btn-new-random" class="lt-btn lt-btn-new-random">🎲 New Random</button>
                            </div>
                            <button id="lt-btn-confirm-merge" class="lt-btn lt-btn-confirm lt-hidden">✓ Confirm Merge</button>
                        </div>
                    </div>

                    <!-- ACTION BAR -->
                    <div class="lt-action-bar">
                        <div class="lt-action-group">
                            <button id="lt-btn-gen-show" class="lt-btn lt-btn-action">⚙ Setup</button>
                            <button id="lt-btn-undo" class="lt-btn lt-btn-action">↩ Undo</button>
                        </div>
                        <div class="lt-action-group">
                            <button id="lt-btn-shuffle" class="lt-btn lt-btn-action">🔀 Shuffle</button>
                            <button id="lt-btn-merge" class="lt-btn lt-btn-action">⊕ Merge</button>
                            <button id="lt-btn-copy-seq" class="lt-btn lt-btn-action" title="Copy sequence">📋 Copy</button>
                            <button id="lt-btn-paste-seq" class="lt-btn lt-btn-action" title="Paste sequence">📥 Paste</button>
                        </div>
                        <div class="lt-action-group">
                            <button id="lt-btn-game" class="lt-btn lt-btn-action" title="Flappy Bird">🐦</button>
                            <button id="lt-btn-snake" class="lt-btn lt-btn-action" title="Snake">🐍</button>
                        </div>
                    </div>

                <div id="lt-gen-panel" class="lt-hidden" style="background:#0f172a;padding:15px;border-radius:8px;border:1px solid #334155;margin-top:10px;">
                    <div class="lt-tabs"><div class="lt-tab active" id="lt-tab-bankroll">Bankroll</div><div class="lt-tab" id="lt-tab-target">Target</div></div>

                    <div class="lt-mult-group">
                        <div id="lt-btn-mult-1x" class="lt-mult-btn ${this.savedSettings.multKey==='1x'?'active':''}">1x</div>
                        <div id="lt-btn-mult-k" class="lt-mult-btn ${this.savedSettings.multKey==='k'?'active':''}">K (Thous)</div>
                        <div id="lt-btn-mult-m" class="lt-mult-btn ${this.savedSettings.multKey==='m'?'active':''}">M (Mill)</div>
                        <div id="lt-btn-mult-b" class="lt-mult-btn ${this.savedSettings.multKey==='b'?'active':''}">B (Bill)</div>
                    </div>

                    <div id="lt-mode-bankroll"><div class="lt-input-group"><span class="lt-label">Bankroll</span><input id="lt-inp-bankroll" class="lt-input" type="number" value="${this.savedSettings.bankroll}"></div><div class="lt-input-group"><span class="lt-label">Risk %</span><input id="lt-inp-risk" class="lt-input" type="number" value="${this.savedSettings.risk}"></div></div>
                    <div id="lt-mode-target" class="lt-hidden"><div class="lt-input-group"><span class="lt-label">Target</span><input id="lt-inp-target" class="lt-input" type="number" value="${this.savedSettings.target}"></div></div>
                    <div class="lt-input-group"><span class="lt-label">Parts</span><input id="lt-inp-parts" class="lt-input" type="number" value="${this.savedSettings.parts}"><button id="lt-btn-suggest-parts" class="lt-suggest-btn" type="button" title="Suggest a parts count from your Risk % so the opening bet stays ~0.5% of bankroll">Suggest</button></div>
                    <div style="margin-top:12px;display:flex;gap:15px;">
                        <label class="lt-checkbox-row"><input type="checkbox" id="lt-chk-integers" class="lt-checkbox" ${this.savedSettings.integers?'checked':''}> Whole Numbers</label>
                        <label class="lt-checkbox-row"><input type="checkbox" id="lt-chk-uniform" class="lt-checkbox" ${this.savedSettings.uniform?'checked':''}> Equal Split</label>
                    </div>

                    <!-- V7.00: Preview Section -->
                    <div id="lt-gen-preview" style="margin-top:15px;padding:12px;background:rgba(124,58,237,0.08);border:1px solid rgba(124,58,237,0.2);border-radius:6px;display:none;">
                        <div style="font-size:11px;color:#a78bfa;font-weight:bold;margin-bottom:8px;">📊 PREVIEW</div>
                        <div id="lt-gen-preview-stats" style="font-size:11px;color:#cbd5e1;margin-bottom:8px;"></div>
                        <div id="lt-gen-preview-seq" style="display:flex;flex-wrap:wrap;gap:4px;max-height:60px;overflow-y:auto;"></div>
                    </div>

                    <button id="lt-btn-generate" class="lt-btn lt-btn-primary" style="margin-top:15px;">Generate</button>
                    <button id="lt-btn-reset-all" class="lt-btn lt-btn-reset">⚠️ Reset Data</button>
                </div>

                <div id="lt-history-panel" class="lt-hidden" style="background:#0f172a;padding:10px;border-radius:8px;border:1px solid #334155;margin-top:10px;">
                    <h4 style="color:#e2e8f0;margin:0 0 10px 0;font-size:14px;text-align:center;">History</h4><div id="lt-history-list" class="lt-history-list"></div><button id="lt-btn-close-hist" class="lt-btn lt-btn-action" style="margin-top:10px;">Close</button>
                </div>

                <div id="lt-info-panel" class="lt-hidden" style="background:#0f172a;padding:15px;border-radius:8px;border:1px solid #334155;margin-top:10px;max-height:400px;overflow-y:auto;">
                    <div class="lt-info-grid">
                        <div class="lt-info-card">
                            <div class="lt-info-head">📖 Strategy Guide (Labouchere)</div>
                            <ul class="lt-info-list">
                                <li>Set a <b>Target</b> (Profit Goal).</li>
                                <li>The script splits this into a sequence.</li>
                                <li><b>Bet:</b> First + Last Number.</li>
                                <li><b>Win:</b> Numbers are crossed out.</li>
                                <li><b>Loss:</b> Bet is added to the end.</li>
                            </ul>
                        </div>
                        <div class="lt-info-card">
                            <div class="lt-info-head">🚀 Features</div>
                            <ul class="lt-info-list">
                                <li><b>Auto-Detect:</b> Detects Wins/Losses automatically.</li>
                                <li><b>Smart Drag & Drop:</b> Reorder numbers via mouse.</li>
                                <li><b>Multiplier:</b> Use K/M/B buttons for input.</li>
                                <li><b>Pot Scanner:</b> Infers bet from Pot Money.</li>
                                <li><b>Safe Lock:</b> Prevents double-counting wins.</li>
                            </ul>
                        </div>
                        <div class="lt-info-card">
                            <div class="lt-info-head">🎛️ Controls</div>
                            <ul class="lt-info-list">
                                <li><b>Split:</b> Click a number to split it.</li>
                                <li><b>Merge:</b> Select two numbers to merge them.</li>
                                <li><b>Undo:</b> "Last Round" reverts the last action.</li>
                                <li><b>Custom Bet:</b> Click the purple box.</li>
                            </ul>
                        </div>
                        <div class="lt-info-card">
                            <div class="lt-info-head">⚖️ Terms of Service & Risk Disclosure</div>
                            <table class="lt-tos-table">
                                <tr><th>Item</th><th>Details</th></tr>
                                <tr><td>License</td><td>Free to use. Provided "as is" without warranty.</td></tr>
                                <tr><td>Risk</td><td>Gambling involves financial risk. The Labouchere strategy can lead to high bets during losing streaks.</td></tr>
                                <tr><td>Torn Rules</td><td>This is a helper tool. It does not automate clicks (Macroing). Use responsibly to avoid bans.</td></tr>
                                <tr><td>Data Privacy</td><td>Operates 100% locally. Reads User ID & Game Data to function. No data is sent to external servers.</td></tr>
                                <tr><td>Responsibility</td><td>The author is not responsible for any virtual money lost while using this script.</td></tr>
                            </table>
                        </div>
                    </div>
                    <button id="lt-btn-close-info" class="lt-btn lt-btn-action" style="margin-top:15px;">Close</button>
                </div>

                <!-- V8.4: Fixed Game History panel on the right -->
                <div id="lt-history-side">
                    <div class="lt-hist-side-head"><span>📜 Game History</span></div>
                    <div id="lt-hist-side-list"></div>
                </div>

                </div>`;
            container.insertBefore(div, container.firstChild);

            // Bind Multiplier Buttons
            ['1x','k','m','b'].forEach(k => {
                const btn = document.getElementById('lt-btn-mult-'+k);
                if(btn) btn.onclick = () => this.setMultiplier(k);
            });

            ['lt-inp-bankroll','lt-inp-risk','lt-inp-target','lt-inp-parts','lt-chk-integers','lt-chk-uniform'].forEach(id=>{
                const el=document.getElementById(id); if(el){ el.addEventListener('change',()=>this.saveSettingsFromUI()); el.addEventListener('input',()=>this.saveSettingsFromUI()); }
            });
            // Stop the mouse wheel from changing number inputs — blur on wheel so the
            // page scrolls normally instead of silently editing the value.
            // Also auto-fit the multiplier once the user commits a value (on change,
            // not on every keystroke, so typing stays smooth).
            ['lt-inp-bankroll','lt-inp-risk','lt-inp-target','lt-inp-parts'].forEach(id=>{
                const el=document.getElementById(id);
                if(el) {
                    el.addEventListener('wheel', () => { if(document.activeElement===el) el.blur(); }, { passive:true });
                    el.addEventListener('change', () => this.autoFitMultiplier());
                }
            });
            this.switchGenMode(this.savedSettings.mode);

            document.getElementById('lt-toggle-ui').onclick = () => { const c = document.getElementById('lt-content'); c.style.display = c.style.display === 'none' ? 'flex' : 'none'; };
            document.getElementById('lt-btn-win').onclick = () => engine.processWin();
            document.getElementById('lt-btn-loss').onclick = () => engine.processLoss();

            // Toggle Panels
            const closeAllPanels = () => {
                ['lt-gen-panel', 'lt-history-panel', 'lt-info-panel'].forEach(id => { const el = document.getElementById(id); if (el) el.classList.add('lt-hidden'); });
                // Mini-game popups are independent (floating) — NOT closed here
            };

            document.getElementById('lt-btn-gen-show').onclick = () => {
                const p = document.getElementById('lt-gen-panel');
                const wasHidden = p.classList.contains('lt-hidden');
                closeAllPanels();
                if(wasHidden) { p.classList.remove('lt-hidden'); this.updateInputPreviews(); }
            };
            document.getElementById('lt-btn-info').onclick = () => {
                const p = document.getElementById('lt-info-panel');
                const wasHidden = p.classList.contains('lt-hidden');
                closeAllPanels();
                if(wasHidden) p.classList.remove('lt-hidden');
            };

            document.getElementById('lt-btn-close-hist').onclick = () => document.getElementById('lt-history-panel').classList.add('lt-hidden');
            document.getElementById('lt-btn-close-info').onclick = () => document.getElementById('lt-info-panel').classList.add('lt-hidden');

            document.getElementById('lt-btn-game').onclick  = () => this.toggleGamePopup('flappy');
            document.getElementById('lt-btn-snake').onclick = () => this.toggleGamePopup('snake');

            document.getElementById('lt-tab-bankroll').onclick = () => this.switchGenMode('bankroll');
            document.getElementById('lt-tab-target').onclick = () => this.switchGenMode('target');
            document.getElementById('lt-btn-generate').onclick = () => this.handleGenerate();
            document.getElementById('lt-btn-reset-all').onclick = () => { if(confirm("Reset all data?")) engine.resetData(); };

            // V7.00: Preview on input change
            const previewInputs = ['lt-inp-bankroll', 'lt-inp-risk', 'lt-inp-target', 'lt-inp-parts'];
            previewInputs.forEach(id => {
                const el = document.getElementById(id);
                if (el) {
                    el.addEventListener('input', () => this.updatePreview());
                }
            });
            ['lt-chk-integers', 'lt-chk-uniform'].forEach(id => {
                const el = document.getElementById(id);
                if (el) {
                    el.addEventListener('change', () => this.updatePreview());
                }
            });
            document.getElementById('lt-btn-play-again').onclick = () => engine.restartGame();
            document.getElementById('lt-btn-new-random').onclick = () => this.handleGenerate();
            document.getElementById('lt-btn-shuffle').onclick = () => engine.shuffleSequence();
            document.getElementById('lt-btn-merge').onclick = () => { this.mergeMode = !this.mergeMode; this.selectedForMerge = []; this.update(); };
            document.getElementById('lt-seq-toggle').onclick = () => document.getElementById('lt-seq-section').classList.toggle('collapsed');
            document.getElementById('lt-chart-toggle').onclick = () => document.getElementById('lt-chart-section').classList.toggle('collapsed');

            document.getElementById('lt-btn-dl-log').onclick = () => auditLog.download();

            document.getElementById('lt-btn-copy-seq').onclick = () => {
                const seq = engine.state.sequence;
                if (!seq.length) { Utils.showToast('No sequence available'); return; }
                const text = seq.map(i => i.value).join(',');
                navigator.clipboard.writeText(text)
                    .then(() => {
                        Utils.showToast(`Copied (${seq.length} values)`);
                        auditLog.record('EVENT', 'SEQ/COPY', `Sequence copied`, { values: seq.map(i => i.value) });
                    })
                    .catch(() => Utils.showToast('Copy failed'));
            };

            document.getElementById('lt-btn-paste-seq').onclick = () => {
                navigator.clipboard.readText()
                    .then(text => {
                        const values = text.split(',')
                            .map(s => parseFloat(s.trim()))
                            .filter(v => !isNaN(v) && v > 0);
                        if (!values.length) { Utils.showToast('No valid values in clipboard'); return; }
                        if (!confirm(`Paste ${values.length} values?\nCurrent sequence and profit will be reset.`)) return;
                        engine.loadFromValues(values);
                        Utils.showToast(`Pasted (${values.length} values)`);
                        this.update();
                    })
                    .catch(() => Utils.showToast('Paste failed'));
            };
            document.getElementById('lt-btn-confirm-merge').onclick = () => { engine.mergeList(this.selectedForMerge); this.selectedForMerge=[]; this.mergeMode=false; this.update(); };
            document.getElementById('lt-btn-auto').onclick = () => engine.toggleAutoDetect();
            document.getElementById('lt-btn-undo').onclick = () => engine.undo();
            document.getElementById('lt-btn-suggest-parts').onclick = () => this.suggestParts();
            document.getElementById('lt-btn-undo-hero').onclick = () => engine.undo();
            document.getElementById('lt-stop-warning-close').onclick = () => {
                const s = engine.state;
                const targetUnits = (s.initialSequence || []).reduce((a, b) => a + (b.value || 0), 0);
                const lossUnits = s.totalProfit < 0 ? -s.totalProfit : 0;
                // Snapshot the current "badness" — banner stays hidden until it grows.
                this._stopWarnDismissed = { length: s.sequence.length, lossUnits, targetUnits };
                document.getElementById('lt-stop-warning').style.display = 'none';
            };
            document.getElementById('lt-stop-warning-reset').onclick = () => {
                engine.restartGame();
                Utils.showToast('New standard line');
                this.update();
            };
            document.getElementById('lt-btn-reset-bet').onclick = () => {
                engine.resetBet();
                Utils.showToast('Bet Reset');
                this.update();
            };
            document.getElementById('lt-bet-box').onclick = (e) => {
                // V8: Only open custom-bet dialog when clicking the bet display or its label
                // (not WIN/LOSS/Auto/Reset which now live inside the hero-center)
                if (e.target.id !== 'lt-bet-display' && !e.target.classList.contains('lt-bet-label-v8')) return;
                // V7.02: Improved Custom Bet dialog
                const currentCustom = engine.state.customBet;
                const expectedBet = engine.getEffectiveBet();

                let promptText = "Custom Bet (Total Amount):";
                if (currentCustom !== null) {
                    promptText = `Custom Bet Active: ${Utils.formatNumber(currentCustom)}\n\nEnter new amount (0 to clear):`;
                } else {
                    promptText = `Expected Bet: ${Utils.formatNumber(expectedBet)}\n\nEnter Custom Bet (0 to use expected):`;
                }

                const inp = prompt(promptText, currentCustom || expectedBet);
                if(inp !== null) {
                    const v = parseFloat(inp);
                    if(!isNaN(v) && v > 0) {
                        engine.setCustomBet(v);
                        Utils.showToast(`Custom Bet Set: ${Utils.formatNumber(v)}`);
                    } else if(v === 0) {
                        engine.clearCustomBet();
                        Utils.showToast("Custom Bet Cleared");
                    }
                }
            };
            this.update();
        }
        // --- RESTORED METHODS ---
        switchGenMode(mode) {
            this.genMode = mode; this.saveSettingsFromUI();
            const b = document.getElementById('lt-mode-bankroll'); const t = document.getElementById('lt-mode-target');
            const tb = document.getElementById('lt-tab-bankroll'); const tt = document.getElementById('lt-tab-target');
            if(mode==='bankroll'){ b.classList.remove('lt-hidden'); t.classList.add('lt-hidden'); tb.classList.add('active'); tt.classList.remove('active'); }
            else { b.classList.add('lt-hidden'); t.classList.remove('lt-hidden'); tb.classList.remove('active'); tt.classList.add('active'); }
            this.updateInputPreviews();
            this.updatePreview();
        }

        // V7.00: Live Preview of Sequence
        updatePreview() {
            const preview = document.getElementById('lt-gen-preview');
            const statsEl = document.getElementById('lt-gen-preview-stats');
            const seqEl = document.getElementById('lt-gen-preview-seq');

            if (!preview || !statsEl || !seqEl) return;

            try {
                // Get current inputs
                const parts = parseInt(document.getElementById('lt-inp-parts')?.value) || 10;
                const useIntegers = document.getElementById('lt-chk-integers')?.checked || false;
                const uniform = document.getElementById('lt-chk-uniform')?.checked || false;

                let target = 0;
                let errorMsg = null;

                if (this.genMode === 'bankroll') {
                    const bankroll = parseFloat(document.getElementById('lt-inp-bankroll')?.value) || 0;
                    const risk = parseFloat(document.getElementById('lt-inp-risk')?.value) || 0;

                    if (bankroll > 0 && risk > 0) {
                        target = bankroll * (risk / 100);
                    } else {
                        errorMsg = bankroll <= 0 ? 'Enter Bankroll' : 'Enter Risk %';
                    }
                } else {
                    target = parseFloat(document.getElementById('lt-inp-target')?.value) || 0;
                    if (target <= 0) {
                        errorMsg = 'Enter Target';
                    }
                }

                // Hide preview if invalid
                if (errorMsg || target <= 0 || parts <= 0) {
                    preview.style.display = 'none';
                    return;
                }

                // Generate preview sequence
                const previewSeq = Utils.generateSequence(target, parts, uniform, useIntegers);
                const actualSum = previewSeq.reduce((a, b) => a + b, 0);
                const deviation = Math.abs(actualSum - target);
                const deviationPercent = ((deviation / target) * 100).toFixed(2);

                // Worst-case bet projection: how high the bet climbs on a losing
                // streak (each loss appends the lost bet → next bet = first + last).
                const mult = this.savedSettings.multVal || 1;
                const proj = (vals, n) => {
                    let s = vals.slice();
                    let bet = s.length > 1 ? s[0] + s[s.length - 1] : (s[0] || 0);
                    for (let k = 0; k < n; k++) { s.push(bet); bet = s.length > 1 ? s[0] + s[s.length - 1] : s[0]; }
                    return bet;
                };
                const betNow = proj(previewSeq, 0) * mult;
                const bet5   = proj(previewSeq, 5) * mult;
                // Total risked across a 5-loss streak (rounds 0..4) — what you'd actually lose.
                let risked5 = 0; for (let n = 0; n < 5; n++) risked5 += proj(previewSeq, n) * mult;
                const realBankroll = this.genMode === 'bankroll'
                    ? (parseFloat(document.getElementById('lt-inp-bankroll')?.value) || 0) * mult : 0;
                const overBankroll = realBankroll > 0 && risked5 > realBankroll;
                const bet5Color = overBankroll ? '#f87171' : '#fbbf24';

                // Show stats
                statsEl.innerHTML = `
                    <div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;">
                        <div><span style="color:#94a3b8;">Target:</span> <b style="color:#fff;">${Utils.formatNumber(target)}</b></div>
                        <div><span style="color:#94a3b8;">Parts:</span> <b style="color:#fff;">${parts}</b></div>
                        <div><span style="color:#94a3b8;">Actual Sum:</span> <b style="color:${deviation < 1 ? '#4ade80' : '#fbbf24'};">${Utils.formatNumber(actualSum)}</b></div>
                        <div><span style="color:#94a3b8;">Deviation:</span> <b style="color:${deviation < 1 ? '#4ade80' : deviation < 10 ? '#fbbf24' : '#f87171'};">${deviationPercent}%</b></div>
                    </div>
                    <div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:8px;padding-top:8px;border-top:1px solid rgba(124,58,237,0.2);">
                        <div><span style="color:#94a3b8;">Start bet:</span> <b style="color:#fff;">${Utils.formatNumber(betNow)}</b></div>
                        <div><span style="color:#94a3b8;">Bet @ 5 losses:</span> <b style="color:${bet5Color};">${Utils.formatNumber(bet5)}</b></div>
                        <div style="grid-column:1 / -1;"><span style="color:#94a3b8;">Risked over 5-loss streak:</span> <b style="color:${bet5Color};">${Utils.formatNumber(risked5)}</b>${overBankroll ? ' <span style="color:#f87171;">⚠️ exceeds bankroll</span>' : ''}</div>
                    </div>
                `;

                // Show sequence badges
                seqEl.innerHTML = previewSeq.map(val =>
                    `<span style="background:#1e293b;border:1px solid rgba(124,58,237,0.3);padding:4px 8px;border-radius:4px;font-size:10px;color:#cbd5e1;font-family:monospace;">${val}</span>`
                ).join('');

                preview.style.display = 'block';

            } catch(e) {
                Logger.error('Preview', `Failed: ${e.message}`);
                preview.style.display = 'none';
            }
        }
        handleGenerate() {
            try {
                // Make sure we're in the right unit before generating (prevents deviation).
                this.autoFitMultiplier();
                const partsInput = document.getElementById('lt-inp-parts');
                const integersInput = document.getElementById('lt-chk-integers');
                const uniformInput = document.getElementById('lt-chk-uniform');
                const bankrollInput = document.getElementById('lt-inp-bankroll');
                const riskInput = document.getElementById('lt-inp-risk');
                const targetInput = document.getElementById('lt-inp-target');

                const parts = parseInt(partsInput ? partsInput.value : 10) || 10;
                const useIntegers = integersInput ? integersInput.checked : false;
                const uniform = uniformInput ? uniformInput.checked : false;

                // V6.29: Using short numbers for generation logic
                let target = 0;
                if (this.genMode === 'bankroll') {
                    const bankroll = parseFloat(bankrollInput ? bankrollInput.value : 0);
                    const risk = parseFloat(riskInput ? riskInput.value : 0);
                    if (bankroll > 0 && risk > 0) target = bankroll * (risk / 100);
                    else {
                        Utils.showToast("Enter Bankroll & Risk");
                        if(bankrollInput) bankrollInput.classList.add('error');
                        setTimeout(()=>bankrollInput?.classList.remove('error'), 1000);
                        return;
                    }
                } else {
                    target = parseFloat(targetInput ? targetInput.value : 0);
                    if (!target || target <= 0) {
                        Utils.showToast("Enter Target");
                        if(targetInput) targetInput.classList.add('error');
                        setTimeout(()=>targetInput?.classList.remove('error'), 1000);
                        return;
                    }
                }

                if(target > 0) {
                    engine.generateNew(target, parts, uniform, useIntegers);
                    document.getElementById('lt-gen-panel').classList.add('lt-hidden');
                }
            } catch(e) { console.error("Gen Error:", e); Utils.showToast("Gen Error"); }
        }
        renderHistory() { this.renderHistoryInto(document.getElementById('lt-history-list')); }
        renderHistoryInto(l) {
            if (!l) return; l.innerHTML='';
            const h = engine.state.roundHistory || [];
            // Reserve 10 visual slots — empty placeholders sit where future games will
            // land, so the list stays the same size from round 0 onwards. Anything past
            // the 10th still appears (the panel scrolls). No layout shift on add/remove.
            const SLOTS = 10;
            h.forEach(r => {
                const div = document.createElement('div');
                div.className = `lt-history-item ${r.result==='WIN'?'lt-history-win':'lt-history-loss'}`;
                div.innerHTML = `<span style="color:#888">${new Date(r.time).toLocaleTimeString([],{hour:'2-digit',minute:'2-digit'})}</span><span class="lt-hist-badge ${r.result==='WIN'?'lt-hist-win-bg':'lt-hist-loss-bg'}">${r.result}</span><b>$${Utils.formatNumber(r.bet)}</b><span style="color:${r.profit>=0?'#4ade80':'#f87171'}">${r.profit>0?'+':''}${Utils.formatNumber(r.profit)}</span>`;
                l.appendChild(div);
            });
            const empties = Math.max(0, SLOTS - h.length);
            for (let i = 0; i < empties; i++) {
                const div = document.createElement('div');
                div.className = 'lt-history-item lt-history-empty';
                div.innerHTML = `<span class="lt-hist-empty-line">— slot ${h.length + i + 1} —</span>`;
                l.appendChild(div);
            }
        }
        // Labouchere discipline: warn (don't force) when the line has grown too long
        // or the running loss has reached a multiple of the target. Experts advise
        // capping the sequence (~initial + 5 entries) and abandoning at ~2× target
        // loss, then restarting a fresh standard line instead of chasing.
        // Dashboard border stays the default calm purple — only Hospital flips it
        // to a pulsing red so an "I can't play" state is unmistakable at a glance.
        syncBorderState() {
            const db = document.getElementById('lt-dashboard');
            if (!db) return;
            const inHospital = !!document.querySelector('[aria-label^="Hospital:" i]');
            db.classList.toggle('lt-state-danger', inHospital);
            db.classList.remove('lt-state-hot', 'lt-state-cold');
        }

        // Tween a number element from its previous value to `to` over `dur` ms with
        // ease-out-cubic. Keeps the cinematic count-up feel without bloating updates.
        // Cancels any in-flight tween on the same element so rapid value changes don't
        // queue up. `signed` adds a leading '+' for positive values (used by profit pills).
        animateNumber(el, to, opts) {
            if (!el) return;
            opts = opts || {};
            const dur = opts.duration || 380;
            const signed = !!opts.signed;
            const suffix = opts.suffix || '';
            const fmt = (v) => {
                const sign = signed && v >= 0 ? '+' : '';
                return sign + Utils.formatNumber(v) + suffix;
            };
            if (el._ltAnim) cancelAnimationFrame(el._ltAnim);
            const from = (el._ltAnimTo != null) ? el._ltAnimTo : to; // first time → no tween
            // If the change is tiny relative to the value, skip the tween (avoids
            // micro-jitter on the bet display each tick during a game).
            if (Math.abs(to - from) < 0.5) { el.textContent = fmt(to); el._ltAnimTo = to; return; }
            el._ltAnimTo = to;
            const start = performance.now();
            const tick = (now) => {
                const t = Math.min(1, (now - start) / dur);
                const e = 1 - Math.pow(1 - t, 3);
                el.textContent = fmt(from + (to - from) * e);
                if (t < 1) el._ltAnim = requestAnimationFrame(tick);
                else { el._ltAnim = null; el.textContent = fmt(to); }
            };
            el._ltAnim = requestAnimationFrame(tick);
        }

        // Big profit chart — clean stock-style: one dot per round, segments coloured
        // by direction (green = rising/WIN, red = falling/LOSS). Y-axis on the left,
        // X-axis at the bottom (every ~5 rounds), dashed zero line, hover tooltips.
        updateProfitChart(s, mult) {
            const svg     = document.getElementById('lt-profit-chart');
            const segG    = document.getElementById('lt-chart-segments');
            const dotsG   = document.getElementById('lt-chart-dots');
            const gridG   = document.getElementById('lt-chart-grid');
            const axisY   = document.getElementById('lt-chart-axis-y');
            const axisX   = document.getElementById('lt-chart-axis-x');
            const zeroL   = document.getElementById('lt-chart-zero');
            const empty   = document.getElementById('lt-chart-empty');
            const meta    = document.getElementById('lt-chart-meta');
            const tip     = document.getElementById('lt-chart-tooltip');
            if (!svg || !segG || !dotsG) return;

            const hist = (s.roundHistory || []).slice().reverse(); // oldest → newest
            if (meta) meta.textContent = hist.length ? `${hist.length} round${hist.length === 1 ? '' : 's'}` : '— rounds';
            if (empty) empty.classList.toggle('hidden', hist.length >= 2);
            if (hist.length < 2) {
                segG.innerHTML = ''; dotsG.innerHTML = '';
                gridG.innerHTML = ''; axisY.innerHTML = ''; axisX.innerHTML = '';
                return;
            }
            const series = [];
            let acc = 0;
            for (const r of hist) {
                acc += (r.profit || 0);
                series.push({ cum: acc, profit: r.profit || 0, result: r.result, bet: r.bet || 0, time: r.time });
            }

            const W = 600, H = 240, PAD_L = 52, PAD_R = 10, PAD_T = 12, PAD_B = 22;
            const min = Math.min(0, ...series.map(p => p.cum));
            const max = Math.max(0, ...series.map(p => p.cum));
            const range = (max - min) || 1;
            const xStep = (series.length === 1) ? 0 : (W - PAD_L - PAD_R) / (series.length - 1);
            const xFor = i => PAD_L + i * xStep;
            const yFor = v => H - PAD_B - ((v - min) / range) * (H - PAD_T - PAD_B);

            // ── Y-axis: 5 ticks evenly distributed between min and max ──
            const yTicks = [];
            for (let i = 0; i <= 4; i++) yTicks.push(min + (max - min) * i / 4);
            gridG.innerHTML = yTicks.map(t => `<line x1="${PAD_L}" x2="${W - PAD_R}" y1="${yFor(t)}" y2="${yFor(t)}"/>`).join('');
            axisY.innerHTML = yTicks.map(t => `<text x="${PAD_L - 6}" y="${yFor(t) + 3}" text-anchor="end">${Utils.formatNumber(t)}</text>`).join('');

            // ── X-axis: round number every ~5 rounds (always include first + last) ──
            const xStepSize = series.length <= 10 ? 1 : series.length <= 30 ? 5 : series.length <= 80 ? 10 : 20;
            let xLabels = '';
            for (let i = 0; i < series.length; i++) {
                const round = i + 1;
                if (i === 0 || i === series.length - 1 || round % xStepSize === 0) {
                    xLabels += `<text x="${xFor(i)}" y="${H - PAD_B + 12}" text-anchor="middle">${round}</text>`;
                }
            }
            axisX.innerHTML = xLabels;

            // ── Zero baseline ──
            const baseY = yFor(0);
            zeroL.setAttribute('x1', PAD_L); zeroL.setAttribute('x2', W - PAD_R);
            zeroL.setAttribute('y1', baseY); zeroL.setAttribute('y2', baseY);

            // ── Segments: one <line> per pair, coloured by whether the running total
            // is in profit (green) or loss (red). When a segment crosses the zero line
            // it's split exactly at the crossing so each half gets its true colour. ──
            const GREEN = '#4ade80', RED = '#ef4444';
            const colorFor = v => v >= 0 ? GREEN : RED;
            let segHtml = '';
            for (let i = 0; i < series.length - 1; i++) {
                const a = series[i], b = series[i + 1];
                const ax = xFor(i),   ay = yFor(a.cum);
                const bx = xFor(i+1), by = yFor(b.cum);
                const crosses = (a.cum > 0 && b.cum < 0) || (a.cum < 0 && b.cum > 0);
                if (crosses) {
                    const t = a.cum / (a.cum - b.cum);            // fraction along segment where cum = 0
                    const cx = ax + (bx - ax) * t;
                    const cy = baseY;                              // y of cum = 0
                    segHtml += `<line x1="${ax}" y1="${ay}" x2="${cx}" y2="${cy}" stroke="${colorFor(a.cum)}"/>`;
                    segHtml += `<line x1="${cx}" y1="${cy}" x2="${bx}" y2="${by}" stroke="${colorFor(b.cum)}"/>`;
                } else {
                    // Use destination's sign (0 counts as green).
                    segHtml += `<line x1="${ax}" y1="${ay}" x2="${bx}" y2="${by}" stroke="${colorFor(b.cum)}"/>`;
                }
            }
            segG.innerHTML = segHtml;

            // ── Dots (one per round), coloured by result ──
            dotsG.innerHTML = series.map((p, i) =>
                `<circle class="lt-chart-dot ${p.result === 'WIN' ? 'win' : 'loss'}" cx="${xFor(i)}" cy="${yFor(p.cum)}" r="3.5" data-i="${i}"/>`
            ).join('');

            // ── Tooltips ── clamp inside the chart wrap so edge dots stay readable
            if (tip) {
                const wrap = svg.parentElement;
                dotsG.querySelectorAll('.lt-chart-dot').forEach(c => {
                    c.onmouseenter = () => {
                        const i = parseInt(c.dataset.i);
                        const p = series[i];
                        tip.innerHTML = `<b>Round ${i + 1}</b> · <span style="color:${p.result === 'WIN' ? '#4ade80' : '#f87171'}">${p.result}</span><br>
                            Bet ${Utils.formatNumber(p.bet)} · ${p.profit >= 0 ? '+' : ''}${Utils.formatNumber(p.profit)}<br>
                            Total: <b>${p.cum >= 0 ? '+' : ''}${Utils.formatNumber(p.cum)}</b>`;
                        // Show first so we can measure the rendered tooltip size
                        tip.style.transform = 'none';
                        tip.style.left = '0px'; tip.style.top = '0px';
                        tip.classList.add('show');
                        const wrapRect = wrap.getBoundingClientRect();
                        const dotRect  = c.getBoundingClientRect();
                        const tipRect  = tip.getBoundingClientRect();
                        const pad = 6;
                        // Horizontal: centre on dot, then clamp inside wrap bounds
                        let cx = dotRect.left - wrapRect.left + dotRect.width / 2;
                        let left = cx - tipRect.width / 2;
                        left = Math.max(pad, Math.min(left, wrapRect.width - tipRect.width - pad));
                        // Vertical: above the dot by tipHeight + 8; if not enough room, place below
                        let top = (dotRect.top - wrapRect.top) - tipRect.height - 8;
                        if (top < pad) top = (dotRect.top - wrapRect.top) + dotRect.height + 8;
                        tip.style.left = left + 'px';
                        tip.style.top  = top + 'px';
                    };
                    c.onmouseleave = () => tip.classList.remove('show');
                });
            }
        }

        // Risk ring around the bet display — same idea as the old meter, but as a
        // circular SVG arc hugging the bet number. Tweens green → yellow → red as
        // you approach 2× target loss, pulses red once you cross.
        updateRiskRing(lossUnits, targetUnits, mult) {
            const fill = document.getElementById('lt-bet-ring-fill');
            const valEl = document.getElementById('lt-bet-ring-val');
            if (!fill || !valEl) return;
            const C = parseFloat(fill.dataset.circumference) || 0;
            const threshold = targetUnits * 2;
            if (!(threshold > 0) || !C) {
                fill.style.strokeDashoffset = C;
                fill.style.stroke = 'rgba(124,58,237,0.35)';
                fill.classList.remove('danger');
                valEl.textContent = '';
                return;
            }
            const rawPct = (lossUnits / threshold) * 100;
            const pct = Math.min(100, Math.max(0, rawPct));
            // Lerp green (34,197,94) → yellow (251,191,36) → red (239,68,68)
            const lerp = (a, b, t) => Math.round(a + (b - a) * t);
            let r, g, b;
            if (pct <= 50) {
                const t = pct / 50;
                r = lerp(34, 251, t); g = lerp(197, 191, t); b = lerp(94, 36, t);
            } else {
                const t = (pct - 50) / 50;
                r = lerp(251, 239, t); g = lerp(191, 68, t); b = lerp(36, 68, t);
            }
            const color = `rgb(${r},${g},${b})`;
            fill.style.strokeDashoffset = C - (pct / 100) * C;
            fill.style.stroke = color;
            fill.style.filter = pct > 50 ? `drop-shadow(0 0 ${6 + (pct - 50) / 6}px rgba(${r},${g},${b},0.7))` : 'none';
            fill.classList.toggle('danger', rawPct >= 100);
            valEl.style.color = color;
            valEl.textContent = `${Math.round(rawPct)}%`;
        }

        updateStopWarning(s, mult) {
            const banner = document.getElementById('lt-stop-warning');
            if (!banner) return;
            const reasonEl = document.getElementById('lt-stop-warning-reason');

            const initialLen = (s.initialSequence && s.initialSequence.length) || 10;
            const lengthThreshold = initialLen + 5;
            const targetUnits = (s.initialSequence || []).reduce((a, b) => a + (b.value || 0), 0);
            const lossUnits = s.totalProfit < 0 ? -s.totalProfit : 0;
            const lossThreshold = targetUnits * 2; // 2× target

            // Update the risk ring (around the bet display) — single source of truth.
            this.updateRiskRing(lossUnits, targetUnits, mult);

            const lenHit = s.sequence.length >= lengthThreshold;
            const lossHit = targetUnits > 0 && lossUnits >= lossThreshold;

            if (!lenHit && !lossHit) {
                // Conditions clear → also reset any dismissal so the next trigger shows again.
                this._stopWarnDismissed = null;
                banner.style.display = 'none';
                return;
            }

            // Respect a user dismissal until the situation gets noticeably worse
            // (line grew further OR loss grew by ≥10% of target). Otherwise it'd just
            // pop right back the next tick and the close button would be useless.
            if (this._stopWarnDismissed) {
                const dis = this._stopWarnDismissed;
                const worsened = s.sequence.length > dis.length
                              || (targetUnits > 0 && lossUnits >= dis.lossUnits + targetUnits * 0.1);
                if (!worsened) { banner.style.display = 'none'; return; }
                this._stopWarnDismissed = null; // re-show with a fresh state
            }

            const reasons = [];
            if (lossHit) reasons.push(`Loss ${Utils.formatNumber(lossUnits * mult)} ≥ 2× target`);
            if (lenHit) reasons.push(`Line grew to ${s.sequence.length} numbers`);
            if (reasonEl) reasonEl.innerText = reasons.join(' · ') + ' — abandon & restart, don\'t chase.';
            banner.style.display = 'flex';
        }

        update() {
            try {
                if(!document.getElementById('lt-bet-display')) { this.updateExternalButtons(); return; }
                const s = engine.state; const bet = engine.getEffectiveBet();

                // Calculate internal profit with multiplier
                const mult = this.savedSettings.multVal || 1;
                const totalProfitVal = s.totalProfit * mult;

                const bd = document.getElementById('lt-bet-display'); const bb = document.getElementById('lt-bet-box');
                this.animateNumber(bd, bet, { duration: 320 });
                if(s.customBet!==null) { bd.classList.add('override'); bb.classList.add('override'); } else { bd.classList.remove('override'); bb.classList.remove('override'); }

                const ab = document.getElementById('lt-btn-auto');
                if(s.autoDetect) { ab.innerText="Auto: ON"; ab.classList.add('active'); } else { ab.innerText="Auto: OFF"; ab.classList.remove('active'); }
                this.syncManualButtons(s.autoDetect);

                // V6.33 FIX: Correctly sum up sequence values (objects)
                const rawSum = s.sequence.reduce((a,b) => a + (b.value || 0), 0);
                this.animateNumber(document.getElementById('lt-rem-val'), rawSum * mult);

                const profEl = document.getElementById('lt-prof-val');
                this.animateNumber(profEl, totalProfitVal);
                profEl.style.color = totalProfitVal>=0?'#4ade80':'#f87171';

                // V8: Header live-profit pill
                const hp = document.getElementById('lt-header-profit');
                if (hp) {
                    this.animateNumber(hp, totalProfitVal, { signed: true });
                    hp.classList.remove('pos', 'neg');
                    if (totalProfitVal > 0) hp.classList.add('pos');
                    else if (totalProfitVal < 0) hp.classList.add('neg');
                }
                this.updateProfitChart(s, mult);

                // Trigger the round-result celebration when a NEW history entry
                // appears (manual click OR auto-detect — single unified path).
                const histLen = (s.roundHistory || []).length;
                if (this._lastHistLen != null && histLen > this._lastHistLen) {
                    const newest = s.roundHistory[0];
                    if (newest) this.celebrate(newest.result === 'WIN' ? 'win' : 'loss');
                }
                this._lastHistLen = histLen;

                this.updateStopWarning(s, mult);
                this.syncHospitalWarning(); // also poll-driven via checkAndMount

                // V6.33: Calc Streak & Winrate
                let streak = 0; let streakType = '';
                if(s.roundHistory.length > 0) {
                    streakType = s.roundHistory[0].result;
                    for(let i=0; i<s.roundHistory.length; i++) {
                        if(s.roundHistory[i].result === streakType) streak++;
                        else break;
                    }
                }
                const wins = s.roundHistory.filter(r=>r.result==='WIN').length;
                const rate = s.roundHistory.length > 0 ? Math.round((wins / s.roundHistory.length)*100) : 0;
                this.syncBorderState();

                // Update Rounds Counter
                const rv = document.getElementById('lt-rounds-val');
                if(rv) this.animateNumber(rv, s.roundCount || 0, { duration: 280 });

                const sv = document.getElementById('lt-streak-val');
                // Streak visualisation: flames for WIN streaks (size+glow grows with
                // streak length), ❄ for LOSS streaks of 2+, plain number otherwise.
                sv.classList.remove('lt-streak-fire-1','lt-streak-fire-2','lt-streak-fire-3','lt-streak-cold');
                if (streakType === 'WIN' && streak >= 1) {
                    const lvl = streak >= 6 ? 3 : streak >= 3 ? 2 : 1;
                    sv.classList.add(`lt-streak-fire-${lvl}`);
                    sv.style.color = '';
                    sv.innerHTML = `<span class="lt-streak-icon">🔥</span><span class="lt-streak-num">${streak}</span>`;
                } else if (streakType === 'LOSS' && streak >= 2) {
                    sv.classList.add('lt-streak-cold');
                    sv.style.color = '';
                    sv.innerHTML = `<span class="lt-streak-icon">❄</span><span class="lt-streak-num">${streak}</span>`;
                } else {
                    sv.innerText = streak > 0 ? `${streak} ${streakType}` : '0';
                    sv.style.color = streakType === 'WIN' ? '#4ade80' : (streakType === 'LOSS' ? '#f87171' : '#e2e8f0');
                }

                const wr = document.getElementById('lt-winrate-val');
                this.animateNumber(wr, rate, { duration: 280, suffix: '%' });
                wr.style.color = rate >= 50 ? '#4ade80' : '#e2e8f0';

                const mb = document.getElementById('lt-btn-confirm-merge');
                const mt = document.getElementById('lt-btn-merge');
                if(this.mergeMode) mt.classList.add('lt-btn-active'); else mt.classList.remove('lt-btn-active');
                if(this.mergeMode && this.selectedForMerge.length>=2) { mb.classList.remove('lt-hidden'); mb.innerText=`✓ Confirm Merge (${this.selectedForMerge.length})`; } else mb.classList.add('lt-hidden');

                // V8: Sequence meta (count + sum)
                const seqMeta = document.getElementById('lt-seq-meta');
                if (seqMeta) {
                    seqMeta.innerText = s.sequence.length
                        ? `${s.sequence.length} numbers · Σ ${Utils.formatNumber(rawSum * mult)}`
                        : 'empty';
                }

                const cont = document.getElementById('lt-sequence'); const pa = document.getElementById('lt-play-again-container');
                if(s.sequence.length===0 && s.roundCount>0) { cont.style.display='none'; pa.style.display='flex'; }
                else {
                    cont.style.display='flex'; pa.style.display='none'; cont.innerHTML='';
                    if(s.sequence.length===0) cont.innerHTML='<div style="color:#666;font-size:11px;width:100%;text-align:center;">⊘ Empty sequence — generate a new one or paste</div>';
                    else s.sequence.forEach((item,idx) => {
                        const el=document.createElement('div'); el.className='lt-seq-card';
                        if(!this.mergeMode && (idx===0||idx===s.sequence.length-1)) el.classList.add('edge');
                        if(this.mergeMode && this.selectedForMerge.includes(idx)) el.classList.add('selected');

                        // V6.29: Display SHORT number in sequence
                        el.innerText = Utils.formatNumber(item.value);

                        // V8.2: Delete (×) badge — remove this number entirely (not in merge mode)
                        if (!this.mergeMode) {
                            const del = document.createElement('span');
                            del.className = 'lt-seq-card-del';
                            del.textContent = '×';
                            del.title = 'Remove this number';
                            del.onclick = (e) => { e.stopPropagation(); engine.removeItem(idx); };
                            el.appendChild(del);
                        }

                        // CLICK HANDLER
                        if(!this.mergeMode) {
                             el.onclick=()=>{ const v=prompt("Split value:", item.value/2); if(v){ const n=parseFloat(v); if(n>0 && n<item.value) engine.splitItem(idx,n); } };
                        } else {
                             el.onclick=()=>{ if(this.selectedForMerge.includes(idx)) this.selectedForMerge=this.selectedForMerge.filter(i=>i!==idx); else this.selectedForMerge.push(idx); this.update(); };
                        }

                        // SMART DRAG & DROP LOGIC (V6.26)
                        if (!this.mergeMode) {
                            el.draggable = true;
                            el.addEventListener('dragstart', (e) => {
                                this.dragSrcIndex = idx;
                                e.dataTransfer.effectAllowed = 'move';
                                el.classList.add('dragging');
                            });
                            el.addEventListener('dragover', (e) => {
                                e.preventDefault();
                                e.dataTransfer.dropEffect = 'move';
                                const rect = el.getBoundingClientRect();
                                const mid = rect.left + (rect.width / 2);
                                el.classList.remove('drop-left', 'drop-right');
                                if (e.clientX < mid) {
                                    el.classList.add('drop-left');
                                } else {
                                    el.classList.add('drop-right');
                                }
                            });
                            el.addEventListener('dragleave', () => {
                                el.classList.remove('drop-left', 'drop-right');
                            });
                            el.addEventListener('dragend', () => {
                                el.classList.remove('dragging');
                                document.querySelectorAll('.lt-seq-card').forEach(b => b.classList.remove('drop-left', 'drop-right'));
                            });
                            el.addEventListener('drop', (e) => {
                                e.stopPropagation();
                                el.classList.remove('drop-left', 'drop-right');
                                const rect = el.getBoundingClientRect();
                                const mid = rect.left + (rect.width / 2);
                                let targetIdx = idx;
                                if (e.clientX >= mid) {
                                    targetIdx++;
                                }
                                if (this.dragSrcIndex !== null && this.dragSrcIndex !== targetIdx) {
                                    engine.reorderSequence(this.dragSrcIndex, targetIdx);
                                }
                                return false;
                            });
                        }
                        cont.appendChild(el);
                    });
                    // V8.2: Add (+) card — append a new number to the sequence
                    if (!this.mergeMode) {
                        const addCard = document.createElement('div');
                        addCard.className = 'lt-seq-card lt-seq-add';
                        addCard.textContent = '+';
                        addCard.title = 'Add a number';
                        addCard.onclick = () => {
                            const v = prompt('Add number (value):', '');
                            if (v !== null) { const n = parseFloat(v); if (n > 0) engine.addItem(n); }
                        };
                        cont.appendChild(addCard);
                    }
                }
                if(!document.getElementById('lt-history-panel').classList.contains('lt-hidden')) this.renderHistory();
                // V8.4: Always keep the fixed right-side history panel up to date
                this.renderHistoryInto(document.getElementById('lt-hist-side-list'));
                this.updateExternalButtons();
            } catch(e) { console.error("Update Err", e); }
        }
        // V8.3: Mini-games as floating, draggable popups over the Torn window
        toggleGamePopup(type) {
            const popupId = type === 'flappy' ? 'lt-popup-flappy' : 'lt-popup-snake';
            const gameKey = type === 'flappy' ? '_flappyGame' : '_snakeGame';
            const existing = document.getElementById(popupId);
            if (existing) {
                if (this[gameKey]) { this[gameKey].stop(); this[gameKey] = null; }
                existing.remove();
                return;
            }
            const W = 290, H = type === 'flappy' ? 370 : 290;
            const popup = document.createElement('div');
            popup.className = 'lt-popup';
            popup.id = popupId;
            popup.innerHTML = `
                <div class="lt-popup-header">
                    <span class="lt-popup-title">${type === 'flappy' ? '🐦 Flappy Bird' : '🐍 Snake'}</span>
                    <span class="lt-popup-close" title="Close">✕</span>
                </div>
                <div class="lt-popup-body"><canvas width="${W}" height="${H}"></canvas></div>`;
            document.body.appendChild(popup);
            // Insert centered near the top
            popup.style.left = `${Math.max(10, (window.innerWidth - popup.offsetWidth) / 2)}px`;
            popup.style.top  = `${Math.max(10, window.innerHeight * 0.12)}px`;

            const close = () => {
                if (this[gameKey]) { this[gameKey].stop(); this[gameKey] = null; }
                popup.remove();
            };
            popup.querySelector('.lt-popup-close').onclick = close;
            this.makeDraggable(popup, popup.querySelector('.lt-popup-header'));

            const canvas = popup.querySelector('canvas');
            if (type === 'flappy') {
                this._flappyGame = new FlappyGame(canvas);
                this._flappyGame.start();
                canvas.onclick = () => this._flappyGame && this._flappyGame.flap();
            } else {
                this._snakeGame = new SnakeGame(canvas);
                this._snakeGame.start();
            }
        }

        makeDraggable(el, handle) {
            let ox = 0, oy = 0, dragging = false;
            handle.addEventListener('mousedown', (e) => {
                dragging = true;
                ox = e.clientX - el.offsetLeft;
                oy = e.clientY - el.offsetTop;
                e.preventDefault();
            });
            document.addEventListener('mousemove', (e) => {
                if (!dragging) return;
                const nx = Math.max(0, Math.min(window.innerWidth  - el.offsetWidth,  e.clientX - ox));
                const ny = Math.max(0, Math.min(window.innerHeight - el.offsetHeight, e.clientY - oy));
                el.style.left = `${nx}px`;
                el.style.top  = `${ny}px`;
            });
            document.addEventListener('mouseup', () => { dragging = false; });
        }

        updateStatus(text, color) {
            const el = document.getElementById('lt-status-bar'); if(el) { el.innerText = text; el.style.color = color; }
            // V8: Hero mini status (short label)
            const mini = document.getElementById('lt-status-mini');
            if (mini) {
                const short = text.toUpperCase().replace(/\s*\(.*\)/, '').trim();
                mini.innerText = short.length > 20 ? short.slice(0, 18) + '…' : short;
                mini.style.color = color || '#a78bfa';
            }
        }
        updateExternalButtons() {
            const amt = engine.getEffectiveBet();
            const setLabel = (b, t) => { if (b.tagName === 'INPUT') b.value = t; else b.innerText = t; };
            const b1 = document.getElementById('lt-trade-btn-1'); const b2 = document.getElementById('lt-trade-btn-2');
            if(b1 && b2) {
                if(amt>0) { setLabel(b1, `- ${Utils.formatNumber(amt)}`); setLabel(b2, `-${Utils.formatNumber(amt+20)} +20`); b1.disabled=false; b2.disabled=false; }
                else { setLabel(b1, "Done"); setLabel(b2, "Done"); b1.disabled=true; b2.disabled=true; }
            }
            const fb = document.getElementById('lt-fill-btn'); if(fb) fb.innerText=amt>0?`📋 ${Utils.formatNumber(amt)}`:"✅";
        }
    }

    // --- INTEGRATION (V6.26 HYBRID & ASYNC SCANNER) ---
    class TornIntegration {
        constructor() {
            this.isRR = false; this.myId = this.getMyId();
            this.isArmed = false; this.lastManualBet = null;
            this.isProcessing = false;
            this.lockResults = false;

            // V7.01 FIX: Duplicate detection
            this.lastProcessedWinner = null;
            this.lastProcessedTime = 0;

            // V7.04 FIX: Text deduplication for DOM messages
            this.lastProcessedDomText = null;
            this.lastProcessedDomTime = 0;
            this.pendingDomProcess = false;

            // Make available globally so DevTool can trigger it
            window.LabTrackIntegration = this;

            this.installNetworkHooks();
            this.installAudioHook();


            console.log(`[LabTrack] Init - User ID: ${this.myId}`);
            auditLog.record('NET', 'INIT', `UserID: ${this.myId}`);
        }
        getMyId() { const m = document.cookie.match(/uid=(\d+)/); return m ? parseInt(m[1], 10) : null; }

        // V8.2: Detect RR sounds so our revolver can animate in sync with Torn's sound.
        // Torn plays sounds via the Web Audio API (AudioBufferSourceNode), NOT <audio>,
        // so we hook AudioBufferSourceNode.start. We also tag decoded buffers with their
        // sound type (bang/blank/join) where possible. Plus an <audio> fallback.
        installAudioHook() {
            if (window._ltAudioHooked) return;
            window._ltAudioHooked = true;
            const fire = (t) => { try { window.dispatchEvent(new CustomEvent('lt-rr-sound', { detail: t })); } catch(e) {} };

            // <audio> fallback (with type from src)
            const origPlay = HTMLMediaElement.prototype.play;
            HTMLMediaElement.prototype.play = function() {
                try {
                    const m = (this.src || this.currentSrc || '').match(/russianRoulette\/audio\/(bang|blank|join)/);
                    if (m) fire(m[1]);
                } catch(e) {}
                return origPlay.apply(this, arguments);
            };

            // Web Audio: tag decoded buffers with their type, fire on playback start
            try {
                const AC = window.AudioContext || window.webkitAudioContext;
                if (AC && AC.prototype.decodeAudioData) {
                    const origDecode = AC.prototype.decodeAudioData;
                    AC.prototype.decodeAudioData = function(buf, success, error) {
                        const type = buf && buf._ltSound;
                        const tag = (ab) => { if (type && ab) { try { ab._ltSound = type; } catch(e) {} } return ab; };
                        if (typeof success === 'function') return origDecode.call(this, buf, (ab) => success(tag(ab)), error);
                        const p = origDecode.call(this, buf);
                        return (p && p.then) ? p.then(tag) : p;
                    };
                }
                const ABSN = window.AudioBufferSourceNode;
                if (ABSN && ABSN.prototype.start) {
                    const origStart = ABSN.prototype.start;
                    ABSN.prototype.start = function() {
                        try { fire((this.buffer && this.buffer._ltSound) || 'shot'); } catch(e) {}
                        return origStart.apply(this, arguments);
                    };
                }
            } catch(e) {}
        }
        setUI(ui) { this.ui = ui; }

        start() {
            setInterval(() => {
                const href = window.location.href;
                this.checkRoute();
                this.scanPotMoney();

                if (href.includes('sid=russianRoulette') && !href.includes('/game')) {
                    if (!this.isArmed) { this.isArmed = true; }
                }
                if(href.includes('russianRoulette')) { this.isRR=true; this.initDOMButtons(); this.embedGameWindow(); } else this.isRR=false;
                if(href.includes('trade.php')) this.initTrade();
                this.applyWideLayout(); // RR-only: widen the centered container for more room
            }, CONFIG.POLL_MS);
        }

        // V8.4: On the RR page, widen Torn's centered layout container so the sidebar sits
        // flush-left and the content (RR + our dashboard) gets more horizontal room.
        // Auto-detects the constraining/centered ancestors. Reversible when leaving RR.
        applyWideLayout() {
            const onRR = window.location.href.includes('sid=russianRoulette');
            if (!onRR) { this._restoreWideLayout(); return; }
            if (this._wideApplied) return;
            const sb = document.getElementById('sidebar');
            if (!sb) return;
            const touched = [];
            let el = sb.parentElement;
            while (el && el !== document.body && el !== document.documentElement) {
                const cs = getComputedStyle(el);
                const mw = parseFloat(cs.maxWidth);
                const ml = parseFloat(cs.marginLeft) || 0;
                const constrained = !isNaN(mw) && mw < window.innerWidth - 50;
                const bigLeftMargin = ml > 20; // e.g. Torn's .container with margin-left:194px
                if ((constrained || bigLeftMargin) && el.offsetWidth > 500) {
                    touched.push({ el, mw: el.style.maxWidth, ml: el.style.marginLeft, mr: el.style.marginRight, w: el.style.width });
                    el.style.setProperty('max-width', 'none', 'important');
                    el.style.setProperty('margin-left', '0', 'important');
                    el.style.setProperty('margin-right', '0', 'important');
                    el.style.setProperty('width', 'auto', 'important');
                }
                el = el.parentElement;
            }
            if (touched.length) {
                this._wideTouched = touched; this._wideApplied = true;
                document.body.classList.add('lt-rr-wide'); // reserve room for the history panel
                Logger.info('Layout', `Widened ${touched.length} container(s) for RR`);
            }
        }

        _restoreWideLayout() {
            if (!this._wideApplied) return;
            (this._wideTouched || []).forEach(t => {
                t.el.style.maxWidth = t.mw; t.el.style.marginLeft = t.ml;
                t.el.style.marginRight = t.mr; t.el.style.width = t.w;
            });
            document.body.classList.remove('lt-rr-wide');
            this._wideApplied = false; this._wideTouched = null;
        }

        // V8.2: Custom appContainer rebuild via GameUIController
        embedGameWindow() {
            if (!this.isRR) return;
            if (!this._gameUI) this._gameUI = new GameUIController();
            this._gameUI.update();
        }

        // V6.31: Smart Pot Money Scanner
        scanPotMoney() {
            // Only scan if we are not locked/processing and in RR
            if (this.lockResults || !this.isRR) return;

            // Try to find the element by TEXT content to be robust against class changes
            // Searching for "POT MONEY:" label
            try {
                const spans = document.querySelectorAll('span');
                let potLabel = null;
                for(let i=0; i<spans.length; i++) {
                    if(spans[i]?.innerText === "POT MONEY:") {
                        potLabel = spans[i];
                        break;
                    }
                }

                if (potLabel && potLabel.nextElementSibling) {
                    const valText = potLabel.nextElementSibling.innerText; // e.g. "$20"
                    const val = parseFloat(valText.replace(/[^0-9.]/g, ''));

                    if (!isNaN(val) && val > 0) {
                        // V6.32 FIX: If pendingBet is not set, use HALF of Pot.
                        if (engine.pendingBet === null) {
                             engine.setPendingBet(val / 2);
                        }
                    }
                }
            } catch(e) {}
        }

        checkRoute() {
             const hash = window.location.hash;
             if ((hash === '' || hash === '#/') && window.location.href.includes('sid=russianRoulette')) {
                 if (this.lockResults) {
                     this.lockResults = false;
                     // V7.06 FIX: Reset all duplicate detection on lobby return
                     this.lastProcessedWinner = null;
                     this.lastProcessedTime = 0;
                     this.lastSeenWinnerId = null;
                     this.lastSeenWinnerTime = 0;
                     this._winnerLock = null;
                     this.lastProcessedDomText = null;
                     this.lastProcessedDomTime = 0;
                     this.pendingDomProcess = false;
                     auditLog.record('NET', 'RESET', 'Lobby detected. Unlocked.');
                     if (this.ui) this.ui.updateStatus("READY", "#4ade80");
                 }
             } else if (hash.includes('/game')) {
                 if (this.lockResults && this.ui) {
                     this.ui.updateStatus("LOCKED (RETURN TO LOBBY)", "#f87171");
                 } else if (this.ui) {
                     this.ui.updateStatus("GAME IN PROGRESS", "#fbbf24");
                 }
             }
        }



        triggerResult(outcome, stake) {
            // V7.01 FIX: Check if already being processed
            // Locks should have been set by caller (DOM or Network handler)
            // If somehow we get here without locks, something is wrong
            if (!this.lockResults || !this.isProcessing) {
                auditLog.record('NET', 'ERROR', 'triggerResult called without locks set!');
                this.isProcessing = true;
                this.lockResults = true;
            }

            // Get stake from engine if missing
            let finalStake = stake;
            if (!finalStake) finalStake = engine.pendingBet || engine.getEffectiveBet();

            if (outcome === 'win') {
                engine.processWin();
                if(this.ui) {
                    this.ui.flash('win');
                    Utils.showToast(`WIN (Verified): ${Utils.formatNumber(finalStake)}`);
                }
                auditLog.record('NET', 'RESULT', 'WIN (Hybrid) processed. Locked.');
            } else if (outcome === 'loss') {
                engine.processLoss(finalStake);
                if(this.ui) {
                    this.ui.flash('loss');
                    Utils.showToast(`LOSS (Verified): ${Utils.formatNumber(finalStake)}`);
                }
                auditLog.record('NET', 'RESULT', 'LOSS (Hybrid) processed. Locked.');
            }

            this.isArmed = false;
            this.lastManualBet = null;
            if(this.ui) this.ui.update();

            setTimeout(() => { this.isProcessing = false; }, 500);
        }

        installNetworkHooks() {
            const win = (typeof unsafeWindow !== 'undefined' ? unsafeWindow : window); const self = this;
            const origSend = win.XMLHttpRequest.prototype.send;
            win.XMLHttpRequest.prototype.send = function(body) {
                // V6.30: Enhanced hook for debugging custom bets
                if(typeof body==='string' && (body.includes('sid=russianRouletteData')||window.location.href.includes('russianRoulette'))) {
                    console.log("[LabTrack] XHR POST:", body); // Debug log
                    auditLog.record('NET', 'POST', body.substring(0, 50));

                    const m = body.match(/amount=(\d+)/);
                    if(m) engine.setPendingBet(parseInt(m[1]));
                }
                this.addEventListener('load', function() {
                    if((this.responseType===''||this.responseType==='text') && this.responseText) {
                        if(self.isRR) self.scanTextForResults(this.responseText);
                    }
                });
                return origSend.apply(this, arguments);
            };
            const origFetch = win.fetch;
            win.fetch = async function(...args) {
                const url = args[0]?args[0].toString():"";
                if(self.isRR && url.includes('russianRouletteData') && args[1]?.body) {
                    const bodyStr = args[1].body.toString();
                    console.log("[LabTrack] Fetch POST:", bodyStr);
                    auditLog.record('NET', 'FETCH', bodyStr.substring(0, 50));

                    const m = bodyStr.match(/amount=(\d+)/);
                    if(m) engine.setPendingBet(parseInt(m[1]));
                }
                const res = await origFetch.apply(this, args);
                const c = res.clone(); c.text().then(t => {
                    if(url.includes('russianRouletteData')) self.scanTextForResults(t);
                }).catch(()=>{});
                return res;
            };
            const OrigWS = win.WebSocket;
            win.WebSocket = function(...args) {
                const ws = new OrigWS(...args);
                ws.addEventListener('message', e => { if(typeof e.data==='string') self.scanTextForResults(e.data); });
                return ws;
            };
            win.WebSocket.prototype = OrigWS.prototype; Object.assign(win.WebSocket, OrigWS);
        }
        scanTextForResults(text) {
            if (!engine.state.autoDetect) return;
            if (this.lockResults || this.isProcessing) return;

            // V7.05 FIX: Quick pre-check - does this text even contain a winner?
            if(!text || text.length<10 || text.indexOf('winner')===-1) return;

            // V7.05 FIX: Extract winnerId FIRST, then do atomic check
            const winnerMatch = text.match(/"winner"\s*:\s*"?(\d+)"?/);
            if(!winnerMatch) return;
            const winnerId = parseInt(winnerMatch[1], 10);
            if(winnerId===0) return;

            // V7.06 FIX: TRUE ATOMIC CHECK - Set FIRST, then check previous value
            // This prevents TOCTOU race condition where two calls both pass the check
            // before either sets the lock
            const prevLock = this._winnerLock;
            this._winnerLock = winnerId;  // SET IMMEDIATELY before any check
            if (prevLock === winnerId) {
                return; // Another call is already processing this exact winner
            }

            // Additional time-based check for safety (different game rounds with same winner)
            const now = Date.now();
            if (this.lastSeenWinnerId === winnerId && (now - this.lastSeenWinnerTime < 5000)) {
                this._winnerLock = null; // Release lock
                return;
            }
            this.lastSeenWinnerId = winnerId;
            this.lastSeenWinnerTime = now;

            if(!this.myId) this.myId = this.getMyId();
            if(!this.myId) { this._winnerLock = null; return; }

            try {
                auditLog.record('NET', 'NET', `Winner found: ${winnerId}`);

                let outcome = 'none';
                if(winnerId === this.myId) {
                    outcome = 'win';
                } else {
                    const participationRegex = new RegExp(`"userID"\\s*:\\s*"?${this.myId}"?`);
                    const regexMatch = participationRegex.test(text);
                    const hasPendingBet = engine.pendingBet !== null;
                    const hasRecentManualBet = this.lastManualBet && (Date.now() - this.lastManualBet.time < 30000);

                    auditLog.record('NET', 'CHECK', `Regex: ${regexMatch}, Pending: ${hasPendingBet}, Manual: ${hasRecentManualBet}`);

                    if (regexMatch || hasPendingBet || hasRecentManualBet) {
                        outcome = 'loss';
                    }
                }

                if (outcome !== 'none') {
                    // V7.01 FIX: DOUBLE CHECK - locks might have been set by DOM already
                    if (this.lockResults || this.isProcessing) {
                        auditLog.record('NET', 'SKIP', 'Already locked by DOM detection');
                        return;
                    }

                    // V7.01 FIX: Set locks IMMEDIATELY before triggerResult
                    this.isProcessing = true;
                    this.lockResults = true;

                    // V7.01 FIX: Mark this winnerId as processed
                    this.lastProcessedWinner = winnerId;
                    this.lastProcessedTime = Date.now();

                    let val=0, isPot=false;
                    const mm = text.match(/"(pot_amount|betAmount|amount|money)"\s*:\s*"?(\d+(\.\d+)?)"?/);
                    if(mm) { val=parseFloat(mm[2]); if(mm[1]==='pot_amount') isPot=true; }
                    let netStake = isPot ? val/2 : val;

                    this.triggerResult(outcome, netStake);
                } else {
                     auditLog.record('NET', 'IGNORE', 'Spectator / Not Involved');
                     this._winnerLock = null; // Release lock for spectator
                }

            } catch(e) {
                console.error("[LabTrack] Scan Err", e);
                auditLog.record('NET', 'ERR', e.message);
                this.isProcessing = false;
                this._winnerLock = null; // Release lock on error
            }
        }
        initDOMButtons() {
            const inp = document.querySelector('input[aria-label="Money value"]');
            if(inp && !inp.hasAttribute('data-lt')) {
                inp.setAttribute('data-lt','1');

                // V7.02: Auto-detect Custom Bets
                const capture = (e) => {
                    // V8.x: Skip synthetic events from our own writeNativeBet (would create loop)
                    const syntheticMark = e.target._ltSyntheticBetWrite;
                    if (syntheticMark && Date.now() - syntheticMark < 200) return;

                    const v = parseFloat(e.target.value.replace(/[^0-9.]/g,''));
                    if(!isNaN(v) && v > 0) {
                        this.lastManualBet = { amount: v, time: Date.now() };

                        // V7.02: Auto-detect if bet differs from expected
                        const expectedBet = engine.getEffectiveBet();
                        const tolerance = 1; // Allow 1$ difference for rounding

                        if (Math.abs(v - expectedBet) > tolerance) {
                            // User entered a different bet - set as custom bet
                            Logger.info('CustomBet', `Auto-detected: ${v} (Expected: ${expectedBet})`);
                            engine.setCustomBet(v);
                            Utils.showToast(`Custom Bet: ${Utils.formatNumber(v)}`);
                            if(this.ui) this.ui.update();
                        }
                    }
                };
                inp.addEventListener('input', capture);
                inp.addEventListener('keyup', capture);
                inp.addEventListener('change', capture);
            }
            if(inp && !document.getElementById('lt-fill-btn')) {
                const btn = document.createElement("button"); btn.id="lt-fill-btn"; btn.innerText="📋";
                btn.style.cssText="margin-left:5px;background:#6d28d9;color:#fff;border:none;border-radius:4px;cursor:pointer;padding:0 10px;height:34px;font-weight:bold;";
                btn.onclick = (e) => { e.preventDefault(); const v=engine.getEffectiveBet(); if(v>0){ Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype,"value").set.call(inp,Math.floor(v)); inp.dispatchEvent(new Event('input',{bubbles:true})); } };
                if(inp.parentNode) inp.parentNode.appendChild(btn);
                if(this.ui) this.ui.update();
            }
        }
        initTrade() {
            const inp = document.querySelector(".user-id.input-money");
            if (!inp) return;

            const moneyGroup = inp.closest('.input-money-group');
            const li         = moneyGroup?.closest('li');
            const form       = inp.closest('form');
            if (!moneyGroup || !li || !form) return;

            // Bereits aufgebaut?
            if (document.getElementById('lt-trade-dashboard')) return;

            // ── Migration: clean up old layouts ──
            // If the Change button was stuck in an old container → move it back to the form end
            const oldActions = document.getElementById('lt-trade-actions');
            if (oldActions) {
                const oldSubmitWrap = oldActions.querySelector('.btn-wrap');
                if (oldSubmitWrap) form.appendChild(oldSubmitWrap);
                oldActions.remove();
            }
            document.getElementById('lt-trade-pnl')?.remove();
            moneyGroup.querySelector('#lt-max-minus20')?.remove();

            // Reference the native Max button (for −$20 logic)
            const maxSpan = inp.closest('.input-money-group')?.querySelector('.input-money-symbol');
            const nativeMaxBtn = maxSpan?.querySelector('.wai-btn');

            // React-friendly value setting
            const setReactValue = (newVal) => {
                const cur = parseInt(inp.value.replace(/[^0-9]/g, '')) || 0;
                const tracker = inp._valueTracker;
                if (tracker) tracker.setValue(String(cur));
                Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set.call(inp, String(newVal));
                inp.dispatchEvent(new Event('input', { bubbles: true }));
                inp.dispatchEvent(new Event('change', { bubbles: true }));
            };
            const subtractV = v => {
                const c = parseInt(inp.value.replace(/[^0-9]/g, '')) || 0;
                setReactValue(Math.max(0, c - v));
            };

            // ── "MAX − $20" button (with XHR wait via MutationObserver) ──
            const m20Btn = document.createElement('input');
            m20Btn.type = 'button';
            m20Btn.className = 'torn-btn';
            m20Btn.id = 'lt-max-minus20';
            m20Btn.value = 'MAX − $20';
            m20Btn.title = 'Fills max minus $20 — leaves $20 in your wallet';
            m20Btn.onclick = (e) => {
                e.preventDefault();
                if (!nativeMaxBtn || m20Btn._pending) return;
                m20Btn._pending = true;

                let applied = false;
                let observer = null;
                let fallbackTimer = null;

                const cleanup = () => {
                    if (observer) { observer.disconnect(); observer = null; }
                    if (fallbackTimer) { clearTimeout(fallbackTimer); fallbackTimer = null; }
                    m20Btn._pending = false;
                };

                const applySubtract = () => {
                    if (applied) return;
                    applied = true;
                    const fromAttr = parseInt(inp.getAttribute('data-money')) || 0;
                    const fromVal  = parseInt(inp.value.replace(/[^0-9]/g, '')) || 0;
                    const max = Math.max(fromAttr, fromVal);
                    if (max <= 20) { cleanup(); return; }
                    const target = max - 20;
                    setReactValue(target);
                    setTimeout(() => {
                        const verify = parseInt(inp.value.replace(/[^0-9]/g, '')) || 0;
                        if (verify !== target) setReactValue(target);
                        cleanup();
                    }, 200);
                };

                observer = new MutationObserver(applySubtract);
                observer.observe(inp, { attributes: true, attributeFilter: ['data-money'] });
                fallbackTimer = setTimeout(applySubtract, 2000);
                nativeMaxBtn.click();
            };

            // ── Bet buttons (subtract effective bet [+20]) ──
            const mkBetBtn = (id, getAmount) => {
                const b = document.createElement('input');
                b.type = 'button';
                b.className = 'torn-btn';
                b.id = id;
                b.value = 'Wait...';
                b.onclick = e => { e.preventDefault(); subtractV(getAmount()); };
                return b;
            };
            const betBtn1 = mkBetBtn('lt-trade-btn-1', () => engine.getEffectiveBet());
            const betBtn2 = mkBetBtn('lt-trade-btn-2', () => engine.getEffectiveBet() + 20);

            // ── LabTrack dashboard at the top of the <li> ──
            const dash = document.createElement('div');
            dash.id = 'lt-trade-dashboard';
            dash.style.cssText = 'margin-bottom:10px;padding:8px 10px;background:rgba(124,58,237,0.06);border-left:3px solid #7c3aed;border-radius:4px;display:flex;flex-direction:column;gap:8px;';

            const dashLabel = document.createElement('div');
            dashLabel.style.cssText = 'font-size:10px;color:#a78bfa;font-weight:bold;text-transform:uppercase;letter-spacing:0.6px;';
            dashLabel.innerText = '🎯 LabTrack';
            dash.appendChild(dashLabel);

            // ── Change submit button (left of MAX − $20) ──
            const changeBtn = document.createElement('input');
            changeBtn.type = 'submit';
            changeBtn.className = 'torn-btn';
            changeBtn.id = 'lt-change-btn';
            changeBtn.value = 'Change';
            changeBtn.title = 'Submit trade change';
            // No onclick needed — type="submit" inside the <form> triggers native submission

            const topRow = document.createElement('div');
            topRow.style.cssText = 'display:flex;gap:6px;flex-wrap:wrap;align-items:center;';
            topRow.appendChild(changeBtn);
            topRow.appendChild(m20Btn);
            dash.appendChild(topRow);

            const betRow = document.createElement('div');
            betRow.style.cssText = 'display:flex;gap:6px;flex-wrap:wrap;align-items:center;';
            betRow.appendChild(betBtn1);
            betRow.appendChild(betBtn2);
            dash.appendChild(betRow);

            // Insert at the very top of the <li> (before "Money:")
            li.insertBefore(dash, li.firstChild);

            if (this.ui) this.ui.updateExternalButtons();
        }
    }

    // =============================================================================
    // FLAPPY BIRD MINI-GAME
    // =============================================================================
    class FlappyGame {
        constructor(canvas) {
            this.canvas   = canvas;
            this.ctx      = canvas.getContext('2d');
            this.W        = canvas.width;
            this.H        = canvas.height;
            this.highScore = parseInt(localStorage.getItem('lt_flappy_hs') || '0');
            this.animId   = null;
            this.state    = 'idle';
            this._keyHandler = null;
            this._reset();
        }

        _reset() {
            this.bird     = { x: 65, y: this.H / 2, vy: 0, r: 11 };
            this.pipes    = [];
            this.score    = 0;
            this.frame    = 0;
            this.speed    = 2.2;
            this.gap      = 135;
            this.pipeW    = 44;
            this.interval = 95;
            this._lastTs  = undefined;
            this.paused   = false;
        }

        flap() {
            if (this.state === 'dead') this._reset();
            this.state = 'playing';
            this.paused = false;
            this.bird.vy = -7;
        }

        _update() {
            if (this.state !== 'playing' || this.paused) return;
            this.frame++;

            // Physics
            this.bird.vy += 0.4;
            this.bird.y  += this.bird.vy;

            // Spawn pipes
            if (this.frame % this.interval === 0) {
                const minY = 55, maxY = this.H - this.gap - 55;
                const gapY = minY + Math.random() * (maxY - minY);
                this.pipes.push({ x: this.W + 10, gapY, passed: false });
            }

            // Move & score pipes
            for (const p of this.pipes) {
                p.x -= this.speed;
                if (!p.passed && p.x + this.pipeW < this.bird.x) {
                    p.passed = true;
                    this.score++;
                    this.speed = Math.min(4.8, 2.2 + this.score * 0.06);
                }
            }
            this.pipes = this.pipes.filter(p => p.x + this.pipeW > -10);

            // Floor / ceiling
            if (this.bird.y + this.bird.r > this.H - 20 || this.bird.y - this.bird.r < 0) {
                this._die(); return;
            }

            // Pipe collision
            for (const p of this.pipes) {
                const inX = this.bird.x + this.bird.r > p.x && this.bird.x - this.bird.r < p.x + this.pipeW;
                if (inX) {
                    const inGap = this.bird.y - this.bird.r > p.gapY && this.bird.y + this.bird.r < p.gapY + this.gap;
                    if (!inGap) { this._die(); return; }
                }
            }
        }

        _die() {
            this.state = 'dead';
            if (this.score > this.highScore) {
                this.highScore = this.score;
                localStorage.setItem('lt_flappy_hs', String(this.highScore));
            }
        }

        _draw() {
            const { ctx, W, H, bird, pipes, score, highScore, state, gap, pipeW } = this;

            // Background
            const sky = ctx.createLinearGradient(0, 0, 0, H);
            sky.addColorStop(0, '#050810');
            sky.addColorStop(1, '#0f172a');
            ctx.fillStyle = sky;
            ctx.fillRect(0, 0, W, H);

            // Static stars
            ctx.fillStyle = 'rgba(255,255,255,0.45)';
            for (let i = 0; i < 22; i++) {
                ctx.fillRect((i * 137 + 17) % W, (i * 97 + 31) % (H - 40), 1, 1);
            }

            // Ground
            ctx.fillStyle = '#1e3a2f';
            ctx.fillRect(0, H - 20, W, 20);
            ctx.fillStyle = '#15803d';
            ctx.fillRect(0, H - 20, W, 3);

            // Pipes
            for (const p of pipes) {
                const g = ctx.createLinearGradient(p.x, 0, p.x + pipeW, 0);
                g.addColorStop(0, '#14532d');
                g.addColorStop(0.5, '#22c55e');
                g.addColorStop(1, '#15803d');
                ctx.fillStyle = g;
                // Top pipe body
                ctx.fillRect(p.x, 0, pipeW, p.gapY - 14);
                // Top cap
                ctx.fillStyle = '#16a34a';
                ctx.fillRect(p.x - 5, p.gapY - 14, pipeW + 10, 14);
                // Bottom body
                ctx.fillStyle = g;
                ctx.fillRect(p.x, p.gapY + gap + 14, pipeW, H - p.gapY - gap - 14 - 20);
                // Bottom cap
                ctx.fillStyle = '#16a34a';
                ctx.fillRect(p.x - 5, p.gapY + gap, pipeW + 10, 14);
            }

            // Bird
            const tilt = Math.min(Math.max(bird.vy * 4, -28), 55) * Math.PI / 180;
            ctx.save();
            ctx.translate(bird.x, bird.y);
            ctx.rotate(tilt);

            // Body
            const bodyG = ctx.createRadialGradient(-2, -2, 1, 0, 0, bird.r);
            bodyG.addColorStop(0, '#c084fc');
            bodyG.addColorStop(1, '#7c3aed');
            ctx.beginPath();
            ctx.ellipse(0, 0, bird.r, bird.r * 0.88, 0, 0, Math.PI * 2);
            ctx.fillStyle = bodyG;
            ctx.fill();

            // Wing
            ctx.beginPath();
            ctx.ellipse(-1, 4, bird.r * 0.6, bird.r * 0.32, -0.25, 0, Math.PI * 2);
            ctx.fillStyle = '#6d28d9';
            ctx.fill();

            // Eye white
            ctx.beginPath();
            ctx.arc(bird.r * 0.38, -bird.r * 0.22, 3.5, 0, Math.PI * 2);
            ctx.fillStyle = '#fff';
            ctx.fill();
            // Pupil
            ctx.beginPath();
            ctx.arc(bird.r * 0.38 + 1, -bird.r * 0.22, 1.8, 0, Math.PI * 2);
            ctx.fillStyle = '#1e293b';
            ctx.fill();

            // Beak
            ctx.beginPath();
            ctx.moveTo(bird.r * 0.72, 0);
            ctx.lineTo(bird.r * 1.25, 2.5);
            ctx.lineTo(bird.r * 0.72, 5);
            ctx.closePath();
            ctx.fillStyle = '#f97316';
            ctx.fill();

            ctx.restore();

            // HUD
            ctx.textAlign = 'center';
            ctx.shadowColor = 'rgba(0,0,0,0.9)';
            ctx.shadowBlur = 6;
            ctx.fillStyle = '#fff';
            ctx.font = 'bold 24px monospace';
            ctx.fillText(score, W / 2, 38);
            ctx.font = '10px monospace';
            ctx.fillStyle = '#64748b';
            ctx.fillText(`BEST: ${highScore}`, W / 2, 54);
            ctx.shadowBlur = 0;

            // Idle overlay
            if (state === 'idle') {
                ctx.fillStyle = 'rgba(0,0,0,0.55)';
                ctx.fillRect(0, 0, W, H);
                ctx.fillStyle = '#a78bfa';
                ctx.font = 'bold 20px monospace';
                ctx.fillText('FLAPPY BIRD', W / 2, H / 2 - 22);
                ctx.fillStyle = '#94a3b8';
                ctx.font = '12px monospace';
                ctx.fillText('SPACE  or  CLICK', W / 2, H / 2 + 8);
                if (highScore > 0) {
                    ctx.fillStyle = '#fbbf24';
                    ctx.font = '11px monospace';
                    ctx.fillText(`🏆 Best: ${highScore}`, W / 2, H / 2 + 30);
                }
            }

            // Dead overlay
            if (state === 'dead') {
                ctx.fillStyle = 'rgba(0,0,0,0.65)';
                ctx.fillRect(0, 0, W, H);
                ctx.fillStyle = '#ef4444';
                ctx.font = 'bold 20px monospace';
                ctx.fillText('GAME OVER', W / 2, H / 2 - 34);
                ctx.fillStyle = '#fff';
                ctx.font = 'bold 16px monospace';
                ctx.fillText(`Score: ${score}`, W / 2, H / 2 - 8);
                if (score > 0 && score >= highScore) {
                    ctx.fillStyle = '#fbbf24';
                    ctx.font = 'bold 13px monospace';
                    ctx.fillText('🏆 NEW RECORD!', W / 2, H / 2 + 14);
                }
                ctx.fillStyle = '#a78bfa';
                ctx.font = '12px monospace';
                ctx.fillText('SPACE  or  CLICK', W / 2, H / 2 + 38);
            }

            // Paused overlay
            if (this.paused && state === 'playing') {
                ctx.fillStyle = 'rgba(0,0,0,0.7)';
                ctx.fillRect(0, 0, W, H);
                ctx.textAlign = 'center';
                ctx.fillStyle = '#a78bfa';
                ctx.font = 'bold 22px monospace';
                ctx.fillText('⏸ PAUSED', W / 2, H / 2 - 8);
                ctx.fillStyle = '#94a3b8';
                ctx.font = '12px monospace';
                ctx.fillText('Press SPACE to resume', W / 2, H / 2 + 18);
            }

            ctx.textAlign = 'left';
        }

        _loop(ts) {
            // Cap at 60fps regardless of monitor refresh rate
            if (this._lastTs === undefined) this._lastTs = ts;
            const dt = ts - this._lastTs;
            if (dt >= 16) {
                this._lastTs = ts;
                this._update();
                this._draw();
            }
            this.animId = requestAnimationFrame((t) => this._loop(t));
        }

        start() {
            if (this.animId) cancelAnimationFrame(this.animId);
            this._lastTs = undefined;
            this.state = 'idle';
            this._reset();
            this.animId = requestAnimationFrame((t) => this._loop(t));

            this._keyHandler = (e) => {
                if (e.code !== 'Space') return;
                if (!document.getElementById('lt-popup-flappy')) return; // popup closed → let Torn handle Space
                // SAFETY: stop Space from reaching Torn's RR handlers (could trigger shoot/leave → mug risk)
                e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation();
                if (this.paused) { this.paused = false; this._lastTs = undefined; return; } // resume, no flap
                this.flap();
            };
            document.addEventListener('keydown', this._keyHandler, true); // capture: runs before Torn

            // Pause when tabbing out (window blur / tab switch)
            this._pauseHandler = () => { if (this.state === 'playing') this.paused = true; };
            this._visHandler   = () => { if (document.hidden && this.state === 'playing') this.paused = true; };
            window.addEventListener('blur', this._pauseHandler);
            document.addEventListener('visibilitychange', this._visHandler);
        }

        stop() {
            if (this.animId) { cancelAnimationFrame(this.animId); this.animId = null; }
            if (this._keyHandler) { document.removeEventListener('keydown', this._keyHandler, true); this._keyHandler = null; }
            if (this._pauseHandler) { window.removeEventListener('blur', this._pauseHandler); this._pauseHandler = null; }
            if (this._visHandler) { document.removeEventListener('visibilitychange', this._visHandler); this._visHandler = null; }
        }
    }

    // =============================================================================
    // SNAKE MINI-GAME
    // =============================================================================
    class SnakeGame {
        constructor(canvas) {
            this.canvas    = canvas;
            this.ctx       = canvas.getContext('2d');
            this.W         = canvas.width;   // 290
            this.H         = canvas.height;  // 290
            this.cell      = 10;
            this.cols      = this.W / this.cell; // 29
            this.rows      = this.H / this.cell; // 29
            this.highScore = parseInt(localStorage.getItem('lt_snake_hs') || '0');
            this.animId    = null;
            this.state     = 'idle';
            this._keyHandler = null;
            this._reset();
        }

        _reset() {
            const cx = Math.floor(this.cols / 2);
            const cy = Math.floor(this.rows / 2);
            this.snake    = [{ x: cx, y: cy }, { x: cx-1, y: cy }, { x: cx-2, y: cy }];
            this.dir      = { x: 1, y: 0 };
            this.nextDir  = { x: 1, y: 0 };
            this.score    = 0;
            this.speed    = 120; // ms per tick
            this._lastTick = undefined;
            this.paused   = false;
            this._spawnFood();
        }

        _spawnFood() {
            let pos;
            do {
                pos = { x: Math.floor(Math.random() * this.cols), y: Math.floor(Math.random() * this.rows) };
            } while (this.snake.some(s => s.x === pos.x && s.y === pos.y));
            this.food = pos;
        }

        _tick() {
            this.dir = { ...this.nextDir };
            const head = { x: this.snake[0].x + this.dir.x, y: this.snake[0].y + this.dir.y };

            // Wall collision
            if (head.x < 0 || head.x >= this.cols || head.y < 0 || head.y >= this.rows) { this._die(); return; }
            // Self collision
            if (this.snake.some(s => s.x === head.x && s.y === head.y)) { this._die(); return; }

            this.snake.unshift(head);
            if (head.x === this.food.x && head.y === this.food.y) {
                this.score++;
                this.speed = Math.max(60, 120 - Math.floor(this.score / 5) * 10);
                this._spawnFood();
            } else {
                this.snake.pop();
            }
        }

        _die() {
            this.state = 'dead';
            if (this.score > this.highScore) {
                this.highScore = this.score;
                localStorage.setItem('lt_snake_hs', String(this.highScore));
            }
        }

        _draw() {
            const { ctx, W, H, cell, snake, food, score, highScore, state } = this;

            // Background
            ctx.fillStyle = '#080d1a';
            ctx.fillRect(0, 0, W, H);

            // Subtle grid
            ctx.strokeStyle = 'rgba(255,255,255,0.04)';
            ctx.lineWidth = 0.5;
            for (let x = 0; x <= this.cols; x++) { ctx.beginPath(); ctx.moveTo(x*cell,0); ctx.lineTo(x*cell,H); ctx.stroke(); }
            for (let y = 0; y <= this.rows; y++) { ctx.beginPath(); ctx.moveTo(0,y*cell); ctx.lineTo(W,y*cell); ctx.stroke(); }

            // Food
            const fx = food.x*cell + cell/2, fy = food.y*cell + cell/2;
            ctx.beginPath(); ctx.arc(fx, fy, cell*0.42, 0, Math.PI*2); ctx.fillStyle = '#ef4444'; ctx.fill();
            ctx.beginPath(); ctx.arc(fx-1, fy-1, cell*0.18, 0, Math.PI*2); ctx.fillStyle = '#fca5a5'; ctx.fill();

            // Snake body (head bright purple → tail dark)
            const len = snake.length;
            snake.forEach((seg, i) => {
                const t = i / Math.max(len - 1, 1);
                const r = Math.round(192 - t * 130);
                const g = Math.round(132 - t * 103);
                const b = Math.round(252 - t * 104);
                ctx.fillStyle = `rgb(${r},${g},${b})`;
                ctx.fillRect(seg.x*cell + 1, seg.y*cell + 1, cell-2, cell-2);
            });

            // Head eyes
            if (snake.length > 0) {
                const { x: dx, y: dy } = this.dir;
                const hx = snake[0].x*cell + cell/2, hy = snake[0].y*cell + cell/2;
                ctx.fillStyle = '#fff';
                ctx.beginPath(); ctx.arc(hx + dy*2.5 + dx*2, hy - dx*2.5 + dy*2, 1.5, 0, Math.PI*2); ctx.fill();
                ctx.beginPath(); ctx.arc(hx - dy*2.5 + dx*2, hy + dx*2.5 + dy*2, 1.5, 0, Math.PI*2); ctx.fill();
            }

            // HUD bar
            ctx.fillStyle = 'rgba(0,0,0,0.6)';
            ctx.fillRect(0, 0, W, 20);
            ctx.textAlign = 'center';
            ctx.fillStyle = '#e2e8f0';
            ctx.font = 'bold 11px monospace';
            ctx.fillText(`Score: ${score}`, W/2, 13);
            ctx.textAlign = 'right';
            ctx.fillStyle = '#64748b';
            ctx.font = '9px monospace';
            ctx.fillText(`BEST: ${highScore}`, W - 6, 13);
            ctx.textAlign = 'left';
            ctx.fillStyle = '#64748b';
            ctx.fillText(`SPD: ${Math.round((120 - this.speed) / 10) + 1}`, 6, 13);

            // Idle overlay
            if (state === 'idle') {
                ctx.fillStyle = 'rgba(0,0,0,0.6)';
                ctx.fillRect(0, 0, W, H);
                ctx.textAlign = 'center';
                ctx.fillStyle = '#4ade80';
                ctx.font = 'bold 22px monospace';
                ctx.fillText('SNAKE', W/2, H/2 - 24);
                ctx.fillStyle = '#94a3b8';
                ctx.font = '12px monospace';
                ctx.fillText('Arrow keys / WASD', W/2, H/2 + 4);
                ctx.fillText('Press ENTER to start', W/2, H/2 + 22);
                if (highScore > 0) { ctx.fillStyle = '#fbbf24'; ctx.font = '11px monospace'; ctx.fillText(`🏆 Best: ${highScore}`, W/2, H/2 + 46); }
            }

            // Dead overlay
            if (state === 'dead') {
                ctx.fillStyle = 'rgba(0,0,0,0.65)';
                ctx.fillRect(0, 0, W, H);
                ctx.textAlign = 'center';
                ctx.fillStyle = '#ef4444';
                ctx.font = 'bold 20px monospace';
                ctx.fillText('GAME OVER', W/2, H/2 - 34);
                ctx.fillStyle = '#fff';
                ctx.font = 'bold 16px monospace';
                ctx.fillText(`Score: ${score}`, W/2, H/2 - 8);
                if (score > 0 && score >= highScore) { ctx.fillStyle = '#fbbf24'; ctx.font = 'bold 13px monospace'; ctx.fillText('🏆 NEW RECORD!', W/2, H/2 + 14); }
                ctx.fillStyle = '#a78bfa';
                ctx.font = '12px monospace';
                ctx.fillText('Press ENTER to restart', W/2, H/2 + 38);
            }

            // Paused overlay
            if (this.paused && state === 'playing') {
                ctx.fillStyle = 'rgba(0,0,0,0.7)';
                ctx.fillRect(0, 0, W, H);
                ctx.textAlign = 'center';
                ctx.fillStyle = '#a78bfa';
                ctx.font = 'bold 20px monospace';
                ctx.fillText('⏸ PAUSED', W/2, H/2 - 8);
                ctx.fillStyle = '#94a3b8';
                ctx.font = '12px monospace';
                ctx.fillText('Press SPACE to resume', W/2, H/2 + 18);
            }

            ctx.textAlign = 'left';
        }

        _loop(ts) {
            this._draw();
            if (this.state === 'playing' && !this.paused) {
                if (this._lastTick === undefined) this._lastTick = ts;
                if (ts - this._lastTick >= this.speed) {
                    this._lastTick = ts;
                    this._tick();
                }
            }
            this.animId = requestAnimationFrame((t) => this._loop(t));
        }

        start() {
            if (this.animId) cancelAnimationFrame(this.animId);
            this._reset();
            this.state = 'idle';
            this.animId = requestAnimationFrame((t) => this._loop(t));

            this._keyHandler = (e) => {
                const panel = document.getElementById('lt-popup-snake');
                if (!panel) return;
                const dirs = {
                    ArrowUp:    { x: 0, y: -1 }, KeyW: { x: 0, y: -1 },
                    ArrowDown:  { x: 0, y:  1 }, KeyS: { x: 0, y:  1 },
                    ArrowLeft:  { x:-1, y:  0 }, KeyA: { x:-1, y:  0 },
                    ArrowRight: { x: 1, y:  0 }, KeyD: { x: 1, y:  0 },
                };
                if (e.code === 'Enter' || e.code === 'Space') {
                    // SAFETY: block from Torn's RR handlers (mug risk)
                    e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation();
                    if (this.paused) { this.paused = false; this._lastTick = undefined; return; } // resume
                    if (this.state === 'idle' || this.state === 'dead') { this._reset(); this.state = 'playing'; }
                    return;
                }
                if (!dirs[e.code]) return;
                e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation();
                const d = dirs[e.code];
                if (d.x !== -this.dir.x || d.y !== -this.dir.y) this.nextDir = d;
            };
            document.addEventListener('keydown', this._keyHandler, true); // capture: runs before Torn

            // Pause when tabbing out (window blur / tab switch)
            this._pauseHandler = () => { if (this.state === 'playing') this.paused = true; };
            this._visHandler   = () => { if (document.hidden && this.state === 'playing') this.paused = true; };
            window.addEventListener('blur', this._pauseHandler);
            document.addEventListener('visibilitychange', this._visHandler);
        }

        stop() {
            if (this.animId) { cancelAnimationFrame(this.animId); this.animId = null; }
            if (this._keyHandler) { document.removeEventListener('keydown', this._keyHandler, true); this._keyHandler = null; }
            if (this._pauseHandler) { window.removeEventListener('blur', this._pauseHandler); this._pauseHandler = null; }
            if (this._visHandler) { document.removeEventListener('visibilitychange', this._visHandler); this._visHandler = null; }
        }
    }


    // =============================================================================
    // V8.2 — CUSTOM APPCONTAINER REBUILD (with confirm-step handling)
    // Hides Torn's appContainer off-screen, renders our own UI, syncs state from the
    // hidden DOM, forwards user-initiated clicks to native buttons. States: lobby,
    // confirm (game create/join confirmation), game.
    // =============================================================================
    class GameUIController {
        constructor() {
            this.hiddenApp = null;
            this.observer = null;
            this.state = null;
            this._shellBuilt = false;
            this._lastBetSet = null;
            this._revRot = 0;       // revolver cylinder rotation
            this._deadXid = null;   // XID of the player marked dead (red cross)
            // Quick Create: persist across sessions; default OFF (safer).
            this._quickCreate = localStorage.getItem('lt_quick_create') === '1';
            // Listen for RR sound events (dispatched by the audio hook) → animate revolver
            window.addEventListener('lt-rr-sound', (e) => this.onGameSound(e.detail));
        }

        toggleQuickCreate() {
            this._quickCreate = !this._quickCreate;
            localStorage.setItem('lt_quick_create', this._quickCreate ? '1' : '0');
            const btn = document.getElementById('lt-quick-create');
            if (btn) {
                btn.classList.toggle('on', this._quickCreate);
                btn.innerHTML = `⚡ Quick Create: <b>${this._quickCreate ? 'ON' : 'OFF'}</b>`;
            }
            Utils.showToast(`Quick Create ${this._quickCreate ? 'enabled' : 'disabled'}`);
        }

        update() {
            const app = document.querySelector('div[class^="appContainer"]');
            if (!app) return;
            const slot = document.getElementById('lt-game-frame-slot');
            const frame = document.getElementById('lt-game-frame');
            if (!slot || !frame) return;

            if (!this._shellBuilt) {
                try { this.renderShell(slot); this._shellBuilt = true; }
                catch (e) { Logger.error('GameUI', `Shell render failed: ${e.message}`); return; }
            }
            if (this.hiddenApp !== app) { this.hiddenApp = app; this.startObserver(); }
            frame.style.display = 'block';
            this.sync();
        }

        renderShell(slot) {
            slot.innerHTML = `
                <div id="lt-app">
                    <div class="lt-app-header">
                        <div class="lt-app-title">Russian Roulette</div>
                        <div class="lt-app-nav" id="lt-app-nav"></div>
                    </div>
                    <div id="lt-app-body"></div>
                </div>`;
        }

        sync() {
            // Re-discover appContainer if stale (Torn replaces it on game start)
            if (!this.hiddenApp || !document.contains(this.hiddenApp)) {
                const app = document.querySelector('div[class^="appContainer"]');
                if (app) { this.hiddenApp = app; this.startObserver(); Logger.info('GameUI', 'Re-discovered appContainer'); }
            }
            if (!this.hiddenApp) return;
            this.syncNav();

            const hasConfirm = !!this.hiddenApp.querySelector('[class*="confirmWrap"]');
            const hasTimer   = !!this.hiddenApp.querySelector('[class*="timer"]');
            const hasCards   = !!this.hiddenApp.querySelector('[class*="userCardBlock"]');
            const hasPot     = !!this.hiddenApp.querySelector('[class*="potWrap"]');
            const hasLeave   = !!this.hiddenApp.querySelector('[class*="leaveWrap"]');
            const hasList    = !!this.hiddenApp.querySelector('[class*="rowsWrap"], [class*="createWrap"]');

            let newState;
            if (hasConfirm) newState = 'confirm';
            else if (hasTimer || hasCards || hasPot || hasLeave) newState = 'game';
            else if (hasList) newState = 'lobby';
            else newState = this.state || 'lobby';

            if (newState !== this.state) {
                Logger.info('GameUI', `State: ${this.state || '(init)'} → ${newState}`);
                // When entering 'confirm', snapshot the hospital status. If the dialog
                // appeared while hospitalized, Quick Create is permanently disabled FOR
                // THIS DIALOG — even if the user heals later. This avoids the bad-optics
                // pattern of "auto-confirm fires the moment a condition (heal) is met",
                // which would look like the script is playing on a trigger.
                if (newState === 'confirm') this._confirmStartedHospitalized = this.isHospitalized();
                else if (this.state === 'confirm') this._confirmStartedHospitalized = false;
                this.state = newState;
                this.renderBody();
            }

            if (this.state === 'game')         this.syncGameState();
            else if (this.state === 'confirm') this.syncConfirmState();
            else                               this.syncLobbyState();
        }

        syncNav() {
            const nav = document.getElementById('lt-app-nav');
            if (!nav) return;
            const links = this.hiddenApp.querySelectorAll('[class*="linksContainer"] a');
            // Re-render when the link LABELS change (lobby has "Back to Casino", game has
            // "Back to Lobby" — same count, so a count check would miss the switch).
            const sig = Array.from(links).map(l => (l.querySelector('[class*="linkTitle"]')?.textContent || l.textContent).trim()).join('|');
            if (links.length && nav.dataset.sig === sig && nav.children.length) return;
            nav.dataset.sig = sig;
            nav.innerHTML = '';
            links.forEach(link => {
                const btn = document.createElement('button');
                btn.className = 'lt-nav-btn'; btn.type = 'button';
                btn.textContent = link.querySelector('[class*="linkTitle"]')?.textContent || link.textContent.trim();
                btn.onclick = (e) => { e.preventDefault(); this.fireFullClick(link); };
                nav.appendChild(btn);
            });
        }

        renderBody() {
            const body = document.getElementById('lt-app-body');
            if (!body) return;
            if (this.state === 'game')         this.renderGame(body);
            else if (this.state === 'confirm') this.renderConfirm(body);
            else                               this.renderLobby(body);
        }

        // ── GAME ──
        renderGame(body) {
            body.innerHTML = `
                <div class="lt-igs">
                    <div class="lt-igs-top">
                        <div class="lt-igs-block">
                            <span class="lt-igs-label">⏱ Waiting</span>
                            <span class="lt-igs-timer" id="lt-igs-timer">--:--</span>
                        </div>
                        <button class="lt-igs-sound" id="lt-igs-sound">🔊 Sound</button>
                        <div class="lt-igs-block right">
                            <span class="lt-igs-label">💰 Pot Money</span>
                            <span class="lt-igs-pot" id="lt-igs-pot">$0</span>
                        </div>
                    </div>
                    <div class="lt-igs-msg" id="lt-igs-msg"></div>
                    <div class="lt-igs-players" id="lt-igs-players"></div>
                    <div class="lt-revolver" id="lt-revolver">
                        <div class="lt-rev-cyl" id="lt-rev-cyl"></div>
                    </div>
                    <div class="lt-igs-shots" id="lt-igs-shots"></div>
                    <div class="lt-igs-controls">
                        <button class="lt-leave-btn" id="lt-igs-leave">🚪 Leave Game</button>
                    </div>
                </div>`;
            // Fresh game UI → reset revolver + death state.
            // Mark any stale winner from the previous game as already handled.
            this._revRot = 0;
            this._deadXid = null;
            this._lastHandledWinner = (window.LabTrackIntegration && window.LabTrackIntegration.lastSeenWinnerId) || null;
            this.buildRevolverChambers();
            document.getElementById('lt-igs-leave').onclick = () => {
                const btn = this._findNativeLeave();
                if (btn) this.fireFullClick(btn);
            };
            document.getElementById('lt-igs-sound').onclick = () => {
                const btn = this.hiddenApp.querySelector('[class*="soundToggler"]');
                if (btn) this.fireFullClick(btn);
            };
        }

        // Build 6 chambers in a circle + center hub + muzzle flash overlay
        buildRevolverChambers() {
            const cyl = document.getElementById('lt-rev-cyl');
            if (!cyl) return;
            cyl.innerHTML = '';
            const R = 33; // radius from center (px)
            for (let i = 0; i < 6; i++) {
                const ang = (i * 60 - 90) * Math.PI / 180;
                const x = Math.cos(ang) * R, y = Math.sin(ang) * R;
                const ch = document.createElement('div');
                ch.className = 'lt-rev-chamber';
                ch.style.transform = `translate(calc(-50% + ${x}px), calc(-50% + ${y}px))`;
                cyl.appendChild(ch);
            }
            const hub = document.createElement('div'); hub.className = 'lt-rev-hub'; cyl.appendChild(hub);
            const flash = document.createElement('div'); flash.className = 'lt-rev-flash'; flash.id = 'lt-rev-flash'; cyl.appendChild(flash);
            cyl.style.transform = `rotate(${this._revRot}deg)`;
        }

        // Triggered by the audio hook. type: 'blank' | 'bang' | 'join' | 'shot' (generic)
        onGameSound(type) {
            const cyl = document.getElementById('lt-rev-cyl');
            if (!cyl) return;          // not in our game view
            if (type === 'join') return; // a player joined — not a trigger pull
            // Rotate the cylinder on every shot sound (synced with Torn's audio)
            this._revRot += 60;
            cyl.style.transform = `rotate(${this._revRot}deg)`;
            // Mechanical "click" brightness pulse on each rotation
            cyl.classList.remove('click'); void cyl.offsetWidth; cyl.classList.add('click');
            if (type === 'bang') {
                this.fireRevolver();
                // Sharp horizontal jolt on the whole dashboard — gives the bang impact.
                const db = document.getElementById('lt-dashboard');
                if (db) {
                    db.classList.remove('lt-shake'); void db.offsetWidth; db.classList.add('lt-shake');
                    setTimeout(() => db.classList.remove('lt-shake'), 450);
                }
            }
        }

        // Muzzle flash + recoil (called on bang sound OR on game-end fallback)
        fireRevolver() {
            const rev = document.getElementById('lt-revolver');
            if (rev) { rev.classList.remove('fire'); void rev.offsetWidth; rev.classList.add('fire'); }
            const flash = document.getElementById('lt-rev-flash');
            if (flash) { flash.classList.remove('flash'); void flash.offsetWidth; flash.classList.add('flash'); }
        }

        // The native LEAVE button — lives in leaveWrap, but sometimes Torn renders
        // it inside buttonsWrap. Find it wherever it is so we forward to ONE button.
        _findNativeLeave() {
            if (!this.hiddenApp) return null;
            return this.hiddenApp.querySelector('[class*="leaveWrap"] button')
                || [...this.hiddenApp.querySelectorAll('[class*="buttonsWrap"] button')]
                       .find(b => /leave/i.test(b.textContent || ''));
        }

        _findLoserXid(winnerId) {
            if (!this.hiddenApp) return null;
            const cards = this.hiddenApp.querySelectorAll('[class*="userCardBlock"]');
            for (const card of cards) {
                const link = card.querySelector('a[href*="XID"]');
                const m = link && link.href.match(/XID=(\d+)/);
                if (m && parseInt(m[1]) !== winnerId) return m[1];
            }
            return null;
        }

        syncGameState() {
            // Game-end detection: once a winner is known → fire revolver + red cross on loser.
            // (Fallback for the bang sound, which can't always be distinguished via Web Audio.)
            const integ = window.LabTrackIntegration;
            const winnerId = integ && integ.lastSeenWinnerId;
            if (winnerId && this._lastHandledWinner !== winnerId) {
                this._lastHandledWinner = winnerId;
                this.fireRevolver();
                const loser = this._findLoserXid(winnerId);
                if (loser) this._deadXid = loser;
            }

            const text = (el) => (el?.textContent || '').trim();
            const setIf = (id, val) => { const el = document.getElementById(id); if (el && el.textContent !== val) el.textContent = val; };

            // Timer — urgent pulse under 10s, critical pulse under 5s
            const timerEl = document.getElementById('lt-igs-timer');
            const timerText = text(this.hiddenApp.querySelector('[class*="timer"] [class*="counter"]')) || '--:--';
            setIf('lt-igs-timer', timerText);
            if (timerEl) {
                const m = timerText.match(/(\d+):(\d+)/);
                const totalSec = m ? (parseInt(m[1]) * 60 + parseInt(m[2])) : -1;
                timerEl.classList.toggle('critical', totalSec >= 0 && totalSec < 5);
                timerEl.classList.toggle('urgent',   totalSec >= 5 && totalSec < 10);
            }

            // Pot — flash gold + scale on every increase
            const potEl = document.getElementById('lt-igs-pot');
            const potText = text(this.hiddenApp.querySelector('[class*="potWrap"] [class*="count"]')) || '$0';
            if (potEl && potEl.textContent !== potText) {
                const prev = parseInt((potEl.textContent || '').replace(/[^\d]/g, '')) || 0;
                const curr = parseInt(potText.replace(/[^\d]/g, '')) || 0;
                potEl.textContent = potText;
                if (curr > prev && prev > 0) {
                    potEl.classList.remove('lt-pot-flash'); void potEl.offsetWidth;
                    potEl.classList.add('lt-pot-flash');
                }
            }

            setIf('lt-igs-msg',   text(this.hiddenApp.querySelector('[class*="messageWrap"] [class*="message"]')) || '');

            const soundEl = this.hiddenApp.querySelector('[class*="soundToggler"]');
            const ourSound = document.getElementById('lt-igs-sound');
            if (soundEl && ourSound) ourSound.textContent = /on/i.test(soundEl.textContent) ? '🔊 Sound ON' : '🔇 Sound OFF';

            // LEAVE only shows when Torn actually offers it (waiting / after death) —
            // never during the active fight, where leaving isn't possible.
            const nativeLeave = this._findNativeLeave();
            const ourLeave = document.getElementById('lt-igs-leave');
            if (ourLeave) ourLeave.style.display = nativeLeave ? '' : 'none';

            // Action buttons in buttonsWrap: shoot/x2/x3 (commitButton, your turn) OR
            // "take action" (no commitButton, appears when opponent is AFK).
            const shotsEl = document.getElementById('lt-igs-shots');
            if (shotsEl) {
                // Exclude the LEAVE button — it lives in buttonsWrap too, but we render
                // it separately as our dedicated #lt-igs-leave. (Avoids a 2nd leave button.)
                const nativeBtns = [...this.hiddenApp.querySelectorAll('[class*="buttonsWrap"] button')]
                    .filter(b => !/leave/i.test(b.textContent || ''));
                const sig = nativeBtns.map(b => `${b.dataset.id || ''}:${b.textContent.trim()}`).join('|');
                if (shotsEl.dataset.sig !== sig) {
                    shotsEl.dataset.sig = sig;
                    shotsEl.innerHTML = '';
                    nativeBtns.forEach(nb => {
                        const txt = nb.textContent.trim();
                        const isCommit = /commitButton/i.test(nb.className);
                        const b = document.createElement('button');
                        b.type = 'button';
                        b.textContent = txt;
                        if (isCommit) {
                            const id = nb.dataset.id;
                            b.className = 'lt-shot-btn ' + (id === '1' ? 'primary' : 'mult');
                            const originalTxt = txt;
                            b.dataset.origTxt = originalTxt;
                            // Restore armed visual after a sync-rebuild (state lives on
                            // the controller so it survives DOM wipes).
                            if (this._armedShot && this._armedShot.id === id && Date.now() - this._armedShot.at < 3000) {
                                b.classList.add('armed');
                                b.textContent = `⚠ Confirm ${originalTxt}?`;
                            }
                            b.onclick = () => {
                                const fireNative = () => {
                                    const fresh = this.hiddenApp.querySelector(`button[class*="commitButton"][data-id="${id}"]`);
                                    if (fresh) this.fireFullClick(fresh);
                                };
                                // Single shot fires immediately. Multi-shots (x2/x3) need
                                // a second click within 3 s to confirm — prevents the
                                // accidental double/triple shots that can blow up a sequence.
                                if (id === '1') { fireNative(); return; }
                                const now = Date.now();
                                const armedFor = this._armedShot && this._armedShot.id === id && (now - this._armedShot.at < 3000);
                                if (armedFor) {
                                    this._armedShot = null;
                                    clearTimeout(this._shotArmTimer);
                                    b.classList.remove('armed');
                                    b.textContent = originalTxt;
                                    fireNative();
                                    return;
                                }
                                this._armedShot = { id, at: now };
                                // Clear any previously-armed sibling visuals.
                                shotsEl.querySelectorAll('.lt-shot-btn.armed').forEach(el => {
                                    el.classList.remove('armed');
                                    if (el.dataset.origTxt) el.textContent = el.dataset.origTxt;
                                });
                                b.classList.add('armed');
                                b.textContent = `⚠ Confirm ${originalTxt}?`;
                                clearTimeout(this._shotArmTimer);
                                this._shotArmTimer = setTimeout(() => {
                                    this._armedShot = null;
                                    const live = document.querySelectorAll('#lt-igs-shots .lt-shot-btn.armed');
                                    live.forEach(el => {
                                        el.classList.remove('armed');
                                        if (el.dataset.origTxt) el.textContent = el.dataset.origTxt;
                                    });
                                }, 3000);
                            };
                        } else {
                            // "take action" (claim win vs AFK opponent) or other non-commit button
                            b.className = 'lt-shot-btn take-action';
                            b.onclick = () => {
                                const fresh = [...this.hiddenApp.querySelectorAll('[class*="buttonsWrap"] button')]
                                    .find(x => !/commitButton/i.test(x.className) && x.textContent.trim().toLowerCase() === txt.toLowerCase());
                                if (fresh) this.fireFullClick(fresh);
                            };
                        }
                        shotsEl.appendChild(b);
                    });
                }
            }

            const players = document.getElementById('lt-igs-players');
            if (!players) return;
            const cards = this.hiddenApp.querySelectorAll('[class*="userCardBlock"]');
            // Only rebuild structure when the count changes (no flicker)
            if (players.dataset.cardCount !== String(cards.length)) {
                players.innerHTML = '';
                players.dataset.cardCount = String(cards.length);
                cards.forEach((card, idx) => {
                    if (idx > 0) { const vs = document.createElement('div'); vs.className = 'lt-vs'; vs.textContent = 'VS'; players.appendChild(vs); }
                    const wrap = document.createElement('div');
                    wrap.className = 'lt-player';
                    wrap.innerHTML = `<div class="lt-player-avatar-slot"></div><div class="lt-player-name"></div>`;
                    players.appendChild(wrap);
                });
            }
            // Update content (name + avatar) on EVERY sync → image appears as soon as it loads
            const playerEls = players.querySelectorAll('.lt-player');
            cards.forEach((card, idx) => {
                const pl = playerEls[idx];
                if (!pl) return;
                const link = card.querySelector('a');
                const rawName = link?.textContent.replace(/^User name:\s*/i, '').trim() || 'Unknown';
                const isWaiting = /waiting/i.test(rawName);
                pl.classList.toggle('waiting', isWaiting);
                // Player XID (for death-cross matching + clickable profile link)
                const xidLink = card.querySelector('a[href*="XID"]');
                const xidM = xidLink && xidLink.href.match(/XID=(\d+)/);
                const xid = xidM ? xidM[1] : '';
                pl.dataset.xid = xid;
                // Name → clickable profile link (like original RR), or plain text if no XID
                const nameEl = pl.querySelector('.lt-player-name');
                if (nameEl) {
                    nameEl.classList.toggle('waiting', isWaiting);
                    if (xid && !isWaiting) {
                        const desired = `<a href="/profiles.php?XID=${xid}" target="_blank" rel="noreferrer" class="lt-player-link">${rawName}</a>`;
                        if (nameEl.innerHTML !== desired) nameEl.innerHTML = desired;
                    } else if (nameEl.textContent !== rawName) {
                        nameEl.textContent = rawName;
                    }
                }
                const slot = pl.querySelector('.lt-player-avatar-slot');
                if (slot) {
                    const img = card.querySelector('img[class*="userImage"]') || card.querySelector('img');
                    const realSrc = (img && img.src && !img.src.startsWith('data:')) ? img.src : null;
                    if (realSrc) {
                        let our = slot.querySelector('img.lt-player-avatar');
                        if (!our) { slot.innerHTML = '<img class="lt-player-avatar" alt="">'; our = slot.querySelector('img.lt-player-avatar'); }
                        if (our.src !== realSrc) our.src = realSrc;
                    } else if (!slot.querySelector('.lt-player-avatar-ph')) {
                        slot.innerHTML = '<div class="lt-player-avatar-ph">?</div>';
                    }
                }
                // Death cross overlay (red ✕) on the player who lost
                const isDead = this._deadXid && xid === this._deadXid;
                pl.classList.toggle('dead', !!isDead);
                let cross = pl.querySelector('.lt-player-cross');
                if (isDead && !cross) { cross = document.createElement('div'); cross.className = 'lt-player-cross'; pl.appendChild(cross); }
                else if (!isDead && cross) cross.remove();
            });
        }

        // ── CONFIRM (game create/join) ──
        renderConfirm(body) {
            body.innerHTML = `
                <div class="lt-confirm">
                    <div class="lt-confirm-text" id="lt-confirm-text">Confirm?</div>
                    <div class="lt-confirm-btns">
                        <button class="lt-confirm-yes" id="lt-confirm-yes">Yes</button>
                        <button class="lt-confirm-no" id="lt-confirm-no">No</button>
                    </div>
                </div>`;
            document.getElementById('lt-confirm-yes').onclick = () => {
                const btn = this.hiddenApp.querySelector('[class*="confirmWrap"] button[data-type="confirm"]');
                if (btn) this.fireFullClick(btn);
            };
            document.getElementById('lt-confirm-no').onclick = () => {
                const btn = this.hiddenApp.querySelector('[class*="confirmWrap"] button[data-type="cancel"]');
                if (btn) this.fireFullClick(btn);
            };
        }

        syncConfirmState() {
            const txtEl = document.getElementById('lt-confirm-text');
            const nativeText = this.hiddenApp.querySelector('[class*="confirmWrap"] [class*="text"]');
            if (txtEl && nativeText && txtEl.innerHTML !== nativeText.innerHTML) txtEl.innerHTML = nativeText.innerHTML;

            // QUICK CREATE: auto-click YES once per confirm dialog. Safety gates:
            //  1) blocked while currently hospitalized
            //  2) blocked if the dialog was *opened* while hospitalized — even if the
            //     user heals out later, this specific dialog stays manual-only. (Auto-
            //     clicking on heal-out would look like a "play on condition" bot.)
            //  3) blocked if the bet in the dialog doesn't match our current next-bet
            //     (typo / stale value / wrong native input → no silent wrong-stake game)
            if (this._quickCreate && !this.isHospitalized() && !this._confirmStartedHospitalized) {
                const yesBtn = this.hiddenApp.querySelector('[class*="confirmWrap"] button[data-type="confirm"]');
                if (yesBtn && Date.now() - (this._lastQuickConfirm || 0) > 800) {
                    // Parse the bet amount out of the dialog text ("create a game for $5,000,000")
                    const dlgTxt = (nativeText && nativeText.textContent) || '';
                    const m = dlgTxt.match(/\$\s*([\d,]+)/);
                    const dlgBet = m ? parseInt(m[1].replace(/,/g, '')) : null;
                    const expected = Math.floor(engine.getEffectiveBet() || 0);

                    if (dlgBet === null || expected <= 0 || dlgBet !== expected) {
                        // Mismatch / can't verify → leave the dialog open for the user to decide.
                        // Throttle the warning toast so it doesn't spam on every poll tick.
                        if (Date.now() - (this._lastQuickMismatchToast || 0) > 2000) {
                            this._lastQuickMismatchToast = Date.now();
                            Utils.showToast(dlgBet !== null
                                ? `Quick Create blocked — dialog ${Utils.formatNumber(dlgBet)} ≠ Next ${Utils.formatNumber(expected)}`
                                : `Quick Create blocked — couldn't read bet from dialog`);
                        }
                    } else {
                        this._lastQuickConfirm = Date.now();
                        this.fireFullClick(yesBtn);
                    }
                }
            }
        }

        // Hospital status: the active indicator has aria-label "Hospital: <reason>" (with colon).
        // The channel button only has title="Hospital" (no colon) → not matched.
        isHospitalized() {
            return !!document.querySelector('[aria-label^="Hospital:" i]');
        }

        // ── LOBBY ──
        renderLobby(body) {
            const qc = this._quickCreate ? ' on' : '';
            body.innerHTML = `
                <div class="lt-lobby">
                    <div class="lt-ingame-banner" id="lt-ingame-banner" style="display:none;"></div>
                    <div class="lt-lobby-section">
                        <div class="lt-lobby-section-title">
                            <span><span class="lt-glyph-chamber"></span> Start a new game</span>
                            <button id="lt-quick-create" class="lt-quick-toggle${qc}" type="button" title="When ON, the confirmation dialog after Start is clicked automatically (blocked while hospitalized)">⚡ Quick Create: <b>${this._quickCreate ? 'ON' : 'OFF'}</b></button>
                        </div>
                        <div class="lt-start-form">
                            <span class="lt-start-label">Bet:</span>
                            <input type="text" class="lt-start-input" id="lt-lobby-bet" placeholder="Amount">
                            <button class="lt-start-prefill" id="lt-lobby-prefill" title="Insert next bet">📋 Next Bet</button>
                            <span class="lt-start-label">🔒</span>
                            <input type="text" class="lt-start-input" id="lt-lobby-pw" placeholder="Password (optional)" style="width:150px;">
                            <button class="lt-start-go" id="lt-lobby-start">▶ Start</button>
                        </div>
                    </div>
                    <div class="lt-lobby-section">
                        <div class="lt-coll" id="lt-coll-games">
                            <div class="lt-coll-head" id="lt-coll-toggle">
                                <span class="lt-coll-title">⚔ Available Games <span class="lt-coll-count" id="lt-coll-count">(0)</span></span>
                                <span class="lt-coll-icon">▼</span>
                            </div>
                            <div class="lt-coll-body" id="lt-coll-body"><div class="lt-coll-empty">Loading…</div></div>
                        </div>
                    </div>
                </div>`;
            document.getElementById('lt-coll-toggle').onclick = () => {
                document.getElementById('lt-coll-games').classList.toggle('collapsed');
            };
            const qcBtn = document.getElementById('lt-quick-create');
            if (qcBtn) qcBtn.onclick = () => this.toggleQuickCreate();
            const ourBet = document.getElementById('lt-lobby-bet');
            const ourPw  = document.getElementById('lt-lobby-pw');
            // Pre-fill with the current next-bet (still freely editable). Native bet
            // wins if it's already set (e.g. you typed in Torn's own field first).
            const nbv = this.getNativeBet();
            const nextBet = Math.floor(engine.getEffectiveBet() || 0);
            const initial = nbv || (nextBet > 0 ? String(nextBet) : '');
            if (initial) { ourBet.value = initial; this.writeNativeBet(initial); }
            this._lastAutoBet = initial; // remembered so syncLobbyState can refresh untouched values
            ourBet.addEventListener('input', () => this.writeNativeBet(ourBet.value.replace(/[^0-9]/g, '')));
            ourPw.addEventListener('input', () => this.writeNativePassword(ourPw.value));
            document.getElementById('lt-lobby-prefill').onclick = () => {
                const amt = engine.getEffectiveBet();
                if (amt > 0) { ourBet.value = String(Math.floor(amt)); this.writeNativeBet(String(Math.floor(amt))); }
            };
            document.getElementById('lt-lobby-start').onclick = () => {
                const startBtn = this.findNativeStartButton();
                if (!startBtn) { Logger.warn('GameUI', 'START button not found'); Utils.showToast('Start button not found'); return; }
                const betStr = ourBet.value.replace(/[^0-9]/g, '');

                // SAFETY (Quick Create only): with auto-confirm enabled the dialog
                // is skipped, so a typo or stale value would silently create a wrong
                // -stake game. Refuse to start unless the bet exactly matches the
                // current next-bet. (Normal mode still leaves the dialog as a manual
                // safety net.)
                if (this._quickCreate) {
                    const expected = Math.floor(engine.getEffectiveBet() || 0);
                    const typed = parseInt(betStr) || 0;
                    if (expected <= 0) { Utils.showToast('No next bet — generate a sequence first'); return; }
                    if (typed !== expected) {
                        Utils.showToast(`Bet ${Utils.formatNumber(typed)} ≠ Next ${Utils.formatNumber(expected)} — Quick Create blocked`);
                        ourBet.classList.add('error'); setTimeout(() => ourBet.classList.remove('error'), 1500);
                        return;
                    }
                }

                if (betStr) { this._lastBetSet = null; this.writeNativeBet(betStr); }
                this.writeNativePassword(ourPw.value); // sync password (empty = no password)
                setTimeout(() => this.fireFullClick(startBtn), 60);
            };
        }

        syncLobbyState() {
            this.syncInGameBanner(); // "Return to Game" banner if a game of ours is waiting

            // Auto-refresh the bet field with the current next-bet, but only if the
            // user hasn't typed a custom value. We detect "untouched" by comparing
            // the field value to the last value we auto-wrote — if it still matches,
            // it's safe to update (e.g. after a win/loss changes the next bet).
            const betEl = document.getElementById('lt-lobby-bet');
            if (betEl && (betEl.value === '' || betEl.value === this._lastAutoBet)) {
                const next = Math.floor(engine.getEffectiveBet() || 0);
                if (next > 0) {
                    const str = String(next);
                    if (betEl.value !== str) { betEl.value = str; this.writeNativeBet(str); }
                    this._lastAutoBet = str;
                }
            }

            const body  = document.getElementById('lt-coll-body');
            const count = document.getElementById('lt-coll-count');
            if (!body || !count) return;

            const rows = document.querySelectorAll('div[class*="rowsWrap"] > div[class*="row"]');
            const games = [];
            for (const row of rows) {
                const betBlock = row.querySelector('[class*="betBlock"]');
                let bet = 0;
                const aria = betBlock?.getAttribute('aria-label') || '';
                const am = aria.match(/([\d,]+)/);
                if (am) bet = parseInt(am[1].replace(/,/g, '')) || 0;
                else if (betBlock) bet = parseInt(betBlock.textContent.replace(/[^\d]/g, '')) || 0;
                if (bet <= 0) continue;
                const nameLink = row.querySelector('a[aria-label*="View profile" i], a[href*="XID"]');
                const name = nameLink?.getAttribute('aria-label')?.replace(/^View profile of\s*/i, '').trim()
                          || row.querySelector('.honor-text:not(.honor-text-svg)')?.textContent.trim() || 'Player';
                const xidM = nameLink && (nameLink.href || '').match(/XID=(\d+)/);
                const xid = xidM ? xidM[1] : '';
                const joinBtn = row.querySelector('[class*="joinBlock"] button');
                const locked = !!joinBtn && (joinBtn.disabled || /locked/i.test(joinBtn.className));
                const hasPassword = !!row.querySelector('[class*="withPassword"]');
                games.push({ bet, name, xid, joinBtn, hasPassword, locked });
            }

            count.textContent = `(${games.length})`;

            // RESERVED SLOTS — the Available Games list always reserves space for
            // 10 rows. Real games fill from the top; empty slots sit beneath so the
            // panel stays the same size whether 0 or 10+ games exist, and the
            // dashboard never shifts when games come/go.
            const SLOTS = 10;

            // Game "Bet Amount" = stake per player → compare directly with our NEXT BET
            const target = engine.getEffectiveBet();
            const tolerance = Math.max(20, target * 0.05);
            const closeRange = Math.max(target * 0.20, 1000);
            // Three-tier sort: matching bets first, then close-to-bet, then the rest.
            // Within each tier sort by absolute distance to our next bet (or by bet
            // size when we have no target yet).
            const tier = (bet) => {
                if (target <= 0) return 2;
                const diff = Math.abs(bet - target);
                if (diff <= tolerance)  return 0;  // match
                if (diff <= closeRange) return 1;  // close
                return 2;                          // far
            };
            games.sort((a, b) => {
                const ta = tier(a.bet), tb = tier(b.bet);
                if (ta !== tb) return ta - tb;
                if (target > 0) return Math.abs(a.bet - target) - Math.abs(b.bet - target);
                return a.bet - b.bet;
            });

            body.innerHTML = '';
            for (const g of games) {
                const diff = Math.abs(g.bet - target);
                let mc = '', bc = '';
                if (target > 0) { if (diff <= tolerance) { mc = 'match'; bc = 'match-bet'; } else if (diff <= closeRange) mc = 'close'; }
                const row = document.createElement('div');
                row.className = 'lt-game-row ' + mc;
                const nameHtml = g.xid
                    ? `<a href="/profiles.php?XID=${g.xid}" target="_blank" rel="noreferrer" class="lt-player-link">${g.name}</a>`
                    : g.name;
                row.innerHTML = `
                    <span class="lt-game-row-status"></span>
                    <span class="lt-game-row-name">${nameHtml}${g.hasPassword ? ' 🔒' : ''}</span>
                    <span class="lt-game-row-bet ${bc}">$${g.bet.toLocaleString('de')}</span>`;
                const jb = document.createElement('button');
                jb.className = 'lt-game-row-join'; jb.type = 'button';
                if (g.locked) {
                    jb.disabled = true; jb.textContent = '🔒';
                    jb.title = "Can't join — you're already in a game";
                } else {
                    jb.textContent = mc === 'match' ? '🎯 JOIN' : 'JOIN';
                    jb.onclick = () => {
                        if (!g.joinBtn) { Utils.showToast('Join button not found'); return; }
                        // The joined game's stake = our stake this round.
                        // If it differs from the expected NEXT BET → set as custom bet (like when creating).
                        const expected = engine.getEffectiveBet();
                        if (g.bet > 0 && Math.abs(g.bet - expected) > 1) {
                            engine.setCustomBet(g.bet);
                            Utils.showToast(`Custom Bet (Join): ${Utils.formatNumber(g.bet)}`);
                        }
                        this.fireFullClick(g.joinBtn);
                    };
                }
                row.appendChild(jb);
                body.appendChild(row);
            }

            // Pad with empty rows so the list height is constant (no dashboard jumping)
            const empties = Math.max(0, SLOTS - games.length);
            for (let i = 0; i < empties; i++) {
                const row = document.createElement('div');
                row.className = 'lt-game-row lt-game-row-empty';
                row.innerHTML = `<span class="lt-game-row-empty-line">— slot ${games.length + i + 1} —</span>`;
                body.appendChild(row);
            }
        }

        // "Return to Game" / "Leave Game" banner — shown when browsing the lobby while
        // one of your own games is still waiting (Torn shows a red alert with these).
        syncInGameBanner() {
            const banner = document.getElementById('lt-ingame-banner');
            if (!banner) return;
            const infoBox = this.hiddenApp.querySelector('[class*="infoBox"]');
            const active = infoBox && /return to game/i.test(infoBox.textContent || '');
            if (!active) {
                if (banner.dataset.active === '1') { banner.dataset.active = '0'; banner.style.display = 'none'; banner.innerHTML = ''; }
                return;
            }
            if (banner.dataset.active !== '1') {
                banner.dataset.active = '1';
                banner.style.display = 'flex';
                banner.innerHTML = `
                    <span class="lt-ingame-msg" id="lt-ingame-msg"></span>
                    <div class="lt-ingame-btns">
                        <button class="lt-ingame-return" id="lt-ingame-return">↩ Return to Game</button>
                        <button class="lt-ingame-leave" id="lt-ingame-leave">Leave Game</button>
                    </div>`;
                document.getElementById('lt-ingame-return').onclick = () => {
                    const box = this.hiddenApp.querySelector('[class*="infoBox"]');
                    const rl = box && [...box.querySelectorAll('a')].find(a => /return to game/i.test(a.textContent || ''));
                    if (rl) this.fireFullClick(rl);
                };
                document.getElementById('lt-ingame-leave').onclick = () => {
                    const box = this.hiddenApp.querySelector('[class*="infoBox"]');
                    const lb = box && [...box.querySelectorAll('button')].find(b => /leave game/i.test(b.textContent || ''));
                    if (lb) this.fireFullClick(lb);
                };
            }
            // Live timeout message (keeps the countdown updated)
            const msgEl = document.getElementById('lt-ingame-msg');
            const span = infoBox.querySelector('p span');
            if (msgEl && span && msgEl.innerHTML !== span.innerHTML) msgEl.innerHTML = span.innerHTML;
        }

        // ── Helpers ──
        getNativeBet() {
            const inp = this.hiddenApp?.querySelector('input.input-money[type="text"]')
                      || document.querySelector('input.input-money[type="text"]:not(.lt-start-input)');
            return inp ? (inp.value || '').replace(/[^0-9]/g, '') : '';
        }

        writeNativeBet(value) {
            if (value === this._lastBetSet) return;
            this._lastBetSet = value;
            const inp = this.hiddenApp?.querySelector('input.input-money[type="text"]')
                      || document.querySelector('input.input-money[type="text"]:not(.lt-start-input)');
            if (!inp) return;
            const tracker = inp._valueTracker;
            if (tracker) tracker.setValue(String(parseInt(inp.value) || 0));
            Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set.call(inp, value);
            inp._ltSyntheticBetWrite = Date.now();
            inp.dispatchEvent(new Event('input', { bubbles: true }));
            inp.dispatchEvent(new Event('change', { bubbles: true }));
        }

        // Write the optional password into Torn's native RR password input (React-friendly)
        writeNativePassword(value) {
            const inp = this.hiddenApp?.querySelector('input[class*="password"], input[aria-label*="password" i]');
            if (!inp || inp === document.getElementById('lt-lobby-pw')) return;
            if (inp.value === value) return;
            const tracker = inp._valueTracker;
            if (tracker) tracker.setValue(inp.value);
            Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set.call(inp, value);
            inp.dispatchEvent(new Event('input', { bubbles: true }));
            inp.dispatchEvent(new Event('change', { bubbles: true }));
        }

        findNativeStartButton() {
            const precise = document.querySelector('[class*="createWrap"] [class*="startBlock"] button[class*="submit"], [class*="createWrap"] button.torn-btn');
            if (precise) return precise;
            const matches = (el) => { const t = (el.value || el.textContent || '').trim().toUpperCase(); return t === 'START' || t === 'START GAME'; };
            for (const el of document.querySelectorAll('button, input[type="button"], input[type="submit"]')) {
                if (el.closest('#lt-dashboard, #lt-app')) continue;
                if (matches(el)) return el;
            }
            return null;
        }

        fireFullClick(el) {
            const opts = { bubbles: true, cancelable: true, composed: true, view: window };
            const ptr = { ...opts, pointerId: 1, pointerType: 'mouse', isPrimary: true, button: 0 };
            // preventScroll: otherwise the browser scrolls to the off-screen element (left:-99999px) → jump to top
            try { el.focus({ preventScroll: true }); } catch (e) {}
            try {
                el.dispatchEvent(new PointerEvent('pointerover', ptr));
                el.dispatchEvent(new PointerEvent('pointerenter', ptr));
                el.dispatchEvent(new PointerEvent('pointerdown', ptr));
                el.dispatchEvent(new MouseEvent('mousedown', { ...opts, button: 0 }));
                el.dispatchEvent(new PointerEvent('pointerup', ptr));
                el.dispatchEvent(new MouseEvent('mouseup', { ...opts, button: 0 }));
            } catch (e) {}
            el.click();
        }

        startObserver() {
            if (this.observer) this.observer.disconnect();
            if (!this.hiddenApp) return;
            this.observer = new MutationObserver(() => this.sync());
            this.observer.observe(this.hiddenApp, { subtree: true, childList: true, characterData: true, attributes: true });
        }
    }

    // =============================================================================
    // INITIALIZATION - V7.00
    // =============================================================================
    Logger.info('INIT', `LabTrack Enhanced v${CONFIG.VERSION} starting...`);
    Logger.info('INIT', 'Enhancements: CONFIG constants, Logger, Validator, Performance utils, Error handling');

    const integration = new TornIntegration();
    const waitForBody = () => {
        if (!document.body) {
            setTimeout(waitForBody, CONFIG.DOM_DELAY_MS);
        } else {
            const ui = new OverlayUI();
            ui.init();
            integration.setUI(ui);
            integration.start();
            Logger.info('INIT', 'LabTrack Enhanced initialization complete ✓');
        }
    };
    waitForBody();

})();