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

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

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

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

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

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

You will need to install a user script manager extension to install this script.

(Tôi đã có Trình quản lý tập lệnh người dùng, hãy cài đặt nó!)

Advertisement:

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

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();

})();