Greasy Fork is available in English.

TM Match Viewer

Enhanced match viewer with live replay, lineups, statistics, venue and H2H tabs

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         TM Match Viewer
// @namespace    https://trophymanager.com
// @version      1.5.0
// @description  Enhanced match viewer with live replay, lineups, statistics, venue and H2H tabs
// @match        https://trophymanager.com/matches/*
// @grant        none
// ==/UserScript==

(function () {
    'use strict';

    // ─── Constants ───────────────────────────────────────────────────────
    const STORAGE_KEY = 'TM_LEAGUE_LINEUP_NUM_ROUNDS';
    const SHARE_BONUS = 0.25;
    const ROUTINE_CAP = 40.0;
    const POS_MULTIPLIERS = [0.3, 0.3, 0.9, 0.6, 1.5, 0.9, 0.9, 0.6, 0.3];

    const DEF_POSITIONS = new Set(['gk', 'dl', 'dr', 'dc', 'dcl', 'dcr']);
    const MID_POSITIONS = new Set(['dml', 'dmr', 'dmc', 'dmcl', 'dmcr', 'ml', 'mr', 'mc', 'mcl', 'mcr']);

    const COLOR_LEVELS = [
        { color: '#ff4c4c' }, { color: '#ff8c00' }, { color: '#ffd700' },
        { color: '#90ee90' }, { color: '#00cfcf' }, { color: '#5b9bff' }, { color: '#cc88ff' }
    ];

    const REC_THRESHOLDS = [5.5, 5, 4, 3, 2, 1, 0];
    const R5_THRESHOLDS = [110, 100, 90, 80, 70, 60, 0];
    const AGE_THRESHOLDS = [30, 28, 26, 24, 22, 20, 0];

    // ─── Weight tables ───────────────────────────────────────────────────
    //              Str         Sta         Pac         Mar         Tac         Wor         Pos         Pas         Cro         Tec         Hea         Fin         Lon         Set
    const WEIGHT_R5 = [
        [0.41029304, 0.18048062, 0.56730138, 1.06344654, 1.02312672, 0.40831256, 0.58235457, 0.12717479, 0.05454137, 0.09089830, 0.42381693, 0.04626272, 0.02199046, 0], // DC
        [0.42126371, 0.18293193, 0.60567629, 0.91904794, 0.89070915, 0.40038476, 0.56146633, 0.15053902, 0.15955429, 0.15682932, 0.42109742, 0.09460329, 0.03589655, 0], // DL/R
        [0.23412419, 0.32032289, 0.62194779, 0.63162534, 0.63143081, 0.45218831, 0.47370658, 0.55054737, 0.17744915, 0.39932519, 0.26915814, 0.16413124, 0.07404301, 0], // DMC
        [0.27276905, 0.26814289, 0.61104798, 0.39865092, 0.42862643, 0.43582015, 0.46617076, 0.44931076, 0.25175412, 0.46446692, 0.29986350, 0.43843061, 0.21494592, 0], // DML/R
        [0.25219260, 0.25112993, 0.56090649, 0.18230261, 0.18376490, 0.45928749, 0.53498118, 0.59461481, 0.09851189, 0.61601950, 0.31243959, 0.65402884, 0.29982016, 0], // MC
        [0.28155678, 0.24090675, 0.60680245, 0.19068879, 0.20018012, 0.45148647, 0.48230007, 0.42982389, 0.26268609, 0.57933805, 0.31712419, 0.65824985, 0.29885649, 0], // ML/R
        [0.22029884, 0.29229690, 0.63248227, 0.09904394, 0.10043602, 0.47469498, 0.52919791, 0.77555880, 0.10531819, 0.71048302, 0.27667115, 0.56813972, 0.21537826, 0], // OMC
        [0.21151292, 0.35804710, 0.88688492, 0.14391236, 0.13769621, 0.46586605, 0.34446036, 0.51377701, 0.59723919, 0.75126119, 0.16550722, 0.29966502, 0.12417045, 0], // OML/R
        [0.35479780, 0.14887553, 0.43273380, 0.00023928, 0.00021111, 0.46931131, 0.57731335, 0.41686333, 0.05607604, 0.62121195, 0.45370457, 1.03660702, 0.43205492, 0], // F
        [0.45462811, 0.30278232, 0.45462811, 0.90925623, 0.45462811, 0.90925623, 0.45462811, 0.45462811, 0.30278232, 0.15139116, 0.15139116]  // GK
    ];

    //              Str         Sta         Pac         Mar         Tac         Wor         Pos         Pas         Cro         Tec         Hea         Fin         Lon         Set
    const WEIGHT_RB = [
        [0.10493615, 0.05208547, 0.07934211, 0.14448971, 0.13159554, 0.06553072, 0.07778375, 0.06669303, 0.05158306, 0.02753168, 0.12055170, 0.01350989, 0.02549169, 0.03887550], // DC
        [0.07715535, 0.04943315, 0.11627229, 0.11638685, 0.12893778, 0.07747251, 0.06370799, 0.03830611, 0.10361093, 0.06253997, 0.09128094, 0.01314110, 0.02449199, 0.03726305], // DL/R
        [0.08219824, 0.08668831, 0.07434242, 0.09661001, 0.08894242, 0.08998026, 0.09281287, 0.08868309, 0.04753574, 0.06042619, 0.05396986, 0.05059984, 0.05660203, 0.03060871], // DMC
        [0.06744248, 0.06641401, 0.09977251, 0.08253749, 0.09709316, 0.09241026, 0.08513703, 0.06127851, 0.10275520, 0.07985941, 0.04618960, 0.03927270, 0.05285911, 0.02697852], // DML/R
        [0.07304213, 0.08174111, 0.07248656, 0.08482334, 0.07078726, 0.09568392, 0.09464529, 0.09580381, 0.04746231, 0.07093008, 0.04595281, 0.05955544, 0.07161249, 0.03547345], // MC
        [0.06527363, 0.06410270, 0.09701305, 0.07406706, 0.08563595, 0.09648566, 0.08651209, 0.06357183, 0.10819222, 0.07386495, 0.03245554, 0.05430668, 0.06572005, 0.03279859], // ML/R
        [0.07842736, 0.07744888, 0.07201150, 0.06734457, 0.05002348, 0.08350204, 0.08207655, 0.11181914, 0.03756112, 0.07486004, 0.06533972, 0.07457344, 0.09781475, 0.02719742], // OMC
        [0.06545375, 0.06145378, 0.10503536, 0.06421508, 0.07627526, 0.09232981, 0.07763931, 0.07001035, 0.11307331, 0.07298351, 0.04248486, 0.06462713, 0.07038293, 0.02403557], // OML/R
        [0.07738289, 0.05022488, 0.07790481, 0.01356516, 0.01038191, 0.06495444, 0.07721954, 0.07701905, 0.02680715, 0.07759692, 0.12701687, 0.15378395, 0.12808992, 0.03805251], // F
        [0.07466384, 0.07466384, 0.07466384, 0.14932769, 0.10452938, 0.14932769, 0.10452938, 0.10344411, 0.07512610, 0.04492581, 0.04479831]  // GK
    ];

    // ─── Utility helpers ─────────────────────────────────────────────────
    const fix2 = v => (Math.round(v * 100) / 100).toFixed(2);

    const parseNum = str => Number(String(str).replace(/,/g, ''));

    const getColor = (value, thresholds) => {
        for (let i = 0; i < thresholds.length; i++) {
            if (value >= thresholds[i]) return COLOR_LEVELS[i].color;
        }
        return COLOR_LEVELS[COLOR_LEVELS.length - 1].color;
    };

    const getPositionIndex = pos => {
        switch (pos.toLowerCase()) {
            case 'gk': return 9;
            case 'dc': case 'dcl': case 'dcr': return 0;
            case 'dr': case 'dl': return 1;
            case 'dmc': case 'dmcl': case 'dmcr': return 2;
            case 'dmr': case 'dml': return 3;
            case 'mc': case 'mcl': case 'mcr': return 4;
            case 'mr': case 'ml': return 5;
            case 'omc': case 'omcl': case 'omcr': return 6;
            case 'omr': case 'oml': return 7;
            case 'fc': case 'fcl': case 'fcr': case 'f': return 8;
            default: return 0;
        }
    };

    const classifyPosition = pos => {
        if (DEF_POSITIONS.has(pos)) return 'D';
        if (MID_POSITIONS.has(pos)) return 'M';
        return 'F';
    };

    // ─── Rating calculations ─────────────────────────────────────────────
    const calculateRemainders = (posIdx, skills, asi) => {
        const weight = posIdx === 9 ? 48717927500 : 263533760000;
        const skillSum = skills.reduce((sum, s) => sum + parseInt(s), 0);
        const remainder = Math.round((Math.pow(2, Math.log(weight * asi) / Math.log(Math.pow(2, 7))) - skillSum) * 10) / 10;

        let rec = 0, ratingR = 0, remainderW1 = 0, remainderW2 = 0, not20 = 0;

        for (let i = 0; i < WEIGHT_RB[posIdx].length; i++) {
            rec += skills[i] * WEIGHT_RB[posIdx][i];
            ratingR += skills[i] * WEIGHT_R5[posIdx][i];
            if (skills[i] != 20) {
                remainderW1 += WEIGHT_RB[posIdx][i];
                remainderW2 += WEIGHT_R5[posIdx][i];
                not20++;
            }
        }

        if (remainder / not20 > 0.9 || !not20) {
            not20 = posIdx === 9 ? 11 : 14;
            remainderW1 = 1;
            remainderW2 = 5;
        }

        rec = fix2((rec + remainder * remainderW1 / not20 - 2) / 3);
        return { remainder, remainderW2, not20, ratingR, rec };
    };

    const calculateBaseR5 = (posIdx, skills, asi, rou) => {
        const r = calculateRemainders(posIdx, skills, asi);
        const routineBonus = (3 / 100) * (100 - 100 * Math.pow(Math.E, -rou * 0.035));
        const ratingR = r.ratingR + (r.remainder * r.remainderW2 / r.not20);
        return Number(fix2(ratingR + routineBonus * 5));
    };

    const calculateR5 = (posIdx, skills, asi, rou) => {
        let rating = calculateBaseR5(posIdx, skills, asi, rou);
        const rou2 = (3 / 100) * (100 - 100 * Math.pow(Math.E, -rou * 0.035));
        const r = calculateRemainders(posIdx, skills, asi);
        const { pow, E } = Math;

        // Build skill+remainder array
        const goldstar = skills.filter(s => s == 20).length;
        const skillsB = skills.map(s => s == 20 ? 20 : s * 1 + r.remainder / (skills.length - goldstar));

        // Apply routine to all except stamina (index 1)
        const sr = skillsB.map((s, i) => i === 1 ? s : s + rou2);

        if (skills.length !== 11) {
            const headerBonus = sr[10] > 12
                ? fix2((pow(E, (sr[10] - 10) ** 3 / 1584.77) - 1) * 0.8 +
                    pow(E, sr[0] ** 2 * 0.007 / 8.73021) * 0.15 +
                    pow(E, sr[6] ** 2 * 0.007 / 8.73021) * 0.05)
                : 0;
            const fkBonus = fix2(pow(E, (sr[13] + sr[12] + sr[9] * 0.5) ** 2 * 0.002) / 327.92526);
            const ckBonus = fix2(pow(E, (sr[13] + sr[8] + sr[9] * 0.5) ** 2 * 0.002) / 983.65770);
            const pkBonus = fix2(pow(E, (sr[13] + sr[11] + sr[9] * 0.5) ** 2 * 0.002) / 1967.31409);
            const allBonus = headerBonus * 1 + fkBonus * 1 + ckBonus * 1 + pkBonus * 1;

            const defSkillsSq = sr[0] ** 2 + sr[1] ** 2 * 0.5 + sr[2] ** 2 * 0.5 + sr[3] ** 2 + sr[4] ** 2 + sr[5] ** 2 + sr[6] ** 2;
            const gainBase = fix2(defSkillsSq / 6 / 22.9 ** 2);
            const offSkillsSq = sr[0] ** 2 * 0.5 + sr[1] ** 2 * 0.5 + sr[2] ** 2 + sr[3] ** 2 + sr[4] ** 2 + sr[5] ** 2 + sr[6] ** 2;
            const keepBase = fix2(offSkillsSq / 6 / 22.9 ** 2);

            const m = POS_MULTIPLIERS[posIdx];
            return fix2(rating + allBonus + gainBase * m * 1 + keepBase * m * 1);
        }

        return fix2(rating);
    };

    // ─── Skill extraction from tooltip ───────────────────────────────────
    const extractSkills = (player, posIdx) => {
        const checkSkills = player.skills.filter(s => s.value);
        let skills;

        if (posIdx === 9) {
            // GK: Str, Pac, Jum, Sta, One, Ref, Aer, Com, Kic, Thr, Han (reordered)
            skills = [0, 2, 4, 1, 3, 5, 6, 7, 8, 9, 10].map(i => checkSkills[i].value);
        } else {
            // Field: even indices first, then odd (matches tooltip layout)
            skills = [];
            for (let i = 0; i <= checkSkills.length; i += 2) {
                if (checkSkills[i]) skills.push(checkSkills[i].value);
            }
            for (let i = 1; i <= checkSkills.length; i += 2) {
                if (checkSkills[i]) skills.push(checkSkills[i].value);
            }
        }

        // Convert silver/gold star strings to numbers
        return skills.map(s => {
            if (typeof s !== 'string') return s;
            return (s.includes('silver') || s.includes('19')) ? 19 : 20;
        });
    };

    // ─── Player data fetching (with Promise-based cache) ─────────────────
    const tooltipCache = new Map();

    const fetchTooltip = playerId => {
        const pid = String(playerId);
        if (!tooltipCache.has(pid)) {
            tooltipCache.set(pid, new Promise((resolve, reject) => {
                $.post('/ajax/tooltip.ajax.php', { player_id: playerId })
                    .done(res => resolve(JSON.parse(res)))
                    .fail(reject);
            }));
        }
        return tooltipCache.get(pid);
    };

    const getPlayerData = (playerId, routineMap, positionMap) => {
        return fetchTooltip(playerId).then(rawData => {
            // Deep clone to avoid modifying cached data
            const player = JSON.parse(JSON.stringify(rawData.player));

            // Apply match-specific overrides
            if (routineMap.has(playerId)) player.routine = String(routineMap.get(playerId));
            if (positionMap.has(playerId)) player.favposition = positionMap.get(playerId);

            const asi = parseNum(player.skill_index);
            const xp = parseNum(player.routine);
            const positions = player.favposition.split(',');
            const posIdx = getPositionIndex(positions[0]);
            const skills = extractSkills(player, posIdx);

            let r5 = Number(calculateR5(posIdx, skills, asi, xp));
            let rec = Number(calculateRemainders(posIdx, skills, asi).rec);

            // Dual position — take the better rating
            if (positions.length > 1) {
                const posIdx2 = getPositionIndex(positions[1]);
                if (posIdx2 !== posIdx) {
                    const r5_2 = Number(calculateR5(posIdx2, skills, asi, xp));
                    const rec_2 = Number(calculateRemainders(posIdx2, skills, asi).rec);
                    if (r5_2 > r5) r5 = r5_2;
                    if (rec_2 > rec) rec = rec_2;
                }
            }

            return {
                Age: Number(player.age) * 12 + Number(player.months),
                REC: rec,
                R5: r5
            };
        });
    };

    // ─── Routine sharing logic ───────────────────────────────────────────
    const applyRoutineSharing = (group, routineMap) => {
        if (group.length <= 1) return;
        group.sort((a, b) => a.ROU - b.ROU);

        const min = group[0].ROU;
        if (min >= ROUTINE_CAP) return;

        const max = group[group.length - 1].ROU;
        const min2 = group[1].ROU;
        let newRoutine = Math.min(min + max * SHARE_BONUS, min2, ROUTINE_CAP);
        routineMap.set(group[0].ID, parseFloat(newRoutine.toFixed(1)));
    };

    // ─── Team statistics (sum of 11 starters) ───────────────────────────
    const computeTeamStats = (playerIds, lineup, routineMap, positionMap) => {
        const starters = playerIds.filter(id => !lineup[id].position.includes('sub'));
        const promises = starters.map(id => getPlayerData(id, routineMap, positionMap));

        return Promise.all(promises).then(players => {
            const totals = { Age: 0, REC: 0, R5: 0 };
            players.forEach(p => {
                totals.Age += p.Age;
                totals.REC += p.REC;
                totals.R5 += p.R5;
            });
            return totals;
        });
    };

    // ─── Match processing ────────────────────────────────────────────────
    const classifyLineup = (playerIds, lineup, routineMap, positionMap) => {
        const groups = { D: [], M: [], F: [] };
        playerIds.forEach(id => {
            const pos = lineup[id].position;
            if (pos.includes('sub')) return;
            const rou = Number(lineup[id].routine);
            positionMap.set(id, pos);
            routineMap.set(id, rou);
            groups[classifyPosition(pos)].push({ ID: id, ROLE: pos, ROU: rou });
        });
        return groups;
    };

    const isMatchPage = true;

    const injectStyles = () => {
        if (document.getElementById('tsa-match-style')) return;
        const style = document.createElement('style');
        style.id = 'tsa-match-style';
        style.textContent = `
            /* ── Match Dialog ── */
            .rnd-overlay {
                position: fixed; top:0; left:0; right:0; bottom:0;
                background: rgba(0,0,0,0.65);
                z-index: 10000;
                display: flex; align-items: center; justify-content: center;
            }
            .rnd-dialog {
                background: #1c3410; border: none;
                border-radius: 0; width: 100vw; height: 100vh;
                overflow: hidden; display: flex; flex-direction: column;
            }
            .rnd-dlg-head {
                background: linear-gradient(180deg, #162e0e 0%, #1c3a14 50%, #152c0d 100%);
                padding: 14px 16px 8px;
                position: relative;
                border-bottom: 2px solid rgba(80,160,48,.2);
                overflow: visible; z-index: 2;
            }
            .rnd-dlg-head-content {
                display: flex; flex-direction: column; align-items: center;
            }
            .rnd-dlg-head-row {
                display: flex; align-items: center;
                justify-content: center; width: 100%;
            }
            .rnd-dlg-team-group {
                display: flex; align-items: center; gap: 10px;
                flex: 1; min-width: 0;
            }
            .rnd-dlg-team-group.home { justify-content: flex-end; }
            .rnd-dlg-team-group.away { justify-content: flex-start; }
            .rnd-dlg-team-info {
                display: flex; flex-direction: column; gap: 3px; min-width: 0;
            }
            .rnd-dlg-team-group.home .rnd-dlg-team-info { align-items: flex-end; }
            .rnd-dlg-team-group.away .rnd-dlg-team-info { align-items: flex-start; }
            .rnd-dlg-team {
                color: #eaf6dc; font-weight: 700; font-size: 14px;
                letter-spacing: 0.3px; line-height: 1.2;
                white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
                max-width: 200px;
            }
            .rnd-dlg-chips {
                display: flex; gap: 3px; flex-wrap: wrap;
            }
            .rnd-dlg-team-group.home .rnd-dlg-chips { justify-content: flex-end; }
            .rnd-dlg-chip {
                font-size: 8.5px; font-weight: 600; color: #8cb878;
                background: rgba(0,0,0,.35); padding: 1px 5px;
                border-radius: 4px; white-space: nowrap;
                letter-spacing: 0.2px; line-height: 1.4;
                border: 1px solid rgba(255,255,255,.04);
            }
            .rnd-dlg-chip .chip-val { color: #c8e4b0; font-weight: 700; }
            .rnd-dlg-score-block {
                display: flex; flex-direction: column; align-items: center;
                flex-shrink: 0; padding: 0 14px;
            }
            .rnd-dlg-score {
                color: #ffffff; font-weight: 800; font-size: 32px;
                letter-spacing: 3px; line-height: 1;
                text-shadow: 0 0 20px rgba(128,224,64,.2), 0 1px 3px rgba(0,0,0,.5);
            }
            .rnd-dlg-datetime {
                text-align: center; margin-top: 2px;
                font-size: 10.5px; color: #6a9a58; letter-spacing: 0.3px;
                font-weight: 500;
            }
            .rnd-dlg-close {
                position: absolute; top: 6px; right: 6px;
                background: rgba(255,255,255,.05); border: none; border-radius: 50%;
                color: rgba(232,245,216,.4); font-size: 17px; cursor: pointer;
                width: 26px; height: 26px;
                display: flex; align-items: center; justify-content: center;
                transition: all 0.2s; z-index: 3; line-height: 1;
            }
            .rnd-dlg-close:hover { background: rgba(255,255,255,.15); color: #e8f5d8; transform: scale(1.1); }

            /* ── Live replay ── */
            .rnd-live-bar {
                display: flex; align-items: center; gap: 10px;
                background: #1a3a10; padding: 6px 24px;
                border-bottom: 1px solid #3d6828; justify-content: center;
            }
            .rnd-live-min {
                font-size: 16px; font-weight: 700; color: #80e040;
                min-width: 48px; text-align: center;
                animation: rnd-pulse 1.2s ease-in-out infinite;
            }
            @keyframes rnd-pulse { 0%,100%{opacity:1} 50%{opacity:.45} }
            .rnd-live-progress {
                flex: 1; max-width: 400px; height: 6px;
                background: #274a18; border-radius: 3px; overflow: hidden;
            }
            .rnd-live-progress-fill {
                height: 100%; background: linear-gradient(90deg, #4a9030, #80e040);
                border-radius: 3px; transition: width 0.4s;
            }
            .rnd-live-btn {
                background: none; border: 1px solid #5aa838; border-radius: 3px;
                color: #c8e0b4; font-size: 14px; cursor: pointer;
                width: 28px; height: 28px;
                display: flex; align-items: center; justify-content: center;
                transition: background 0.15s;
            }
            .rnd-live-btn:hover { background: rgba(255,255,255,0.1); }
            .rnd-live-label {
                font-size: 11px; color: #5a7a48; text-transform: uppercase;
                letter-spacing: 1px; font-weight: 600;
            }
            .rnd-live-ended .rnd-live-min { color: #90b878; animation: none; }
            .rnd-live-feed-line {
                padding: 6px 0; border-bottom: 1px solid #274a18;
                animation: rnd-feedIn 0.4s ease;
            }
            @keyframes rnd-feedIn { from{opacity:0;transform:translateY(-8px)} to{opacity:1;transform:translateY(0)} }
            .rnd-live-feed-min {
                font-size: 11px; font-weight: 700; color: #80e040;
                margin-right: 6px;
            }
            .rnd-live-feed-text { color: #c8e0b4; font-size: 13px; }
            .rnd-tabs {
                display: flex; background: #274a18;
                border-bottom: 1px solid #3d6828;
            }
            .rnd-tab {
                flex: 1; padding: 6px 8px; text-align: center;
                font-size: 11px; font-weight: 600; text-transform: uppercase;
                letter-spacing: 0.5px; color: #90b878; cursor: pointer;
                border-bottom: 2px solid transparent; transition: all 0.15s;
            }
            .rnd-tab:hover { color: #c8e0b4; background: #305820; }
            .rnd-tab.active { color: #e8f5d8; border-bottom-color: #6cc040; background: #305820; }
            .rnd-dlg-body {
                overflow-y: auto; padding: 8px 32px;
                flex: 1; color: #c8e0b4; font-size: 13px;
            }
            .rnd-event-row {
                display: flex; align-items: flex-start; gap: 8px;
                padding: 4px 0; border-bottom: 1px solid #325a1e;
            }
            .rnd-event-min { color: #90b878; font-weight: 600; min-width: 28px; text-align: right; }
            .rnd-event-icon { min-width: 18px; text-align: center; }
            .rnd-event-text { flex: 1; color: #c8e0b4; }
            /* ── Venue tab ── */
            .rnd-venue-wrap { max-width: 900px; margin: 0 auto; }
            .rnd-venue-hero {
                position: relative; border-radius: 14px; overflow: hidden;
                background: linear-gradient(135deg, #1a3d0f 0%, #2d5e1a 40%, #1a4a0e 100%);
                margin-bottom: 20px; padding: 30px 24px 24px;
                border: 1px solid #3d6828;
                box-shadow: 0 6px 24px rgba(0,0,0,0.4);
            }
            .rnd-venue-hero::before {
                content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0;
                background: repeating-linear-gradient(90deg, transparent, transparent 48%, rgba(255,255,255,0.02) 48%, rgba(255,255,255,0.02) 52%);
                pointer-events: none;
            }
            .rnd-venue-stadium-svg { display: block; margin: 0 auto 20px; opacity: 0.55; }
            .rnd-venue-name {
                text-align: center; font-size: 22px; font-weight: 800; color: #e8f5d8;
                letter-spacing: 0.5px; margin-bottom: 4px; text-shadow: 0 2px 8px rgba(0,0,0,0.3);
            }
            .rnd-venue-city {
                text-align: center; font-size: 13px; color: #a0c888; margin-bottom: 10px;
                letter-spacing: 1px; text-transform: uppercase;
            }
            .rnd-venue-tournament {
                text-align: center; margin-bottom: 0;
            }
            .rnd-venue-tournament span {
                display: inline-block; background: rgba(74,144,48,0.35); padding: 4px 14px;
                border-radius: 20px; font-size: 11px; color: #b8d8a0; letter-spacing: 0.5px;
                border: 1px solid rgba(144,184,120,0.2);
            }
            .rnd-venue-cards {
                display: grid; grid-template-columns: 1fr 1fr; gap: 14px; margin-bottom: 16px;
            }
            .rnd-venue-card {
                background: linear-gradient(145deg, #243d18, #1e3414);
                border: 1px solid #3d6828; border-radius: 12px; padding: 16px;
                text-align: center; position: relative; overflow: hidden;
            }
            .rnd-venue-card::after {
                content: ''; position: absolute; top: -20px; right: -20px;
                width: 60px; height: 60px; border-radius: 50%;
                background: rgba(74,144,48,0.1);
            }
            .rnd-venue-card-icon { font-size: 24px; margin-bottom: 6px; }
            .rnd-venue-card-value {
                font-size: 22px; font-weight: 800; color: #e8f5d8; margin-bottom: 2px;
            }
            .rnd-venue-card-label { font-size: 11px; color: #90b878; text-transform: uppercase; letter-spacing: 0.5px; }
            .rnd-venue-gauge-wrap {
                background: linear-gradient(145deg, #243d18, #1e3414);
                border: 1px solid #3d6828; border-radius: 12px; padding: 18px;
                margin-bottom: 16px;
            }
            .rnd-venue-gauge-header {
                display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;
            }
            .rnd-venue-gauge-title { font-size: 12px; color: #90b878; text-transform: uppercase; letter-spacing: 0.5px; }
            .rnd-venue-gauge-value { font-size: 14px; font-weight: 700; color: #e8f5d8; }
            .rnd-venue-gauge-bar {
                height: 10px; background: #162e0d; border-radius: 5px; overflow: hidden;
                position: relative;
            }
            .rnd-venue-gauge-fill {
                height: 100%; border-radius: 5px;
                transition: width 0.6s ease;
            }
            .rnd-venue-gauge-fill.attendance {
                background: linear-gradient(90deg, #4a9030, #6cc048, #8ae060);
            }
            .rnd-venue-gauge-fill.pitch {
                background: linear-gradient(90deg, #8B4513, #6aa030, #4a9030);
            }
            .rnd-venue-weather {
                background: linear-gradient(145deg, #243d18, #1e3414);
                border: 1px solid #3d6828; border-radius: 12px; padding: 20px;
                margin-bottom: 16px; display: flex; align-items: center; gap: 18px;
            }
            .rnd-venue-weather-icon { font-size: 48px; line-height: 1; }
            .rnd-venue-weather-info { flex: 1; }
            .rnd-venue-weather-text { font-size: 18px; font-weight: 700; color: #e8f5d8; margin-bottom: 2px; }
            .rnd-venue-weather-sub { font-size: 12px; color: #90b878; }
            .rnd-venue-facilities {
                display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; margin-bottom: 16px;
            }
            .rnd-venue-facility {
                background: linear-gradient(145deg, #243d18, #1e3414);
                border: 1px solid #3d6828; border-radius: 10px; padding: 12px 8px;
                text-align: center; transition: border-color 0.2s;
            }
            .rnd-venue-facility.active { border-color: #4a9030; background: linear-gradient(145deg, #2a4d1c, #234218); }
            .rnd-venue-facility-icon { font-size: 22px; margin-bottom: 4px; }
            .rnd-venue-facility-label { font-size: 10px; color: #90b878; text-transform: uppercase; letter-spacing: 0.3px; }
            .rnd-venue-facility .rnd-venue-facility-status {
                font-size: 10px; margin-top: 3px; color: #6b8a58; font-weight: 600;
            }
            .rnd-venue-facility.active .rnd-venue-facility-status { color: #8ae060; }

            /* ── Report tab ── */
            .rnd-report-event {
                border-bottom: 1px solid #325a1e; padding: 10px 0;
            }
            .rnd-report-event:last-child { border-bottom: none; }
            .rnd-report-min-header {
                color: #90b878; font-weight: 700; font-size: 12px;
                margin-bottom: 4px; text-transform: uppercase;
                letter-spacing: 0.5px;
            }
            .rnd-report-text {
                color: #c8e0b4; font-size: 13px; line-height: 1.6;
            }
            .rnd-report-text .rnd-goal-text { color: #80d848; font-weight: 700; }
            .rnd-report-text .rnd-yellow-text { color: #ffd700; }
            .rnd-report-text .rnd-red-text { color: #ff4c4c; font-weight: 700; }
            .rnd-report-text .rnd-sub-text { color: #5b9bff; }
            .rnd-report-text .rnd-player-name { color: #e8f5d8; font-weight: 600; }

            /* ── Dialog logos ── */
            .rnd-dlg-logo {
                width: 44px; height: 44px; flex-shrink: 0;
                filter: drop-shadow(0 2px 6px rgba(0,0,0,.5));
                object-fit: contain; pointer-events: none;
            }

            /* ── Statistics tab ── */
            .rnd-stats-wrap {
                max-width: 560px; margin: 0 auto; padding: 4px 0 12px;
            }
            .rnd-stats-team-header {
                display: flex; align-items: center; justify-content: space-between;
                padding: 10px 16px 14px; margin-bottom: 4px;
            }
            .rnd-stats-team-side {
                display: flex; align-items: center; gap: 10px;
            }
            .rnd-stats-team-side.away { flex-direction: row-reverse; }
            .rnd-stats-team-logo {
                width: 36px; height: 36px; object-fit: contain;
                filter: drop-shadow(0 2px 4px rgba(0,0,0,.3));
            }
            .rnd-stats-team-name {
                font-weight: 700; font-size: 14px; color: #e8f5d8;
                letter-spacing: 0.2px;
            }
            .rnd-stats-vs {
                font-size: 11px; color: #6a9a55; font-weight: 600;
                text-transform: uppercase; letter-spacing: 1px;
            }
            .rnd-stat-row {
                padding: 10px 16px;
            }
            .rnd-stat-header {
                display: flex; align-items: baseline; justify-content: space-between;
                margin-bottom: 5px;
            }
            .rnd-stat-val {
                font-weight: 800; font-size: 15px; min-width: 32px;
                font-variant-numeric: tabular-nums;
            }
            .rnd-stat-val.home { text-align: left; color: #80e048; }
            .rnd-stat-val.away { text-align: right; color: #5ba8f0; }
            .rnd-stat-val.leading { font-size: 17px; }
            .rnd-stat-label {
                font-weight: 600; color: #8aac72; font-size: 11px;
                text-transform: uppercase; letter-spacing: 0.8px;
            }
            .rnd-stat-bar-wrap {
                display: flex; height: 7px; border-radius: 4px;
                overflow: hidden; background: rgba(0,0,0,.18);
                gap: 2px;
            }
            .rnd-stat-seg {
                border-radius: 3px;
                transition: width 0.5s cubic-bezier(.4,0,.2,1);
                min-width: 3px;
            }
            .rnd-stat-seg.home {
                background: linear-gradient(90deg, #4a9030, #6cc048);
            }
            .rnd-stat-seg.away {
                background: linear-gradient(90deg, #3a7ab8, #5b9bff);
            }
            .rnd-stat-divider {
                height: 1px; margin: 0 16px;
                background: linear-gradient(90deg, transparent, #3d6828 20%, #3d6828 80%, transparent);
            }
            .rnd-stat-row-highlight {
                background: rgba(60,120,40,.06);
                border-radius: 8px; margin: 2px 8px;
                padding: 10px 12px;
            }

            /* ── Advanced Stats (Attacking Styles) ── */
            .rnd-adv-section {
                margin-top: 16px; padding-top: 12px;
                border-top: 1px solid #3d6828;
            }
            .rnd-adv-title {
                text-align: center; font-size: 11px; font-weight: 700;
                color: #8aac72; text-transform: uppercase; letter-spacing: 1.2px;
                margin-bottom: 10px;
            }
            .rnd-adv-team-label {
                font-size: 11px; font-weight: 700; color: #b0d898;
                text-transform: uppercase; letter-spacing: 0.8px;
                padding: 6px 12px 4px; margin-top: 6px;
            }
            .rnd-adv-table {
                width: 100%; border-collapse: collapse; font-size: 12px;
            }
            .rnd-adv-table th {
                padding: 5px 8px; font-size: 10px; font-weight: 700;
                color: #6a9a58; text-transform: uppercase; letter-spacing: 0.5px;
                border-bottom: 1px solid #2a4a1c; text-align: center;
            }
            .rnd-adv-table th:first-child { text-align: left; }
            .rnd-adv-row {
                cursor: pointer; transition: background 0.15s;
            }
            .rnd-adv-row:hover { background: rgba(255,255,255,.04); }
            .rnd-adv-row td {
                padding: 5px 8px; text-align: center;
                border-bottom: 1px solid rgba(42,74,28,.5);
                font-variant-numeric: tabular-nums;
            }
            .rnd-adv-row td:first-child {
                text-align: left; font-weight: 600; color: #c8e0b4;
            }
            .rnd-adv-row td.adv-zero { color: #4a6a3a; }
            .rnd-adv-row td.adv-goal { color: #80e048; font-weight: 700; }
            .rnd-adv-row td.adv-shot { color: #c8d868; }
            .rnd-adv-row td.adv-lost { color: #c87848; }
            .rnd-adv-row .adv-arrow {
                display: inline-block; font-size: 9px; margin-left: 4px;
                transition: transform 0.2s; color: #6a9a58;
            }
            .rnd-adv-row.expanded .adv-arrow { transform: rotate(90deg); }
            .rnd-adv-row.rnd-adv-total td {
                font-weight: 800; border-top: 1px solid #3d6828;
                color: #e0f0cc; cursor: default;
            }
            .rnd-adv-row.rnd-adv-total td:first-child { color: #8aac72; }
            .rnd-adv-events {
                display: none;
            }
            .rnd-adv-events.visible { display: table-row; }
            .rnd-adv-events td {
                padding: 0; border-bottom: 1px solid rgba(42,74,28,.3);
            }
            .rnd-adv-evt-list {
                padding: 4px 0 6px 0; font-size: 11px;
            }
            .rnd-adv-evt {
                padding: 2px 0; color: #a0c088;
                display: flex; align-items: stretch; gap: 0;
                border-bottom: 1px solid rgba(42,74,28,.25);
            }
            .rnd-adv-evt:last-child { border-bottom: none; }
            .rnd-adv-evt .adv-result-tag {
                font-size: 9px; font-weight: 700; padding: 6px 5px 0;
                text-transform: uppercase; white-space: nowrap;
                min-width: 52px; text-align: center;
                align-self: flex-start;
            }
            .rnd-adv-evt .adv-result-tag.goal { color: #80e048; }
            .rnd-adv-evt .adv-result-tag.shot { color: #c8d868; }
            .rnd-adv-evt .adv-result-tag.lost { color: #c87848; }
            .rnd-adv-evt .rnd-acc { flex: 1; border-bottom: none; }
            .rnd-adv-evt .rnd-acc-head { padding: 4px 6px; min-height: auto; }
            .rnd-adv-evt .rnd-acc-min { font-size: 11px; min-width: 28px; }
            .rnd-adv-evt .rnd-acc-body { padding: 0 6px 6px; }
            .rnd-adv-evt .rnd-player-name { color: #e0f0cc; font-weight: 600; }

            /* ── Player Card Dialog ── */
            .rnd-plr-overlay {
                position: fixed; top: 0; left: 0; right: 0; bottom: 0;
                background: rgba(0,0,0,.7); z-index: 100002;
                display: flex; align-items: center; justify-content: center;
                animation: rndFadeIn .15s ease;
            }
            .rnd-plr-dialog {
                background: linear-gradient(160deg, #1a3d0f 0%, #0e2508 60%, #122a0a 100%);
                border: 1px solid #3d6828; border-radius: 14px;
                width: 680px; max-width: 96vw; max-height: 88vh;
                overflow-y: auto; color: #c8e0b4;
                box-shadow: 0 12px 60px rgba(0,0,0,.7), 0 0 0 1px rgba(74,144,48,.15);
            }
            .rnd-plr-header {
                display: flex; align-items: center; gap: 16px;
                padding: 20px 24px 16px;
                background: linear-gradient(180deg, rgba(42,74,28,.3) 0%, transparent 100%);
                border-bottom: 1px solid #2a4a1c; position: relative;
            }
            .rnd-plr-face {
                width: 84px; height: 84px; border-radius: 50%;
                border: 3px solid #4a9030; overflow: hidden;
                flex-shrink: 0; background: #0e200a;
                box-shadow: 0 4px 16px rgba(0,0,0,.4);
            }
            .rnd-plr-face img { width: 100%; height: 100%; object-fit: cover; }
            .rnd-plr-info { flex: 1; min-width: 0; }
            .rnd-plr-name-row {
                display: flex; align-items: center; gap: 8px; margin-bottom: 4px;
            }
            .rnd-plr-name {
                font-size: 20px; font-weight: 800; color: #e0f0cc;
                text-decoration: none; cursor: pointer;
            }
            .rnd-plr-name:hover { color: #fff; text-decoration: underline; }
            .rnd-plr-link {
                color: #6a9a58; font-size: 14px; text-decoration: none;
                transition: color .15s;
            }
            .rnd-plr-link:hover { color: #80e048; }
            .rnd-plr-badges {
                display: flex; gap: 6px; flex-wrap: wrap; margin-top: 2px;
            }
            .rnd-plr-badge {
                display: inline-flex; align-items: center; gap: 4px;
                background: rgba(42,74,28,.5); border: 1px solid #2a4a1c;
                border-radius: 12px; padding: 2px 8px;
                font-size: 11px; color: #8aac72;
            }
            .rnd-plr-badge .badge-icon { font-size: 12px; }
            .rnd-plr-rating-wrap {
                text-align: center; flex-shrink: 0; min-width: 64px;
            }
            .rnd-plr-rating-big {
                font-size: 32px; font-weight: 900; line-height: 1;
            }
            .rnd-plr-rating-label {
                font-size: 9px; color: #6a9a58; text-transform: uppercase;
                letter-spacing: 0.5px; margin-top: 2px;
            }
            .rnd-plr-close {
                position: absolute; top: 10px; right: 14px;
                background: rgba(42,74,28,.4); border: 1px solid #2a4a1c;
                color: #8aac72; width: 28px; height: 28px; border-radius: 50%;
                font-size: 16px; cursor: pointer; display: flex;
                align-items: center; justify-content: center;
                transition: all .15s;
            }
            .rnd-plr-close:hover { background: rgba(74,144,48,.3); color: #e0f0cc; }
            .rnd-plr-body { padding: 16px 24px 20px; }
            .rnd-plr-stats-row {
                display: grid; grid-template-columns: repeat(5, 1fr);
                gap: 8px; margin-bottom: 16px;
            }
            .rnd-plr-stat-card {
                background: rgba(42,74,28,.35); border: 1px solid #2a4a1c;
                border-radius: 8px; padding: 10px 4px 8px;
                text-align: center; transition: background .15s;
            }
            .rnd-plr-stat-card:hover { background: rgba(42,74,28,.55); }
            .rnd-plr-stat-icon { font-size: 16px; margin-bottom: 2px; }
            .rnd-plr-stat-val {
                font-size: 22px; font-weight: 800; color: #e0f0cc; line-height: 1.1;
            }
            .rnd-plr-stat-lbl {
                font-size: 9px; color: #6a9a58; text-transform: uppercase;
                letter-spacing: 0.3px; margin-top: 2px;
            }
            .rnd-plr-stat-card.green .rnd-plr-stat-val { color: #66dd44; }
            .rnd-plr-stat-card.red .rnd-plr-stat-val { color: #ee6633; }
            .rnd-plr-stat-card.gold .rnd-plr-stat-val { color: #f0d040; }
            .rnd-plr-section-title {
                font-size: 10px; font-weight: 700; color: #6a9a58;
                text-transform: uppercase; letter-spacing: 1px;
                margin: 14px 0 8px; padding-bottom: 5px;
                border-bottom: 1px solid #2a4a1c;
                display: flex; align-items: center; gap: 6px;
            }
            .rnd-plr-section-title .sec-icon { font-size: 13px; }

            /* ── Player Card Profile Section ── */
            .rnd-plr-profile-wrap {
                background: rgba(42,74,28,.25); border: 1px solid #2a4a1c;
                border-radius: 10px; padding: 12px 14px; margin-bottom: 16px;
            }
            .rnd-plr-profile-loading {
                text-align: center; padding: 16px; color: #5a7a48; font-size: 12px;
            }
            .rnd-plr-country-row {
                display: flex; align-items: center; gap: 6px;
                margin-bottom: 10px; padding-bottom: 8px;
                border-bottom: 1px solid rgba(42,74,28,.4);
            }
            .rnd-plr-country-flag {
                height: 14px; vertical-align: -1px;
            }
            .rnd-plr-country-name {
                font-size: 11px; color: #8aac72; font-weight: 600;
            }
            .rnd-plr-skills-grid {
                display: grid; grid-template-columns: 1fr 1fr;
                gap: 0 20px; margin-bottom: 12px;
            }
            .rnd-plr-skill-row {
                display: flex; align-items: center; justify-content: space-between;
                padding: 2px 6px; border-radius: 3px;
                transition: background .1s;
            }
            .rnd-plr-skill-row:hover { background: rgba(42,74,28,.4); }
            .rnd-plr-skill-name {
                font-size: 10px; color: #8abc78; font-weight: 600;
                text-transform: uppercase; letter-spacing: .3px;
            }
            .rnd-plr-skill-val {
                font-size: 12px; font-weight: 800; min-width: 22px;
                text-align: right;
            }
            .rnd-plr-skill-star { color: #d4af37; }
            .rnd-plr-skill-star.silver { color: #c0c0c0; }
            .rnd-plr-profile-footer {
                display: grid; grid-template-columns: repeat(4, 1fr);
                gap: 8px; padding-top: 10px;
                border-top: 1px solid rgba(42,74,28,.4);
            }
            .rnd-plr-profile-stat {
                text-align: center; padding: 6px 4px;
                background: rgba(0,0,0,.15); border-radius: 6px;
            }
            .rnd-plr-profile-stat-val {
                font-size: 16px; font-weight: 800; line-height: 1.2;
            }
            .rnd-plr-profile-stat-lbl {
                font-size: 8px; color: #6a9a58; text-transform: uppercase;
                letter-spacing: .5px; margin-top: 2px; font-weight: 700;
            }

            /* ── Tactics cards ── */
            .rnd-tactics-section {
                margin-top: 6px; padding: 6px;
                background: linear-gradient(180deg, rgba(20,40,14,.6), rgba(16,32,10,.8));
                border-radius: 8px; border: 1px solid #2a4a1c;
            }
            .rnd-tactics-grid { display: flex; flex-direction: column; gap: 0; }
            .rnd-tactic-row {
                display: flex; align-items: center; gap: 6px;
                padding: 5px 8px;
                border-bottom: 1px solid rgba(60,100,40,.15);
            }
            .rnd-tactic-row:last-child { border-bottom: none; }
            .rnd-tactic-row.r5-row {
                padding: 7px 8px; margin-bottom: 2px;
                background: rgba(0,0,0,.12); border-radius: 6px;
                border-bottom: none;
            }
            .rnd-tactic-icon {
                font-size: 12px; line-height: 1; width: 18px;
                text-align: center; flex-shrink: 0;
            }
            .rnd-tactic-label {
                font-size: 9px; color: #7a9a68; text-transform: uppercase;
                letter-spacing: 0.6px; font-weight: 700; min-width: 52px;
                flex-shrink: 0;
            }
            .rnd-tactic-meter {
                flex: 1; height: 4px; background: rgba(0,0,0,.25); border-radius: 2px;
                overflow: hidden;
            }
            .rnd-tactic-meter-fill {
                height: 100%; border-radius: 2px; transition: width 0.4s ease;
            }
            .rnd-tactic-meter-fill.home { background: linear-gradient(90deg, #3a7025, #6cc048); }
            .rnd-tactic-meter-fill.away { background: linear-gradient(90deg, #3a70b0, #5b9bff); }
            .rnd-tactic-value {
                font-size: 10px; font-weight: 700; color: #d0e8c0;
                min-width: 0; text-align: right;
                white-space: nowrap;
            }
            .rnd-tactic-value-pill {
                font-size: 9px; font-weight: 700; padding: 1px 6px;
                border-radius: 4px; white-space: nowrap;
            }
            .rnd-tactic-value-pill.home {
                background: rgba(80,160,50,.15); color: #80d848;
            }
            .rnd-tactic-value-pill.away {
                background: rgba(60,120,200,.15); color: #6ab0ff;
            }
            .rnd-tactic-focus-icon {
                font-size: 13px; line-height: 1;
            }

            /* ── Report event badges ── */
            .rnd-report-evt-badge {
                display: inline-flex; align-items: center; gap: 6px;
                padding: 5px 12px; border-radius: 4px; margin-bottom: 6px;
                font-size: 12px; font-weight: 600;
            }
            .rnd-report-evt-badge.evt-goal { background: rgba(80,200,60,0.15); color: #80d848; }
            .rnd-report-evt-badge.evt-yellow { background: rgba(255,215,0,0.12); color: #ffd700; }
            .rnd-report-evt-badge.evt-red { background: rgba(255,76,76,0.12); color: #ff4c4c; }
            .rnd-report-evt-badge.evt-sub { background: rgba(91,155,255,0.12); color: #5b9bff; }
            .rnd-report-evt-badge.evt-injury { background: rgba(255,140,60,0.12); color: #ff8c3c; }

            /* ── Details timeline ── */
            .rnd-timeline { margin-top: 16px; }
            .rnd-tl-row {
                display: flex; align-items: center;
                border-bottom: 1px solid #325a1e;
                padding: 8px 0; min-height: 32px;
            }
            .rnd-tl-row:last-child { border-bottom: none; }
            .rnd-tl-goal { background: rgba(80,200,60,0.08); }
            .rnd-tl-home {
                flex: 1; text-align: right; padding-right: 14px;
                color: #e0f0cc; font-size: 13px;
            }
            .rnd-tl-min {
                width: 44px; text-align: center; flex-shrink: 0;
                color: #90b878; font-weight: 700; font-size: 12px;
                background: #274a18; border-radius: 3px; padding: 2px 0;
            }
            .rnd-tl-away {
                flex: 1; text-align: left; padding-left: 14px;
                color: #e0f0cc; font-size: 13px;
            }

            /* ── Report accordion ── */
            .rnd-acc { border-bottom: 1px solid #325a1e; }
            .rnd-acc:last-child { border-bottom: none; }
            .rnd-acc-head {
                display: flex; align-items: center;
                padding: 8px 0; min-height: 32px; cursor: pointer;
                transition: background 0.15s;
            }
            .rnd-acc-head:hover { background: rgba(255,255,255,0.03); }
            .rnd-acc-goal { background: rgba(80,200,60,0.08); }
            .rnd-acc-home {
                flex: 1; text-align: right; padding-right: 14px;
                color: #e0f0cc; font-size: 13px;
            }
            .rnd-acc-min {
                width: 44px; text-align: center; flex-shrink: 0;
                color: #90b878; font-weight: 700; font-size: 12px;
                background: #274a18; border-radius: 3px; padding: 2px 0;
            }
            .rnd-acc-away {
                flex: 1; text-align: left; padding-left: 14px;
                color: #e0f0cc; font-size: 13px;
            }
            .rnd-acc-body {
                display: none; padding: 8px 14px 12px;
                background: rgba(0,0,0,0.15); border-radius: 0 0 4px 4px;
            }
            .rnd-acc.open .rnd-acc-body { display: block; }
            .rnd-acc-chevron {
                width: 14px; height: 14px; flex-shrink: 0;
                fill: #5a7a48; transition: transform 0.2s;
                margin: 0 4px;
            }
            .rnd-acc.open .rnd-acc-chevron { transform: rotate(90deg); }

            /* ── Lineups tab ── */
            .rnd-lu-outer { display: flex; flex-direction: column; }
            .rnd-lu-wrap { display: flex; gap: 0; }
            .rnd-lu-list {
                flex: 0 0 25%; font-size: 12px;
                padding: 0 8px; box-sizing: border-box;
            }
            .rnd-lu-list-title {
                font-weight: 700; font-size: 13px; color: #e8f5d8;
                padding: 6px 0; border-bottom: 1px solid #3d6828;
                margin-bottom: 4px; display: flex; align-items: center; gap: 8px;
            }
            .rnd-lu-list-title img { width: 24px; height: 24px; }
            .rnd-lu-badge {
                font-size: 10px; font-weight: 600; color: #b8d8a0; background: rgba(0,0,0,.2);
                padding: 2px 6px; border-radius: 3px; margin-left: auto; white-space: nowrap;
            }
            .rnd-lu-badge + .rnd-lu-badge { margin-left: 4px; }
            .rnd-lu-player {
                display: flex; align-items: center; gap: 6px;
                padding: 4px 0; border-bottom: 1px solid #274a18;
            }
            .rnd-lu-player:last-child { border-bottom: none; }
            .rnd-lu-clickable { cursor: pointer; transition: background .15s; }
            .rnd-lu-clickable:hover { background: rgba(74,144,48,.15); }
            .rnd-lu-no {
                width: 22px; height: 22px; border-radius: 50%;
                display: flex; align-items: center; justify-content: center;
                font-size: 10px; font-weight: 700; flex-shrink: 0;
            }
            .rnd-lu-name { flex: 1; color: #c8e0b4; font-size: 12px; }
            .rnd-lu-pos { color: #90b878; font-size: 10px; text-transform: uppercase; width: 30px; text-align: center; }
            .rnd-lu-rating { font-weight: 700; font-size: 12px; width: 32px; text-align: right; }
            .rnd-lu-r5 {
                font-weight: 700; font-size: 10px; min-width: 36px;
                text-align: center; border-radius: 10px;
                padding: 1px 5px; color: #fff; flex-shrink: 0;
                background: #3a5a2a;
            }
            .rnd-lu-sub-header {
                font-size: 11px; color: #5a7a48; text-transform: uppercase;
                letter-spacing: 1px; padding: 8px 0 4px; font-weight: 600;
            }
            .rnd-lu-captain {
                font-size: 10px; font-weight: 800; color: #ffd700;
                margin-left: 2px;
            }
            .rnd-lu-mom {
                font-size: 10px; margin-left: 2px;
            }
            .rnd-pitch-captain {
                position: absolute; top: 50%; left: 50%;
                transform: translate(30%, -100%);
                font-size: 9px; font-weight: 900; color: #fff;
                background: #d4a017; border-radius: 50%;
                width: 16px; height: 16px;
                display: flex; align-items: center; justify-content: center;
                z-index: 4; box-shadow: 0 1px 3px rgba(0,0,0,0.5);
                border: 1.5px solid #ffd700;
            }
            .rnd-pitch-mom {
                position: absolute; top: 50%; left: 50%;
                transform: translate(-130%, -100%);
                font-size: 12px; z-index: 4;
                filter: drop-shadow(0 1px 2px rgba(0,0,0,0.6));
            }

            /* Pitch */
            .rnd-pitch-wrap { flex: 0 0 50%; display: flex; flex-direction: column; align-items: center; justify-content: start; gap: 8px; }
            /* Unity 3D viewport row: feed | viewport | stats */
            .rnd-unity-row {
                display: flex; gap: 0; width: 100%;
                margin-bottom: 8px; align-items: stretch;
            }
            .rnd-unity-feed {
                flex: 0 0 25%; min-width: 0; display: flex; flex-direction: column;
                gap: 3px; overflow-y: auto; overflow-x: hidden;
                scrollbar-width: none; /* Firefox */
                padding: 6px 8px; box-sizing: border-box;
                max-height: 0; /* will be set dynamically via JS */
            }
            .rnd-unity-feed::-webkit-scrollbar { display: none; } /* Chrome/Edge */
            .rnd-unity-feed-line {
                display: flex; align-items: baseline; gap: 5px;
                font-size: 13px; color: #b8d8a0; line-height: 1.4;
                padding: 2px 0;
                animation: rnd-fade-in 0.4s ease;
            }
            .rnd-unity-feed-min {
                font-size: 9px; font-weight: 700; color: #80e040;
                background: rgba(0,0,0,0.3); border-radius: 3px;
                padding: 1px 4px; white-space: nowrap; flex-shrink: 0;
            }
            .rnd-unity-feed-text { color: #c8e0b4; }
            .rnd-unity-feed-text .rnd-player-name { color: #e8f5d8; font-weight: 600; }
            @keyframes rnd-fade-in { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: translateY(0); } }
            .rnd-unity-stats {
                flex: 0 0 25%; display: flex; flex-direction: column;
                gap: 0; padding: 6px; box-sizing: border-box;
                font-size: 11px; overflow-y: auto;
                background: rgba(16,32,10,.4);
                border-radius: 8px; border: 1px solid rgba(60,100,40,.2);
            }
            .rnd-unity-stat-row {
                padding: 5px 6px;
                border-bottom: 1px solid rgba(60,100,40,.12);
                transition: background .2s;
            }
            .rnd-unity-stat-row:last-child { border-bottom: none; }
            .rnd-unity-stat-row:hover { background: rgba(60,120,40,.08); }
            .rnd-unity-stat-hdr {
                display: flex; align-items: center; justify-content: space-between;
                margin-bottom: 3px;
            }
            .rnd-unity-stat-hdr .val {
                font-weight: 800; font-size: 13px; min-width: 18px;
                font-variant-numeric: tabular-nums;
            }
            .rnd-unity-stat-hdr .val.home { text-align: left; color: #80e048; }
            .rnd-unity-stat-hdr .val.away { text-align: right; color: #5ba8f0; }
            .rnd-unity-stat-hdr .val.lead { font-size: 15px; }
            .rnd-unity-stat-label {
                font-size: 8px; color: #6a9a55; text-transform: uppercase;
                letter-spacing: 0.8px; font-weight: 700; text-align: center; flex: 1;
            }
            .rnd-unity-stat-bar {
                display: flex; height: 5px; border-radius: 3px; overflow: hidden;
                background: rgba(0,0,0,.2); gap: 2px;
            }
            .rnd-unity-stat-bar .seg {
                transition: width 0.5s cubic-bezier(.4,0,.2,1);
                min-width: 2px; border-radius: 2px;
            }
            .rnd-unity-stat-bar .seg.home { background: linear-gradient(90deg, #4a9030, #6cc048); }
            .rnd-unity-stat-bar .seg.away { background: linear-gradient(90deg, #3a7ab8, #5b9bff); }
            .rnd-unity-viewport {
                position: relative; border: 2px solid #4a9030;
                border-radius: 8px; overflow: hidden;
                background: #0a0a0a;
                width: 100%; max-width: 400px;
                margin: 0 auto;
                aspect-ratio: 780 / 447;
            }
            .rnd-unity-viewport .webgl-content {
                position: relative !important;
                top: auto !important; left: auto !important;
                right: auto !important; bottom: auto !important;
                transform: none !important;
                width: 100% !important; height: 100% !important;
                margin: 0 !important; padding: 0 !important;
                display: block !important;
            }
            .rnd-unity-viewport #gameContainer {
                position: relative !important;
                top: auto !important; left: auto !important;
                right: auto !important; bottom: auto !important;
                transform: none !important;
                width: 100% !important; height: 100% !important;
                margin: 0 !important; padding: 0 !important;
            }
            .rnd-unity-viewport #gameContainer .footer { display: none !important; }
            .rnd-unity-viewport canvas {
                width: 100% !important; height: 100% !important;
                display: block !important;
                object-fit: contain;
            }
            /* Hide datetime & show inline time when live */
            .rnd-dlg-head.rnd-live-active .rnd-dlg-datetime { display: none; }
            .rnd-dlg-head-time {
                display: none; gap: 8px; align-items: center;
                justify-content: center; margin-top: 6px;
                padding-top: 6px;
                border-top: 1px solid rgba(80,160,48,.12);
            }
            .rnd-dlg-head.rnd-live-active .rnd-dlg-head-time { display: flex; }
            .rnd-dlg-head-time .rnd-live-min {
                font-size: 14px; font-weight: 800; color: #80e040;
                background: rgba(0,0,0,.45); padding: 2px 10px;
                border-radius: 8px; min-width: 48px; text-align: center;
                letter-spacing: 0.5px;
                box-shadow: 0 0 10px rgba(128,224,64,.15);
                animation: rnd-pulse 1.2s ease-in-out infinite;
            }
            .rnd-dlg-head-time .rnd-live-progress {
                flex: 1; max-width: 180px; height: 4px;
                background: rgba(0,0,0,.4); border-radius: 2px; overflow: hidden;
            }
            .rnd-dlg-head-time .rnd-live-progress-fill {
                height: 100%; border-radius: 2px; transition: width 0.4s;
                background: linear-gradient(90deg, #4a9030, #80e040);
                box-shadow: 0 0 6px rgba(128,224,64,.3);
            }
            .rnd-dlg-head-time .rnd-live-btn {
                background: rgba(255,255,255,.06); border: 1px solid rgba(255,255,255,.1);
                border-radius: 50%; color: #a0d090; font-size: 12px;
                cursor: pointer; width: 26px; height: 26px;
                display: flex; align-items: center; justify-content: center;
                transition: all 0.2s;
            }
            .rnd-dlg-head-time .rnd-live-btn:hover {
                background: rgba(255,255,255,.14); border-color: rgba(255,255,255,.25);
                transform: scale(1.1);
            }
            .rnd-live-filter-group {
                display: flex; gap: 1px;
                background: rgba(0,0,0,.35); border-radius: 10px;
                padding: 2px;
            }
            .rnd-live-filter-btn {
                background: none; border: none; border-radius: 8px;
                color: #7aaa68; font-size: 10px; font-weight: 600;
                cursor: pointer; padding: 2px 8px;
                transition: all 0.2s; white-space: nowrap;
                letter-spacing: 0.3px; text-transform: uppercase;
            }
            .rnd-live-filter-btn:hover { color: #b8dca8; }
            .rnd-live-filter-btn.active {
                background: rgba(108,192,64,.2); color: #80e040;
                box-shadow: 0 0 6px rgba(128,224,64,.15);
            }
            .rnd-live-filter-btn.live-btn.active {
                background: rgba(220,40,40,.2); color: #ff4444;
                box-shadow: 0 0 8px rgba(255,60,60,.25);
            }
            .rnd-live-filter-btn.live-btn::before {
                content: ''; display: inline-block;
                width: 6px; height: 6px; border-radius: 50%;
                background: #ff4444; margin-right: 4px;
                vertical-align: middle;
            }
            .rnd-live-filter-btn.live-btn.active::before {
                animation: rnd-live-dot 1.2s ease-in-out infinite;
            }
            @keyframes rnd-live-dot { 0%,100%{opacity:1} 50%{opacity:.3} }
            .rnd-live-filter-btn:disabled {
                opacity: 0.35; cursor: not-allowed;
                pointer-events: none;
            }
            .rnd-r5-compare { display: flex; gap: 12px; width: 100%; justify-content: center; align-items: center; }
            .rnd-r5-side { display: flex; align-items: center; gap: 6px; flex: 1; }
            .rnd-r5-side.away { flex-direction: row-reverse; }
            .rnd-r5-side-label { font-size: 11px; color: #8ab87a; white-space: nowrap; font-weight: 600; }
            .rnd-r5-side-meter { flex: 1; height: 8px; background: rgba(0,0,0,.25); border-radius: 4px; overflow: hidden; }
            .rnd-r5-side-meter-fill { height: 100%; border-radius: 4px; transition: width .6s ease; }
            .rnd-r5-side-meter-fill.home { background: linear-gradient(90deg, #6cbf4a, #a8e06a); }
            .rnd-r5-side-meter-fill.away { background: linear-gradient(90deg, #e06a6a, #f0a0a0); }
            .rnd-r5-side-val { font-size: 13px; font-weight: 700; min-width: 32px; text-align: center; }
            .rnd-pitch {
                position: relative; width: 100%;
                background: linear-gradient(90deg, #2d6b1e 0%, #357a22 50%, #2d6b1e 100%);
                border: 2px solid #4a9030; border-radius: 6px; overflow: hidden;
            }
            .rnd-pitch-lines {
                position: absolute; top: 0; left: 0; width: 100%; height: 100%;
                pointer-events: none; z-index: 0;
            }
            .rnd-pitch-grid {
                position: relative; z-index: 1;
                display: grid;
                grid-template-columns: repeat(12, 8.333%);
                grid-template-rows: repeat(5, 20%);
                width: 100%; aspect-ratio: 3 / 2;
            }
            .rnd-pitch-cell {
                position: relative; overflow: visible;
            }
            .rnd-pitch-face {
                position: absolute; top: 50%; left: 50%;
                transform: translate(-50%, -50%);
                width: 70%; max-width: 48px; aspect-ratio: 1;
                border-radius: 50%; overflow: hidden;
                box-shadow: 0 2px 6px rgba(0,0,0,0.4);
                z-index: 2;
            }
            .rnd-pitch-face img {
                width: 100%; height: 100%; object-fit: cover;
                border-radius: 50%;
            }
            .rnd-pitch-info {
                position: absolute; top: 50%; left: 50%;
                transform: translate(-50%, 0);
                margin-top: 40%;
                display: flex; flex-direction: column; align-items: center;
                z-index: 3; pointer-events: none;
            }
            .rnd-pitch-label {
                font-size: 10px; color: #fff;
                text-shadow: 0 1px 3px rgba(0,0,0,0.9);
                white-space: nowrap;
                text-align: center;
                font-weight: 600; line-height: 1.2;
            }
            .rnd-pitch-rating {
                font-size: 9px; font-weight: 700;
                padding: 0 3px; border-radius: 3px;
                background: rgba(0,0,0,0.4); line-height: 1.3;
            }
            .rnd-pitch-events {
                display: flex; gap: 1px; flex-wrap: wrap;
                justify-content: center; font-size: 9px;
            }
            /* ── Pitch hover tooltip ── */
            .rnd-pitch-cell[data-pid] { cursor: pointer; }
            .rnd-pitch-tooltip {
                position: fixed; z-index: 100001;
                background: linear-gradient(135deg, #1a2e14 0%, #243a1a 100%);
                border: 1px solid #4a9030; border-radius: 8px;
                padding: 10px 12px; min-width: 200px; max-width: 280px;
                box-shadow: 0 6px 24px rgba(0,0,0,0.6);
                pointer-events: none; font-size: 11px; color: #c8e0b4;
                opacity: 0; transition: opacity .15s ease;
            }
            .rnd-pitch-tooltip.visible { opacity: 1; }
            .rnd-pitch-tooltip-header {
                display: flex; align-items: center; gap: 8px;
                margin-bottom: 8px; padding-bottom: 6px;
                border-bottom: 1px solid rgba(74,144,48,0.3);
            }
            .rnd-pitch-tooltip-name { font-size: 13px; font-weight: 700; color: #e0f0cc; }
            .rnd-pitch-tooltip-pos { font-size: 10px; color: #8abc78; font-weight: 600; }
            .rnd-pitch-tooltip-badges { display: flex; gap: 6px; margin-left: auto; }
            .rnd-pitch-tooltip-badge {
                font-size: 10px; font-weight: 700; padding: 2px 6px;
                border-radius: 4px; background: rgba(0,0,0,0.3);
            }
            .rnd-pitch-tooltip-skills {
                display: flex; gap: 12px; margin-bottom: 6px;
            }
            .rnd-pitch-tooltip-skills-col {
                flex: 1; min-width: 0;
            }
            .rnd-pitch-tooltip-skill {
                display: flex; justify-content: space-between;
                padding: 1px 0; border-bottom: 1px solid rgba(74,144,48,0.12);
            }
            .rnd-pitch-tooltip-skill-name { color: #8abc78; font-size: 10px; }
            .rnd-pitch-tooltip-skill-val { font-weight: 700; font-size: 11px; }
            .rnd-pitch-tooltip-footer {
                display: flex; gap: 8px; justify-content: center;
                padding-top: 6px; border-top: 1px solid rgba(74,144,48,0.3);
            }
            .rnd-pitch-tooltip-stat {
                text-align: center;
            }
            .rnd-pitch-tooltip-stat-val { font-size: 14px; font-weight: 800; }
            .rnd-pitch-tooltip-stat-lbl { font-size: 9px; color: #6a9a58; text-transform: uppercase; }
            .rnd-pitch-tooltip-loading {
                text-align: center; padding: 8px; color: #6a9a58; font-size: 10px;
            }
            .rnd-lu-events {
                display: flex; gap: 1px; flex-shrink: 0; font-size: 11px;
                margin-left: 2px;
            }

            /* H2H tab */
            .rnd-h2h-wrap { max-width: 640px; margin: 0 auto; padding: 8px 0 16px; }

            /* ── Summary cards ── */
            .rnd-h2h-summary {
                display: flex; gap: 10px; margin-bottom: 16px;
                justify-content: center;
            }
            .rnd-h2h-section {
                flex: 1; background: rgba(0,0,0,.2);
                border-radius: 10px; padding: 14px 16px 10px;
                text-align: center; border: 1px solid rgba(255,255,255,.04);
            }
            .rnd-h2h-section-title {
                font-size: 10px; color: #6a9a58; text-transform: uppercase;
                letter-spacing: 1.2px; margin-bottom: 10px; font-weight: 700;
            }
            .rnd-h2h-bar-wrap {
                display: flex; height: 28px; border-radius: 6px;
                overflow: hidden; margin-bottom: 8px;
                background: rgba(0,0,0,.2);
            }
            .rnd-h2h-bar {
                display: flex; align-items: center; justify-content: center;
                font-size: 12px; font-weight: 800; color: #fff;
                min-width: 30px; transition: width 0.5s ease;
            }
            .rnd-h2h-bar.home { background: linear-gradient(135deg, #3d8a28, #5ab03a); }
            .rnd-h2h-bar.draw { background: rgba(255,255,255,.08); color: #8a9a7a; }
            .rnd-h2h-bar.away { background: linear-gradient(135deg, #2a6aa0, #4a8ac8); }
            .rnd-h2h-legend {
                display: flex; justify-content: space-between;
                font-size: 10px; color: #7aaa68; font-weight: 500;
            }

            /* ── Overall record strip ── */
            .rnd-h2h-record {
                display: flex; align-items: center; justify-content: center;
                gap: 24px; padding: 10px 0 14px;
                border-bottom: 1px solid rgba(80,160,48,.12);
                margin-bottom: 4px;
            }
            .rnd-h2h-record-side {
                display: flex; align-items: center; gap: 8px;
            }
            .rnd-h2h-record-side.away { flex-direction: row-reverse; }
            .rnd-h2h-record-logo {
                width: 28px; height: 28px; object-fit: contain;
                filter: drop-shadow(0 1px 3px rgba(0,0,0,.4));
            }
            .rnd-h2h-record-name {
                font-size: 12px; font-weight: 700; color: #c8e4b0;
                max-width: 150px; white-space: nowrap; overflow: hidden;
                text-overflow: ellipsis;
            }
            .rnd-h2h-record-stat {
                display: flex; flex-direction: column; align-items: center;
            }
            .rnd-h2h-record-num {
                font-size: 22px; font-weight: 800; line-height: 1;
            }
            .rnd-h2h-record-num.home { color: #5ab03a; }
            .rnd-h2h-record-num.draw { color: #7a8a6a; }
            .rnd-h2h-record-num.away { color: #4a8ac8; }
            .rnd-h2h-record-label {
                font-size: 8px; color: #5a7a48; text-transform: uppercase;
                letter-spacing: 1px; font-weight: 600; margin-top: 2px;
            }
            .rnd-h2h-goals-summary {
                text-align: center; font-size: 10px; color: #5a7a48;
                margin-top: -6px; padding-bottom: 6px;
            }
            .rnd-h2h-goals-summary span { color: #8abc78; font-weight: 700; }

            /* ── Match list ── */
            .rnd-h2h-matches { padding-top: 4px; }
            .rnd-h2h-season {
                font-size: 9px; color: #4a7a3a; text-transform: uppercase;
                letter-spacing: 1.5px; padding: 12px 0 4px; font-weight: 700;
                border-bottom: 1px solid rgba(80,160,48,.08);
            }
            .rnd-h2h-match {
                position: relative;
                display: flex; align-items: center; gap: 0;
                padding: 7px 8px; margin: 2px 0; border-radius: 6px;
                font-size: 13px; cursor: pointer;
                transition: background 0.15s;
            }
            .rnd-h2h-match:hover { background: rgba(255,255,255,.05); }
            .rnd-h2h-match.h2h-readonly { cursor: default; }
            .rnd-h2h-match.h2h-win { border-left: 3px solid #5ab03a; }
            .rnd-h2h-match.h2h-loss { border-left: 3px solid #4a8ac8; }
            .rnd-h2h-match.h2h-draw { border-left: 3px solid #5a6a4a; }
            .rnd-h2h-date {
                color: #4a7a3a; font-size: 10px; width: 72px; flex-shrink: 0;
                font-weight: 500;
            }
            .rnd-h2h-type-badge {
                font-size: 8px; font-weight: 700; color: #6a9a58;
                background: rgba(0,0,0,.25); padding: 1px 5px;
                border-radius: 3px; text-transform: uppercase;
                letter-spacing: 0.5px; flex-shrink: 0; margin-right: 8px;
                width: 100px;
            }
            .rnd-h2h-home {
                margin-left: 16px; text-align: right; color: #b8d8a0;
                font-size: 12px; white-space: nowrap; overflow: hidden;
                text-overflow: ellipsis; padding-right: 8px;
            }
            .rnd-h2h-result {
                font-weight: 800; color: #e0f0d0; width: 44px;
                text-align: center; font-size: 14px; flex-shrink: 0;
                letter-spacing: 1px;
            }
            .rnd-h2h-away {
                flex: 1; text-align: left; color: #b8d8a0;
                font-size: 12px; white-space: nowrap; overflow: hidden;
                text-overflow: ellipsis; padding-left: 8px;
            }
            .rnd-h2h-home.winner { color: #6adc3a; font-weight: 700; }
            .rnd-h2h-away.winner { color: #6adc3a; font-weight: 700; }
            .rnd-h2h-att {
                font-size: 9px; color: #3a6a2a; width: 50px;
                text-align: right; flex-shrink: 0; font-variant-numeric: tabular-nums;
            }

            /* ── Hover tooltip ── */
            .rnd-h2h-tooltip {
                position: absolute; z-index: 100;
                background: #111f0a; border: 1px solid rgba(80,160,48,.25);
                border-radius: 10px; padding: 18px 24px;
                min-width: 520px; max-width: 600px;
                box-shadow: 0 8px 32px rgba(0,0,0,.6);
                pointer-events: none; opacity: 0;
                transition: opacity 0.15s;
                left: 50%; top: 100%; transform: translateX(-50%);
                margin-top: 4px;
            }
            .rnd-h2h-tooltip.visible { opacity: 1; }
            .rnd-h2h-tooltip-header {
                display: flex; align-items: center; justify-content: center;
                gap: 14px; padding-bottom: 12px; margin-bottom: 10px;
                border-bottom: 1px solid rgba(80,160,48,.12);
            }
            .rnd-h2h-tooltip-logo {
                width: 40px; height: 40px; object-fit: contain;
                filter: drop-shadow(0 1px 3px rgba(0,0,0,.4));
            }
            .rnd-h2h-tooltip-team {
                font-size: 15px; font-weight: 700; color: #c8e4b0;
                max-width: 180px; white-space: nowrap; overflow: hidden;
                text-overflow: ellipsis;
            }
            .rnd-h2h-tooltip-score {
                font-size: 28px; font-weight: 800; color: #fff;
                letter-spacing: 3px;
                text-shadow: 0 0 16px rgba(128,224,64,.15);
            }
            .rnd-h2h-tooltip-meta {
                display: flex; align-items: center; justify-content: center;
                gap: 18px; font-size: 11px; color: #5a7a48;
                margin-bottom: 10px;
            }
            .rnd-h2h-tooltip-meta span { display: flex; align-items: center; gap: 3px; }
            .rnd-h2h-tooltip-events {
                display: flex; flex-direction: column; gap: 5px;
            }
            .rnd-h2h-tooltip-evt {
                display: flex; align-items: center; gap: 10px;
                font-size: 13px; color: #a0c890; padding: 3px 0;
            }
            .rnd-h2h-tooltip-evt.away-evt { flex-direction: row-reverse; text-align: right; }
            .rnd-h2h-tooltip-evt.away-evt .rnd-h2h-tooltip-evt-min { text-align: left; }
            .rnd-h2h-tooltip-evt-min {
                font-weight: 700; color: #80b868; min-width: 32px;
                font-size: 13px; text-align: right; flex-shrink: 0;
            }
            .rnd-h2h-tooltip-evt-icon { flex-shrink: 0; font-size: 16px; }
            .rnd-h2h-tooltip-evt-text { color: #b8d8a0; }
            .rnd-h2h-tooltip-evt-assist {
                font-size: 12px; color: #5a8a48; font-weight: 500; margin-left: 2px;
            }
            .rnd-h2h-tooltip-mom {
                margin-top: 10px; padding-top: 10px;
                border-top: 1px solid rgba(80,160,48,.1);
                font-size: 13px; color: #6a9a58; text-align: center;
            }
            .rnd-h2h-tooltip-mom span { color: #e8d44a; font-weight: 700; }

            /* ── Rich tooltip extras ── */
            .rnd-h2h-tooltip-divider {
                height: 1px; background: rgba(80,160,48,.1); margin: 8px 0;
            }
            .rnd-h2h-tooltip-stats {
                display: grid; grid-template-columns: 1fr auto 1fr; gap: 4px 12px;
                margin: 10px 0; font-size: 14px;
            }
            .rnd-h2h-tooltip-stat-home {
                text-align: right; font-weight: 700; color: #b8d8a0;
            }
            .rnd-h2h-tooltip-stat-label {
                text-align: center; font-size: 10px; color: #5a7a48;
                text-transform: uppercase; letter-spacing: 0.8px; font-weight: 600;
                padding: 0 6px;
            }
            .rnd-h2h-tooltip-stat-away {
                text-align: left; font-weight: 700; color: #b8d8a0;
            }
            .rnd-h2h-tooltip-stat-home.leading { color: #6adc3a; }
            .rnd-h2h-tooltip-stat-away.leading { color: #6adc3a; }
            .rnd-h2h-tooltip-subs {
                display: flex; flex-direction: column; gap: 3px;
                margin-top: 6px;
            }
            .rnd-h2h-tooltip-sub {
                display: flex; align-items: center; gap: 8px;
                font-size: 11px; color: #7aaa68;
            }
            /* (subs section currently unused) */
            .rnd-h2h-tooltip-sub-icon { flex-shrink: 0; }
            .rnd-h2h-tooltip-ratings {
                display: flex; justify-content: space-between;
                margin-top: 10px; padding-top: 10px;
                border-top: 1px solid rgba(80,160,48,.1);
            }
            .rnd-h2h-tooltip-rating-side {
                display: flex; flex-direction: column; gap: 3px; font-size: 12px;
            }
            .rnd-h2h-tooltip-rating-side.away { align-items: flex-end; }
            .rnd-h2h-tooltip-rating-player {
                display: flex; align-items: center; gap: 6px; color: #8ab878;
            }
            .rnd-h2h-tooltip-rating-player .r-val {
                font-weight: 800; min-width: 28px; font-size: 13px;
            }
            .rnd-h2h-tooltip-rating-player .r-val.high { color: #6adc3a; }
            .rnd-h2h-tooltip-rating-player .r-val.mid { color: #c8c848; }
            .rnd-h2h-tooltip-rating-player .r-val.low { color: #c86a4a; }
            .rnd-h2h-tooltip-loading {
                text-align: center; padding: 8px;
                font-size: 10px; color: #5a7a48;
            }
        `;
        document.head.appendChild(style);
    };

    // ─── Match cache & rating cells ─────────────────────────────────────
    const roundMatchCache = new Map(); // matchId -> {homeR5, awayR5, data}

    const fillRatingCells = (matchId, homeR5, awayR5) => {
        const hEl = document.getElementById(`rnd-r-h-${matchId}`);
        const aEl = document.getElementById(`rnd-r-a-${matchId}`);
        if (hEl) {
            hEl.textContent = homeR5.toFixed(2);
            hEl.style.color = getColor(homeR5, R5_THRESHOLDS);
        }
        if (aEl) {
            aEl.textContent = awayR5.toFixed(2);
            aEl.style.color = getColor(awayR5, R5_THRESHOLDS);
        }
    };

    // ─── Match dialog ────────────────────────────────────────────────────
    // ── Live replay state (shared across tabs) ──
    let liveState = null;
    // liveState = { min, sec, curEvtIdx, curLineIdx, playing, timer, mData, speed:1000,
    //               maxMin, ended, schedule, eventMinList, eventMinIdx }

    // ── Unity 3D integration state ──
    let unityState = {
        available: false,       // gameInstance exists on page
        ready: false,           // lineup loaded, ready to play clips
        playing: false,         // currently playing a clip sequence
        pendingMinute: null,    // minute waiting for finished_loading
        loadedMinutes: [],      // minutes that finished loading
        playedMinutes: [],      // minutes that finished playing
        canvasParent: null,     // original parent of the Unity canvas
        tmPaused: false,        // whether we've paused TM's replay
        clipTextQueue: [],      // flat list of {evtIdx, lineIdx} for current minute (first event only)
        clipTextCursor: 0,      // how many text lines we've shown
        clipTextGroups: [],     // group boundaries [{start, count}]
        clipGroupCursor: 0,     // how many groups we've shown
        clipPostQueue: [],      // remaining events' text, shown after animation
        activeMinute: null,     // the minute currently being clip-played
        clipFirstShown: false,  // whether first text group was shown on starting_clip
        clipSkippedFirst: false, // whether we skipped the first finished_clip
    };

    // ── Unity integration helpers ──
    const getUW = () => {
        return typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
    };

    const initUnity = () => {
        if (!isMatchPage) return;
        const uw = getUW();
        const poll = setInterval(() => {
            if (uw.gameInstance || uw.gameInstanceLoaded) {
                clearInterval(poll);
                unityState.available = true;
                unityState.ready = true;  // flash_ready already fired before our override
                console.log('[RND] Unity gameInstance detected, assuming ready');
                stopTMReplay();
                setupStargateOverride();
                // If Lineups tab already rendered, move canvas immediately
                const vp = document.getElementById('rnd-unity-viewport');
                if (vp) {
                    moveUnityCanvas();
                    vp.style.display = 'block';
                }
                // Auto-play live match if paused (e.g. page refresh)
                if (liveState && !liveState.playing && !liveState.ended) {
                    console.log('[RND] Auto-playing live match after Unity detected');
                    livePlay();
                }
            }
        }, 500);
        setTimeout(() => clearInterval(poll), 30000);
    };

    const stopTMReplay = () => {
        const uw = getUW();
        if (unityState.tmPaused) return;
        unityState.tmPaused = true;
        // Pause TM's replay system completely
        if (uw.flash_status) {
            uw.flash_status.playback_mode = 'pause';
            uw.flash_status.enabled = false;  // prevent TM from sending clips
        }
        // Kill TM's run_match loop
        uw._orig_run_match = uw.run_match;
        uw.run_match = function () { /* noop */ };
        // Also kill TM's text display functions so they don't interfere
        if (uw.show_next_action_text_entry) {
            uw._orig_show_next_action_text_entry = uw.show_next_action_text_entry;
            uw.show_next_action_text_entry = function () { /* noop */ };
        }
        if (uw.prepare_next_minute) {
            uw._orig_prepare_next_minute = uw.prepare_next_minute;
            uw.prepare_next_minute = function () { /* noop */ };
        }
        // Clear any pending setTimeout from TM's run_match chain
        // (clear a range of recent timer IDs to catch the pending one)
        const lastId = setTimeout(() => { }, 0);
        for (let i = lastId - 20; i <= lastId; i++) {
            clearTimeout(i);
        }
        console.log('[RND] TM replay stopped completely');
    };

    // Build flat text-line queue for a minute
    // Only the FIRST event is synced to the animation (distributed by groups over clip duration)
    // Remaining events are queued as postQueue, shown after animation finishes
    const buildClipTextQueue = (mData, minute) => {
        const report = mData.report || {};
        const evts = report[String(minute)] || [];
        const queue = [];
        const groups = []; // each entry = { start, count } into queue
        const postQueue = []; // remaining events' text lines
        evts.forEach((evt, evtIdx) => {
            if (!evt.chance || !evt.chance.text) return;
            let flatIdx = 0;
            if (evtIdx === 0) {
                // First event: animation-synced text
                evt.chance.text.forEach(textArr => {
                    const groupStart = queue.length;
                    let groupCount = 0;
                    textArr.forEach(line => {
                        if (!line || !line.trim()) return;
                        queue.push({ evtIdx, lineIdx: flatIdx });
                        flatIdx++;
                        groupCount++;
                    });
                    if (groupCount > 0) groups.push({ start: groupStart, count: groupCount });
                });
            } else {
                // Remaining events: post-animation text
                evt.chance.text.forEach(textArr => {
                    textArr.forEach(line => {
                        if (!line || !line.trim()) return;
                        postQueue.push({ evtIdx, lineIdx: flatIdx });
                        flatIdx++;
                    });
                });
            }
        });
        return { queue, groups, postQueue };
    };

    // Show ONE text line from the clip queue
    const advanceClipTextOneLine = () => {
        if (!liveState || !unityState.clipTextQueue.length) return;
        const idx = unityState.clipTextCursor;
        if (idx >= unityState.clipTextQueue.length) return;
        const entry = unityState.clipTextQueue[idx];
        unityState.clipTextCursor = idx + 1;

        liveState.curEvtIdx = entry.evtIdx;
        liveState.curLineIdx = entry.lineIdx;

        // Check if this event is complete
        const report = liveState.mData.report || {};
        const evts = report[String(liveState.min)] || [];
        const evt = evts[entry.evtIdx];
        const total = evt ? countEventLines(evt) : 1;
        const isComplete = entry.lineIdx >= total - 1;
        liveState.curEvtComplete = isComplete;
        liveState.justCompleted = isComplete;

        updateLiveHeader();
        refreshActiveTab();
        // Also update the unity side panels
        updateUnityFeed();
        // Only update stats when event is fully complete
        if (isComplete) updateUnityStats();
    };

    // ── Update the left-side feed panel next to viewport (current minute only) ──
    const updateUnityFeed = () => {
        const container = $('#rnd-unity-feed');
        if (!container.length || !liveState) return;
        const mData = liveState.mData;
        const report = mData.report || {};
        const playerNames = buildPlayerNames(mData);
        const curMin = liveState.min;
        const curEvtIdx = liveState.curEvtIdx;
        const curLineIdx = liveState.curLineIdx;
        const allLines = [];
        // Only show events for the CURRENT minute
        const evts = report[String(curMin)] || [];
        for (let ei = 0; ei < evts.length; ei++) {
            if (!isEventVisible(curMin, ei, curMin, curEvtIdx)) continue;
            const evt = evts[ei];
            if (!evt || !evt.chance || !evt.chance.text) continue;
            let flatIdx = 0;
            evt.chance.text.forEach(textArr => {
                textArr.forEach(line => {
                    if (!line || !line.trim()) return;
                    if (ei === curEvtIdx && flatIdx > curLineIdx) { flatIdx++; return; }
                    allLines.push({ min: curMin, text: line });
                    flatIdx++;
                });
            });
        }
        let html = '';
        allLines.forEach(item => {
            let resolved = resolvePlayerTags(item.text, playerNames);
            resolved = resolved.replace(/\[(goal|yellow|red|sub|assist)\]/g, '');
            html += `<div class="rnd-unity-feed-line"><span class="rnd-unity-feed-min">${item.min}'</span><span class="rnd-unity-feed-text">${resolved}</span></div>`;
        });
        container.html(html);
        // Auto-scroll to bottom
        container.scrollTop(container[0].scrollHeight);
    };

    // ── Update the right-side mini stats panel next to viewport ──
    const updateUnityStats = () => {
        const container = $('#rnd-unity-stats');
        if (!container.length || !liveState) return;
        const mData = liveState.mData;
        const homeId = String(mData.club.home.id);
        const homeIds = new Set(Object.keys(mData.lineup.home));
        const report = mData.report || {};
        const curMin = liveState.min;
        const curEvtIdx = liveState.curEvtIdx;
        let hShots = 0, aShots = 0, hSoT = 0, aSoT = 0, hGoals = 0, aGoals = 0;
        let hYellow = 0, aYellow = 0, hRed = 0, aRed = 0, hSetPieces = 0, aSetPieces = 0;
        const sortedMins = Object.keys(report).map(Number).sort((a, b) => a - b);
        for (const min of sortedMins) {
            const evts = report[String(min)] || [];
            for (let ei = 0; ei < evts.length; ei++) {
                if (!isEventVisible(min, ei, curMin, curEvtIdx)) continue;
                const evt = evts[ei];
                if (!evt || !evt.parameters) continue;
                evt.parameters.forEach(p => {
                    if (p.shot) {
                        const isHome = String(p.shot.team) === homeId;
                        if (isHome) { hShots++; if (p.shot.target === 'on') hSoT++; }
                        else { aShots++; if (p.shot.target === 'on') aSoT++; }
                    }
                    if (p.goal) {
                        const scorerId = String(p.goal.player);
                        const isHome = homeIds.has(scorerId);
                        if (isHome) hGoals++; else aGoals++;
                    }
                    if (p.yellow) {
                        if (homeIds.has(String(p.yellow))) hYellow++; else aYellow++;
                    }
                    if (p.yellow_red) {
                        if (homeIds.has(String(p.yellow_red))) hRed++; else aRed++;
                    }
                    if (p.red) {
                        if (homeIds.has(String(p.red))) hRed++; else aRed++;
                    }
                    if (p.set_piece) {
                        if (homeIds.has(String(p.set_piece))) hSetPieces++; else aSetPieces++;
                    }
                });
            }
        }
        const miniBar = (label, hv, av) => {
            const total = hv + av;
            const hp = total === 0 ? 50 : Math.round(hv / total * 100);
            const ap = 100 - hp;
            const hLead = hv > av ? ' lead' : '';
            const aLead = av > hv ? ' lead' : '';
            return `<div class="rnd-unity-stat-row">
                <div class="rnd-unity-stat-hdr"><span class="val home${hLead}">${hv}</span><span class="rnd-unity-stat-label">${label}</span><span class="val away${aLead}">${av}</span></div>
                <div class="rnd-unity-stat-bar"><div class="seg home" style="width:${hp}%"></div><div class="seg away" style="width:${ap}%"></div></div>
            </div>`;
        };
        let h = '';
        h += miniBar('Shots', hShots, aShots);
        h += miniBar('On Target', hSoT, aSoT);
        h += miniBar('Goals', hGoals, aGoals);
        h += miniBar('Yellow', hYellow, aYellow);
        h += miniBar('Red', hRed, aRed);
        h += miniBar('Set Pieces', hSetPieces, aSetPieces);
        container.html(h);
    };

    // Flush all remaining text lines at once (for finished_playing)
    const flushClipText = () => {
        if (!liveState) return;
        // Flush remaining animation text (first event)
        while (unityState.clipTextCursor < unityState.clipTextQueue.length) {
            advanceClipTextOneLine();
        }
        // Append and flush post-animation text (remaining events)
        if (unityState.clipPostQueue && unityState.clipPostQueue.length > 0) {
            unityState.clipPostQueue.forEach(entry => {
                unityState.clipTextQueue.push(entry);
            });
            unityState.clipPostQueue = [];
            while (unityState.clipTextCursor < unityState.clipTextQueue.length) {
                advanceClipTextOneLine();
            }
        }
    };

    // Advance the next text group (called on each finished_clip from Unity)
    const advanceClipTextGroup = () => {
        const groups = unityState.clipTextGroups || [];
        const gi = unityState.clipGroupCursor || 0;
        if (gi >= groups.length) return;
        const group = groups[gi];
        // Show all lines in this group at once
        for (let j = 0; j < group.count; j++) {
            if (unityState.clipTextCursor < unityState.clipTextQueue.length) {
                advanceClipTextOneLine();
            }
        }
        unityState.clipGroupCursor = gi + 1;
        console.log('[RND] Advanced text group ' + gi + ' (' + group.count + ' lines)');
    };

    const setupStargateOverride = () => {
        const uw = getUW();
        uw._orig_stargate = uw.stargate;
        uw.stargate = function (vars) {
            console.log('[RND] stargate:', JSON.stringify(vars));

            if (vars.flash_ready) {
                unityState.ready = true;
                console.log('[RND] Unity ready');
            }

            if (vars.finished_loading) {
                const min = vars.finished_loading.id;
                unityState.loadedMinutes.push(min);
                console.log('[RND] Clips loaded for minute', min);
                if (unityState.pendingMinute === min) {
                    unityState.pendingMinute = null;
                    playUnityClips(min);
                }
            }

            // A single clip finished → show the next text group
            // Skip the first finished_clip because its text was already shown on starting_clip
            if (vars.finished_clip) {
                if (unityState.clipFirstShown && !unityState.clipSkippedFirst) {
                    // First clip just finished; text was already shown via starting_clip
                    unityState.clipSkippedFirst = true;
                    console.log('[RND] Skipping finished_clip for group 0 (already shown on start)');
                } else {
                    advanceClipTextGroup();
                }
            }

            // A clip is starting
            if (vars.starting_clip) {
                unityState.playing = true;
                // Show first text group immediately when the first clip starts
                if (!unityState.clipFirstShown) {
                    unityState.clipFirstShown = true;
                    advanceClipTextGroup();
                }
                // Goal clip → update score after a short delay
                if (vars.starting_clip.clip && vars.starting_clip.clip.substring(0, 4) === 'goal') {
                    setTimeout(() => {
                        updateLiveHeader();
                        refreshActiveTab();
                    }, 1200);
                }
            }

            // All clips for a minute finished → let timer advance to next minute
            if (vars.finished_playing) {
                const min = vars.finished_playing.id;
                unityState.playedMinutes.push(min);
                unityState.playing = false;
                console.log('[RND] All clips finished for minute', min);
                // Show any remaining lines
                flushClipText();
                // Clear activeMinute so liveStep resumes normal schedule-based flow
                unityState.activeMinute = null;
                // Force sec to 59 so liveStep advances to next minute on next tick
                if (liveState) {
                    liveState.sec = 59;
                    // Apply deferred filter switch if pending
                    if (liveState.pendingFilterSwitch) {
                        applyFilterSwitch(liveState.pendingFilterSwitch);
                    }
                }
            }
        };
    };

    // Save canvas to a hidden container so it survives tab switches
    const saveUnityCanvas = () => {
        if (!isMatchPage || !unityState.available) return;
        const webglContent = document.querySelector('.webgl-content');
        if (!webglContent) return;
        // Don't save if already in safe container
        if (webglContent.parentElement && webglContent.parentElement.id === 'rnd-unity-safe') return;
        let safe = document.getElementById('rnd-unity-safe');
        if (!safe) {
            safe = document.createElement('div');
            safe.id = 'rnd-unity-safe';
            safe.style.cssText = 'position:fixed;top:-9999px;left:-9999px;width:1px;height:1px;overflow:hidden;pointer-events:none;';
            document.body.appendChild(safe);
        }
        safe.appendChild(webglContent);
        console.log('[RND] Canvas saved to safe container');
    };

    const moveUnityCanvas = () => {
        if (!isMatchPage || !unityState.available) return;
        const webglContent = document.querySelector('.webgl-content');
        if (!webglContent) return;
        const target = document.getElementById('rnd-unity-viewport');
        if (!target) return;
        // Remember original parent so we could restore if needed
        if (!unityState.canvasParent) unityState.canvasParent = webglContent.parentElement;
        // Move the .webgl-content into our viewport
        target.innerHTML = '';
        target.appendChild(webglContent);
        // Make .webgl-content visible (TM hides it initially)
        webglContent.style.display = 'block';
        // Clear inline dimensions on #gameContainer (it has width:300px;height:200px)
        const gc = document.getElementById('gameContainer');
        if (gc) {
            gc.style.width = '100%';
            gc.style.height = '100%';
            gc.style.margin = '0';
        }
        // Show viewport
        target.style.display = 'block';
        console.log('[RND] Canvas moved into viewport');
        // Sync feed & stats height to viewport height
        syncUnityPanelHeights();
    };

    // Keep feed and stats panels the same height as the viewport
    const syncUnityPanelHeights = () => {
        const vp = document.getElementById('rnd-unity-viewport');
        if (!vp) return;
        requestAnimationFrame(() => {
            const h = vp.offsetHeight;
            if (!h) return;
            const feed = document.getElementById('rnd-unity-feed');
            const stats = document.getElementById('rnd-unity-stats');
            if (feed) feed.style.maxHeight = h + 'px';
            if (stats) stats.style.maxHeight = h + 'px';
        });
    };

    const loadUnityClips = (minute, mData) => {
        const uw = getUW();
        if (!unityState.available || !uw.gameInstance) return false;
        const report = mData.report || {};
        const evts = report[String(minute)] || [];
        const videoList = [];
        evts.forEach(evt => {
            if (evt.chance && evt.chance.video) {
                const v = evt.chance.video;
                if (Array.isArray(v)) {
                    videoList.push(...v);
                } else {
                    videoList.push(v);
                }
            }
        });
        if (videoList.length === 0) return false;
        console.log('[RND] Loading clips for minute', minute, videoList.length, 'clips');
        // Prepare the text queue for this minute
        const { queue, groups, postQueue } = buildClipTextQueue(mData, minute);
        unityState.clipTextQueue = queue;
        unityState.clipTextGroups = groups;
        unityState.clipPostQueue = postQueue;
        unityState.clipTextCursor = 0;
        unityState.clipGroupCursor = 0;
        unityState.activeMinute = minute;
        unityState.clipFirstShown = false;
        unityState.clipSkippedFirst = false;
        unityState.pendingMinute = minute;
        uw.gameInstance.SendMessage('ClipsViewerScript', 'PrepareMinute', JSON.stringify({
            queue: videoList,
            id: minute
        }));
        return true;
    };

    const playUnityClips = (minute) => {
        const uw = getUW();
        if (!unityState.available || !uw.gameInstance) return;
        unityState.playing = true;
        console.log('[RND] Playing clips for minute', minute);
        uw.gameInstance.SendMessage('ClipsViewerScript', 'PlayMinute', JSON.stringify({ id: minute }));
    };

    const LINE_INTERVAL = 3;  // seconds between lines within a minute
    const POST_DELAY = 3;     // seconds after last line before advancing to next minute

    // ── Count total non-empty lines in an event's chance.text ──
    const countEventLines = (evt) => {
        if (!evt.chance || !evt.chance.text) return 1;
        let n = 0;
        evt.chance.text.forEach(textArr => {
            textArr.forEach(line => { if (line && line.trim()) n++; });
        });
        return Math.max(1, n);
    };

    // ── Calculate current match minute from kickoff timestamp ──
    const calculateLiveMinute = (kickoff) => {
        const now = Math.floor(Date.now() / 1000);
        const elapsed = now - kickoff;
        if (elapsed < 0) return null; // not started
        const FIRST_HALF = 45 * 60;  // 2700s
        const HT_BREAK = 15 * 60;  // 900s
        if (elapsed < FIRST_HALF) {
            return { minute: Math.floor(elapsed / 60), second: elapsed % 60, isHT: false };
        } else if (elapsed < FIRST_HALF + HT_BREAK) {
            return { minute: 45, second: 0, isHT: true };
        } else {
            const sh = elapsed - FIRST_HALF - HT_BREAK;
            return { minute: 45 + Math.floor(sh / 60), second: sh % 60, isHT: false };
        }
    };

    // ── Check if match is currently in progress (via API's live_min) ──
    const isMatchCurrentlyLive = (mData) => {
        const lm = mData.match_data?.live_min;
        return typeof lm === 'number' && lm > 0;
    };

    // ── Check if match is in the future (not yet started) ──
    const isMatchFuture = (mData) => {
        const md = mData.match_data;
        // Negative live_min means countdown to kickoff → match is in the future
        const lm = md?.live_min;
        if (typeof lm === 'number' && lm < 0) return true;
        // Positive live_min means match is in progress
        if (typeof lm === 'number' && lm > 0) return false;
        // Fallback: check kickoff timestamp
        const ko = md?.venue?.kickoff;
        if (ko) {
            const now = Math.floor(Date.now() / 1000);
            return Number(ko) > now;
        }
        return false;
    };

    // ── Derive effective kickoff timestamp from API's live_min ──
    // live_min = total real elapsed minutes since kickoff (includes HT break).
    // calculateLiveMinute then handles the 45min/15min-HT/second-half split.
    const deriveKickoff = (mData) => {
        const lm = mData.match_data.live_min;
        const now = Math.floor(Date.now() / 1000);
        return now - Math.round(lm * 60);
    };

    // ── Build per-minute schedule: which lines appear at which second ──
    const buildSchedule = (report, keyOnly = false) => {
        const schedule = {};     // min → [{evtIdx, lineIdx, sec}]
        const eventMinList = []; // sorted list of minutes that have events
        const mins = Object.keys(report).map(Number).sort((a, b) => a - b);
        mins.forEach(min => {
            const evts = report[min] || [];
            const entries = [];
            let secCursor = 0;
            evts.forEach((evt, evtIdx) => {
                if (keyOnly && evt.severity !== 1) return;
                const lineCount = countEventLines(evt);
                for (let li = 0; li < lineCount; li++) {
                    entries.push({ evtIdx, lineIdx: li, sec: secCursor });
                    secCursor += LINE_INTERVAL;
                }
            });
            if (entries.length > 0) {
                schedule[min] = entries;
                eventMinList.push(min);
            }
        });
        return { schedule, eventMinList };
    };

    // ── Check if an event is visible at the current live step (event-level) ──
    const isEventVisible = (evtMin, evtIdx, curMin, curEvtIdx) => {
        if (evtMin < curMin) return true;
        if (evtMin === curMin && evtIdx <= curEvtIdx) return true;
        return false;
    };

    // ── Compute score up to current step ──
    const scoreAtStep = (mData, curMin, curEvtIdx) => {
        const score = [0, 0];
        const homeId = String(mData.club.home.id);
        const report = mData.report || {};
        Object.keys(report).forEach(minKey => {
            const min = Number(minKey);
            (report[minKey] || []).forEach((evt, si) => {
                if (!isEventVisible(min, si, curMin, curEvtIdx)) return;
                if (!evt.parameters) return;
                evt.parameters.forEach(p => {
                    if (p.goal) {
                        if (String(evt.club) === homeId) score[0]++; else score[1]++;
                    }
                });
            });
        });
        return score;
    };

    // ── Compute active roster at current step (subs + red cards) ──
    // Returns { home: Set<playerId>, away: Set<playerId>, subbedPositions: Map<playerId, position> }
    const computeActiveRoster = (mData, curMin, curEvtIdx) => {
        const homeIds = new Set(Object.keys(mData.lineup.home));
        const homeActive = new Set();
        const awayActive = new Set();
        // Start with starters
        Object.values(mData.lineup.home).forEach(p => {
            if (!p.position.includes('sub')) homeActive.add(String(p.player_id));
        });
        Object.values(mData.lineup.away).forEach(p => {
            if (!p.position.includes('sub')) awayActive.add(String(p.player_id));
        });

        // Track position of subbed-in players (inherit from subbed-out player)
        const subbedPositions = new Map(); // player_id → position

        const report = mData.report || {};
        Object.keys(report).forEach(minKey => {
            const min = Number(minKey);
            (report[minKey] || []).forEach((evt, si) => {
                if (!isEventVisible(min, si, curMin, curEvtIdx)) return;
                if (!evt.parameters) return;
                evt.parameters.forEach(param => {
                    if (param.sub) {
                        const inId = String(param.sub.player_in);
                        const outId = String(param.sub.player_out);
                        const isHome = homeActive.has(outId) || homeIds.has(outId);
                        // Find position of outgoing player
                        const outPlayer = mData.lineup[isHome ? 'home' : 'away'][outId];
                        const outPos = subbedPositions.get(outId) || (outPlayer ? outPlayer.position : null);
                        if (outPos) subbedPositions.set(inId, outPos);
                        if (isHome) { homeActive.delete(outId); homeActive.add(inId); }
                        else { awayActive.delete(outId); awayActive.add(inId); }
                    }
                    if (param.red || param.yellow_red) {
                        const pid = String(param.red || param.yellow_red);
                        homeActive.delete(pid);
                        awayActive.delete(pid);
                    }
                });
            });
        });
        return { homeActive, awayActive, subbedPositions };
    };

    // ── Update live header (score + minute + progress) ──
    const updateLiveHeader = () => {
        if (!liveState) return;
        // Defer score update: don't count current event's goal until all its text lines are shown
        const scoreEvtIdx = (!liveState.ended && !liveState.curEvtComplete) ? liveState.curEvtIdx - 1 : liveState.curEvtIdx;
        const s = scoreAtStep(liveState.mData, liveState.min, scoreEvtIdx);
        $('#rnd-overlay .rnd-dlg-score').text(`${s[0]} - ${s[1]}`);
        const minDisplay = liveState.ended ? 'FT'
            : liveState.liveIsHT ? 'HT'
                : `${liveState.min}:${String(liveState.sec).padStart(2, '0')}`;
        $('#rnd-live-min-head').text(minDisplay);
        const maxMin = liveState.maxMin || 90;
        const pct = Math.min(100, Math.round((liveState.min * 60 + liveState.sec) / (maxMin * 60) * 100));
        $('#rnd-live-progress-head').css('width', pct + '%');
        if (liveState.ended) {
            $('#rnd-overlay .rnd-dlg-head').removeClass('rnd-live-active');
            $('#rnd-live-play-head').html('▶');
        }
        // Live-update mentality chips from report mentality_change events
        if (liveState.mData) {
            const mentalityMapH = { 1: 'V.Def', 2: 'Def', 3: 'Sl.Def', 4: 'Normal', 5: 'Sl.Att', 6: 'Att', 7: 'V.Att' };
            const homeClubId = String(liveState.mData.club.home.id);
            const awayClubId = String(liveState.mData.club.away.id);
            const mdH = liveState.mData.match_data;
            const curMent = {
                home: Number(mdH.mentality ? mdH.mentality.home : 4),
                away: Number(mdH.mentality ? mdH.mentality.away : 4)
            };
            const rpt = liveState.mData.report || {};
            Object.keys(rpt).forEach(mk => {
                const eMin = Number(mk);
                (rpt[mk] || []).forEach((evt, si) => {
                    if (!isEventVisible(eMin, si, liveState.min, scoreEvtIdx)) return;
                    if (!evt.parameters) return;
                    evt.parameters.forEach(p => {
                        if (p.mentality_change) {
                            const tid = String(p.mentality_change.team);
                            if (tid === homeClubId) curMent.home = Number(p.mentality_change.mentality);
                            else if (tid === awayClubId) curMent.away = Number(p.mentality_change.mentality);
                        }
                    });
                });
            });
            const hChip = $('#rnd-chip-ment-home');
            if (hChip.length) hChip.find('.chip-val').text(mentalityMapH[curMent.home] || curMent.home);
            const aChip = $('#rnd-chip-ment-away');
            if (aChip.length) aChip.find('.chip-val').text(mentalityMapH[curMent.away] || curMent.away);
        }
    };



    // ── Refresh whichever tab is active ──
    const refreshActiveTab = () => {
        if (!liveState) return;
        const tab = $('#rnd-overlay .rnd-tab.active').attr('data-tab');
        if (!tab) return;
        // When match ended/skipped, always do full render
        if (liveState.ended) {
            renderDialogTab(tab, liveState.mData);
            return;
        }
        // Report tab: append/update lines from schedule for current minute
        if (tab === 'report') {
            const entries = liveState.schedule[liveState.min] || [];
            const maxLinePerEvt = {};
            entries.forEach(e => {
                if (e.sec <= liveState.sec) {
                    if (maxLinePerEvt[e.evtIdx] === undefined || e.lineIdx > maxLinePerEvt[e.evtIdx]) {
                        maxLinePerEvt[e.evtIdx] = e.lineIdx;
                    }
                }
            });
            Object.entries(maxLinePerEvt).forEach(([eidx, lidx]) => {
                appendReportText(liveState.mData, liveState.min, Number(eidx), lidx);
            });
            return;
        }
        // Details tab: re-render only when an event just became complete (all text shown)
        if (tab === 'details') {
            if (liveState.justCompleted) renderDialogTab(tab, liveState.mData);
            return;
        }
        // Statistics tab: same — only re-render on event completion
        if (tab === 'statistics') {
            if (liveState.justCompleted) renderDialogTab(tab, liveState.mData);
            return;
        }
        // Lineups tab: re-render only when an event completes (goals, subs, etc. deferred)
        if (tab === 'lineups') {
            if (liveState.justCompleted) renderDialogTab(tab, liveState.mData);
            return;
        }
        // Other tabs: don't re-render during live
    };

    // ── Build HTML for a single report event accordion ──
    // maxLineIdx: how many individual lines to show (-1 = all)
    // hideBadges: when true, hide goal/red/sub badges (show text preview instead)
    const buildReportEventHtml = (evt, min, evtIdx, playerNames, homeId, maxLineIdx = -1, hideBadges = false) => {
        const chance = evt.chance;
        if (!chance || !chance.text) return '';

        const evtClub = String(evt.club || 0);
        const isHome = evtClub === homeId;
        const isNeutral = !evt.club || evtClub === '0';

        let headerBadges = '';
        let hasEvents = false;
        if (evt.parameters && !hideBadges) {
            evt.parameters.forEach(param => {
                if (param.goal) {
                    hasEvents = true;
                    const scorer = playerNames[param.goal.player] || '?';
                    const score = param.goal.score ? param.goal.score.join('-') : '';
                    let b = `⚽ ${scorer}`;
                    if (score) b += ` (${score})`;
                    if (param.goal.assist) b += ` <span style="font-size:11px;color:#90b878">ast. ${playerNames[param.goal.assist] || '?'}</span>`;
                    headerBadges += `<div class="rnd-report-evt-badge evt-goal">${b}</div>`;
                }
                if (param.yellow) { hasEvents = true; headerBadges += `<div class="rnd-report-evt-badge evt-yellow">🟨 ${playerNames[param.yellow] || '?'}</div>`; }
                if (param.yellow_red) { hasEvents = true; headerBadges += `<div class="rnd-report-evt-badge evt-red">🟥🟨 ${playerNames[param.yellow_red] || '?'}</div>`; }
                if (param.red) { hasEvents = true; headerBadges += `<div class="rnd-report-evt-badge evt-red">🟥 ${playerNames[param.red] || '?'}</div>`; }
                if (param.injury) {
                    hasEvents = true;
                    headerBadges += `<div class="rnd-report-evt-badge evt-injury"><span style="color:#ff3c3c;font-weight:800">✚</span> ${playerNames[param.injury] || '?'}</div>`;
                }
                if (param.sub) {
                    hasEvents = true;
                    const pIn = playerNames[param.sub.player_in] || '?';
                    const pOut = playerNames[param.sub.player_out] || '?';
                    headerBadges += `<div class="rnd-report-evt-badge evt-sub">🔄 ↑${pIn} ↓${pOut}</div>`;
                }
            });
        }

        // Build lines, respecting maxLineIdx limit (flat line count)
        const lines = [];
        let flatIdx = 0;
        chance.text.forEach((textArr) => {
            textArr.forEach(line => {
                if (!line || !line.trim()) return;
                if (maxLineIdx >= 0 && flatIdx > maxLineIdx) { flatIdx++; return; }
                let resolved = resolvePlayerTags(line, playerNames);
                resolved = resolved.replace(/\[goal\]/g, '<span class="rnd-goal-text">⚽ ');
                resolved = resolved.replace(/\[yellow\]/g, '<span class="rnd-yellow-text">🟨 ');
                resolved = resolved.replace(/\[red\]/g, '<span class="rnd-red-text">🟥 ');
                resolved = resolved.replace(/\[sub\]/g, '<span class="rnd-sub-text">🔄 ');
                resolved = resolved.replace(/\[assist\]/g, '');
                const openTags = (resolved.match(/<span class="rnd-(goal|yellow|red|sub)-text">/g) || []).length;
                for (let t = 0; t < openTags; t++) resolved += '</span>';
                lines.push(resolved);
                flatIdx++;
            });
        });

        const goalCls = headerBadges.includes('evt-goal') ? ' rnd-acc-goal' : '';
        const chevron = '<svg class="rnd-acc-chevron" viewBox="0 0 24 24"><path d="M8.59 16.59L10 18l6-6-6-6-1.41 1.41L13.17 12z"/></svg>';
        let headerContent = headerBadges;
        if (!hasEvents) {
            const preview = lines.length ? lines[0] : '';
            headerContent = `<span style="color:#90b878;font-size:12px">${preview}</span>`;
        }

        const totalLines = countEventLines(evt);
        let html = `<div class="rnd-acc" data-acc="${min}-${evtIdx}" data-line-count="${maxLineIdx >= 0 ? maxLineIdx + 1 : totalLines}">`;
        html += `<div class="rnd-acc-head${goalCls}">`;
        html += `<div class="rnd-acc-home">${isHome ? headerContent : ''}</div>`;
        html += `<div class="rnd-acc-min">${min}'</div>`;
        html += `<div class="rnd-acc-away">${!isHome && !isNeutral ? headerContent : (isNeutral ? headerContent : '')}</div>`;
        html += chevron;
        html += `</div>`;
        html += `<div class="rnd-acc-body"><div class="rnd-report-text">${lines.join('<br>')}</div></div>`;
        html += `</div>`;
        return html;
    };

    // ── Append or update lines in the Report tab (line-level stepping) ──
    const appendReportText = (mData, curMin, curEvtIdx, curLineIdx) => {
        const container = $('#rnd-report-timeline');
        // If container doesn't exist, the Report tab hasn't been rendered yet — do full render
        if (!container.length) {
            renderDialogTab('report', mData);
            return;
        }

        const report = mData.report || {};
        const evts = report[String(curMin)] || [];
        const evt = evts[curEvtIdx];
        if (!evt || !evt.chance || !evt.chance.text) return;

        const playerNames = buildPlayerNames(mData);
        const homeId = String(mData.club.home.id);
        const key = `${curMin}-${curEvtIdx}`;
        const existing = container.find(`[data-acc="${key}"]`);

        // During live, hide badges until all lines of this event are shown
        const totalLines = countEventLines(evt);
        const isComplete = curLineIdx >= totalLines - 1;
        const hideBadges = liveState && !liveState.ended && !isComplete;

        if (existing.length) {
            // Event accordion already exists — update with one more line
            const oldCount = Number(existing.attr('data-line-count') || 0);
            if (curLineIdx < oldCount) return;  // already shown this line
            // Re-build the accordion with updated line count
            const newHtml = buildReportEventHtml(evt, curMin, curEvtIdx, playerNames, homeId, curLineIdx, hideBadges);
            if (!newHtml) return;
            const wasOpen = existing.hasClass('open');
            const $new = $(newHtml);
            if (wasOpen) $new.addClass('open');
            existing.replaceWith($new);
        } else {
            // New event — collapse all other accordions, then append with auto-open
            container.find('.rnd-acc.open').removeClass('open');
            const evtHtml = buildReportEventHtml(evt, curMin, curEvtIdx, playerNames, homeId, curLineIdx, hideBadges);
            if (!evtHtml) return;
            const $el = $(evtHtml).addClass('rnd-live-feed-line open');
            container.append($el);
        }

        // Auto-scroll the dialog body to show the latest content
        const dlgBody = $('#rnd-dlg-body');
        dlgBody.animate({ scrollTop: dlgBody[0].scrollHeight }, 300);
    };

    // ── Advance one second in the live replay ──
    const liveStep = () => {
        if (!liveState || !liveState.playing) return;

        // ── LIVE mode: wall-clock driven ticking ──
        if (liveState.filterMode === 'live') {
            const kickoff = liveState.liveKickoff;
            if (!kickoff) return;
            const info = calculateLiveMinute(kickoff);
            const lastMin = liveState.mData.match_data.last_min || 90;
            if (!info || info.minute > lastMin) {
                // Match ended
                liveState.min = lastMin; liveState.sec = 59;
                liveState.curEvtIdx = 999; liveState.curLineIdx = 999;
                liveState.ended = true; liveState.playing = false;
                liveState.liveIsHT = false;
                updateLiveHeader(); refreshActiveTab();
                return;
            }
            const prevMin = liveState.min;
            liveState.min = info.minute;
            liveState.sec = info.second;
            liveState.liveIsHT = info.isHT;
            if (info.isHT) {
                // Halftime — all first-half events visible, just tick clock
                liveState.curEvtIdx = 999; liveState.curLineIdx = 999;
                liveState.curEvtComplete = true;
                updateLiveHeader();
                liveState.timer = setTimeout(liveStep, liveState.speed);
                return;
            }
            // If Unity clips are playing for this minute, just tick the clock
            if (unityState.activeMinute === liveState.min) {
                updateLiveHeader();
                liveState.timer = setTimeout(liveStep, liveState.speed);
                return;
            }
            // Show all events up to current minute
            liveState.curEvtIdx = 999; liveState.curLineIdx = 999;
            liveState.curEvtComplete = true;
            const minuteChanged = liveState.min !== prevMin;
            liveState.justCompleted = minuteChanged;
            // Load Unity clips when entering a new event minute
            if (minuteChanged && isMatchPage && unityState.available && unityState.ready) {
                const hasClips = loadUnityClips(liveState.min, liveState.mData);
                if (hasClips) {
                    // Clips loaded — animation will play, text driven by stargate callbacks
                    updateLiveHeader();
                    refreshActiveTab();
                    liveState.timer = setTimeout(liveStep, liveState.speed);
                    return;
                }
            }
            updateLiveHeader();
            if (minuteChanged) refreshActiveTab();
            liveState.timer = setTimeout(liveStep, liveState.speed);
            return;
        }

        liveState.sec++;

        // ── If Unity clips are active for this minute, just tick the clock ──
        if (unityState.activeMinute === liveState.min) {
            // Clock advances, but text is driven by stargate callbacks (advanceClipText)
            // Don't process schedule, don't advance minute — wait for finished_playing
            updateLiveHeader();
            liveState.timer = setTimeout(liveStep, liveState.speed);
            return;
        }

        // Check if current minute is finished (past last line + delay, or >= 60)
        const entries = liveState.schedule[liveState.min] || [];
        const lastSec = entries.length > 0 ? entries[entries.length - 1].sec : -1;
        const minuteEnd = lastSec + POST_DELAY;

        if (liveState.sec > minuteEnd || liveState.sec >= 60) {
            // Move to next event minute
            const nextIdx = liveState.eventMinIdx + 1;
            if (nextIdx >= liveState.eventMinList.length) {
                // Match finished
                liveState.min = liveState.maxMin;
                liveState.sec = 59;
                liveState.curEvtIdx = 999;
                liveState.curLineIdx = 999;
                liveState.playing = false;
                liveState.ended = true;
                updateLiveHeader();
                refreshActiveTab();
                return;
            }
            liveState.eventMinIdx = nextIdx;
            liveState.min = liveState.eventMinList[nextIdx];
            liveState.sec = 0;
            // Reset event tracking to prevent score from briefly showing future goals
            liveState.curEvtIdx = -1;
            liveState.curEvtComplete = false;

            // ── Unity 3D: trigger clip loading when entering a new minute with videos ──
            if (isMatchPage && unityState.available && unityState.ready) {
                const hasClips = loadUnityClips(liveState.min, liveState.mData);
                if (hasClips) {
                    // Timer keeps running — clock ticks, text driven by clip callbacks
                    updateLiveHeader();
                    liveState.timer = setTimeout(liveStep, liveState.speed);
                    return;
                }
            }
        }

        // Check schedule for new lines at current second (non-clip minutes only)
        let hasNew = false;
        liveState.justCompleted = false;
        const curEntries = liveState.schedule[liveState.min] || [];
        const report = liveState.mData.report || {};
        const evts = report[String(liveState.min)] || [];
        curEntries.forEach(entry => {
            if (entry.sec === liveState.sec) {
                liveState.curEvtIdx = entry.evtIdx;
                liveState.curLineIdx = entry.lineIdx;
                hasNew = true;
                // Check if event just became complete (all text lines shown)
                const evt = evts[entry.evtIdx];
                const total = evt ? countEventLines(evt) : 1;
                const isComplete = entry.lineIdx >= total - 1;
                liveState.curEvtComplete = isComplete;
                if (isComplete) liveState.justCompleted = true;
            }
        });

        updateLiveHeader();
        if (hasNew) refreshActiveTab();
        liveState.timer = setTimeout(liveStep, liveState.speed);
    };

    const livePlay = () => {
        if (!liveState || liveState.ended) return;
        liveState.playing = true;
        $('#rnd-live-play-head').html('⏸');
        // If Unity was paused mid-animation, unpause it
        if (unityState.activeMinute !== null) {
            const uw = getUW();
            if (uw.gameInstance) {
                uw.gameInstance.SendMessage('ClipsViewerScript', 'OnPauseGame');
            }
        }
        liveStep();
    };
    const livePause = () => {
        if (!liveState) return;
        liveState.playing = false;
        clearTimeout(liveState.timer);
        $('#rnd-live-play-head').html('▶');
        // Immediately pause Unity animation if playing
        if (unityState.playing && unityState.activeMinute !== null) {
            const uw = getUW();
            if (uw.gameInstance) {
                uw.gameInstance.SendMessage('ClipsViewerScript', 'OnPauseGame');
            }
        }
    };
    const liveToggle = () => {
        if (!liveState) return;
        if (liveState.playing) livePause(); else livePlay();
    };

    // Apply a deferred or immediate filter mode switch (All ↔ Key ↔ Live)
    const applyFilterSwitch = (mode) => {
        if (!liveState) return;
        // LIVE mode: switch to all-events schedule and jump to current wall-clock minute
        if (mode === 'live') {
            const sch = liveState.scheduleAll;
            liveState.schedule = sch.schedule;
            liveState.eventMinList = sch.eventMinList;
            liveState.maxMin = sch.eventMinList.length ? sch.eventMinList[sch.eventMinList.length - 1] : 90;
            let kickoff = liveState.liveKickoff;
            if (!kickoff && isMatchCurrentlyLive(liveState.mData)) {
                kickoff = deriveKickoff(liveState.mData);
                liveState.liveKickoff = kickoff;
            }
            const info = kickoff ? calculateLiveMinute(kickoff) : null;
            const lastMin = liveState.mData.match_data.last_min || 90;
            if (info && info.minute <= lastMin) {
                liveState.min = info.minute;
                liveState.sec = info.second;
                liveState.liveIsHT = info.isHT;
                liveState.curEvtIdx = 999; liveState.curLineIdx = 999;
                liveState.curEvtComplete = true; liveState.justCompleted = true;
                liveState.ended = false;
            } else {
                // Match over
                liveState.min = lastMin; liveState.sec = 59;
                liveState.curEvtIdx = 999; liveState.curLineIdx = 999;
                liveState.ended = true; liveState.playing = false;
                liveState.liveIsHT = false;
            }
            liveState.pendingFilterSwitch = null;
            console.log('[RND] Filter switch applied: live (min ' + liveState.min + ')');
            updateLiveHeader(); refreshActiveTab();
            return;
        }
        liveState.liveIsHT = false;
        const sch = mode === 'key' ? liveState.scheduleKey : liveState.scheduleAll;
        liveState.schedule = sch.schedule;
        liveState.eventMinList = sch.eventMinList;
        liveState.maxMin = sch.eventMinList.length ? sch.eventMinList[sch.eventMinList.length - 1] : 90;
        // Find next event minute AFTER current (don't re-play the minute we just finished)
        const curMin = liveState.min;
        let newIdx = sch.eventMinList.findIndex(m => m > curMin);
        if (newIdx < 0) {
            // No more minutes — match finished
            liveState.min = liveState.maxMin;
            liveState.sec = 59;
            liveState.curEvtIdx = 999;
            liveState.curLineIdx = 999;
            liveState.playing = false;
            liveState.ended = true;
            liveState.pendingFilterSwitch = null;
            updateLiveHeader();
            refreshActiveTab();
            return;
        }
        liveState.eventMinIdx = newIdx;
        liveState.min = sch.eventMinList[newIdx];
        liveState.sec = -1;
        liveState.curEvtIdx = -1;
        liveState.curLineIdx = -1;
        liveState.curEvtComplete = true;
        liveState.justCompleted = false;
        liveState.pendingFilterSwitch = null;
        console.log('[RND] Filter switch applied: ' + mode);
        // Load Unity clips for the new minute (so animation + clip-driven text work)
        if (isMatchPage && unityState.available && unityState.ready) {
            loadUnityClips(liveState.min, liveState.mData);
        }
        updateLiveHeader();
        refreshActiveTab();
    };

    const liveSkip = () => {
        if (!liveState) return;
        livePause();
        liveState.min = liveState.maxMin;
        liveState.sec = 59;
        liveState.curEvtIdx = 999;
        liveState.curLineIdx = 999;
        liveState.eventMinIdx = liveState.eventMinList.length;
        liveState.ended = true;
        updateLiveHeader();
        refreshActiveTab();
    };

    const openMatchDialog = (matchId) => {
        const cached = roundMatchCache.get(String(matchId));
        // If not cached yet, fetch on-demand
        const show = (mData) => {
            const homeClub = mData.club.home.club_name;
            const awayClub = mData.club.away.club_name;
            const homeLogoId = mData.club.home.id;
            const awayLogoId = mData.club.away.id;

            // Determine if this match is in the future
            const matchIsFuture = isMatchFuture(mData);

            // Determine if match is currently live
            const matchIsLive = !matchIsFuture && isMatchCurrentlyLive(mData);

            // Build schedules & live state only for non-future matches
            if (!matchIsFuture) {
                const rpt = mData.report || {};
                const allSch = buildSchedule(rpt, false);
                const keySch = buildSchedule(rpt, true);
                const { schedule: keySchedule, eventMinList: keyEventMinList } = keySch;
                const maxMin = keyEventMinList.length ? keyEventMinList[keyEventMinList.length - 1] : 90;

                if (liveState && liveState.timer) clearTimeout(liveState.timer);
                if (matchIsLive) {
                    const { schedule: allSchedule, eventMinList: allEventMinList } = allSch;
                    const allMaxMin = allEventMinList.length ? allEventMinList[allEventMinList.length - 1] : 90;
                    const effectiveKickoff = deriveKickoff(mData);
                    const info = calculateLiveMinute(effectiveKickoff);
                    const lastMin = mData.match_data.last_min || 90;
                    const liveMin = info ? Math.min(info.minute, lastMin) : Math.floor(mData.match_data.live_min);
                    const liveSec = info ? info.second : 0;
                    const liveHT = info ? info.isHT : false;
                    console.log('[RND] LIVE detected — live_min:', mData.match_data.live_min, '→ min:', liveMin, 'sec:', liveSec, 'HT:', liveHT);
                    liveState = {
                        min: liveMin, sec: liveSec,
                        curEvtIdx: 999, curLineIdx: 999,
                        curEvtComplete: true, justCompleted: false,
                        playing: false, timer: null, mData,
                        speed: 1000, maxMin: allMaxMin,
                        ended: info ? info.minute > lastMin : false,
                        schedule: allSchedule, eventMinList: allEventMinList, eventMinIdx: 0,
                        filterMode: 'live', liveIsHT: liveHT,
                        liveKickoff: effectiveKickoff,
                        scheduleAll: allSch, scheduleKey: keySch
                    };
                } else {
                    liveState = {
                        min: keyEventMinList.length ? keyEventMinList[0] : 0,
                        sec: -1,
                        curEvtIdx: -1, curLineIdx: -1,
                        curEvtComplete: true, justCompleted: false,
                        playing: false, timer: null, mData,
                        speed: 1000, maxMin, ended: false,
                        schedule: keySchedule, eventMinList: keyEventMinList, eventMinIdx: 0,
                        filterMode: 'key', liveIsHT: false,
                        liveKickoff: null,
                        scheduleAll: allSch, scheduleKey: keySch
                    };
                }
            } else {
                // Future match: no live state needed
                if (liveState && liveState.timer) clearTimeout(liveState.timer);
                liveState = null;
            }

            // Build tactic chip strings for header
            const md = mData.match_data;
            const mentalityMap = { 1: 'V.Def', 2: 'Def', 3: 'Sl.Def', 4: 'Normal', 5: 'Sl.Att', 6: 'Att', 7: 'V.Att' };
            const styleMapShort = { 1: 'Balanced', 2: 'Direct', 3: 'Wings', 4: 'Short', 5: 'Long', 6: 'Through' };
            const focusMapShort = { 1: 'Balanced', 2: 'Left', 3: 'Central', 4: 'Right' };
            const buildChips = (side) => {
                let c = '';
                if (!matchIsFuture) {
                    const ment = md.mentality ? (mentalityMap[md.mentality[side]] || md.mentality[side]) : '?';
                    c += `<span class="rnd-dlg-chip" id="rnd-chip-ment-${side}">⚔ <span class="chip-val">${ment}</span></span>`;
                    const style = md.attacking_style ? (styleMapShort[md.attacking_style[side]] || md.attacking_style[side]) : '?';
                    c += `<span class="rnd-dlg-chip">🎯 <span class="chip-val">${style}</span></span>`;
                    const focus = md.focus_side ? (focusMapShort[md.focus_side[side]] || md.focus_side[side]) : '?';
                    c += `<span class="rnd-dlg-chip">◎ <span class="chip-val">${focus}</span></span>`;
                }
                c += `<span class="rnd-dlg-chip" id="rnd-chip-r5-${side}">R5 <span class="chip-val">···</span></span>`;
                return c;
            };

            const overlay = $(`
                <div class="rnd-overlay" id="rnd-overlay">
                    <div class="rnd-dialog">
                        <div class="rnd-dlg-head">
                            <button class="rnd-dlg-close" id="rnd-dlg-close">&times;</button>
                            <div class="rnd-dlg-head-content">
                              <div class="rnd-dlg-head-row">
                                <div class="rnd-dlg-team-group home">
                                  <div class="rnd-dlg-team-info">
                                    <span class="rnd-dlg-team">${homeClub}</span>
                                    <div class="rnd-dlg-chips">${buildChips('home')}</div>
                                  </div>
                                  <img class="rnd-dlg-logo" src="/pics/club_logos/${homeLogoId}_140.png" onerror="this.style.display='none'">
                                </div>
                                <div class="rnd-dlg-score-block">
                                  <span class="rnd-dlg-score">0 - 0</span>
                                  <div class="rnd-dlg-datetime">${(() => {
                    const ko = mData.match_data.venue?.kickoff_readable || '';
                    const d = ko ? new Date(ko.replace(' ', 'T')).toLocaleDateString('en-GB', { weekday: 'short', day: 'numeric', month: 'short', year: 'numeric' }) : '';
                    const t = mData.match_data.match_time_of_day || '';
                    return (d || '') + (t ? ' · ' + t : '');
                })()}</div>
                                </div>
                                <div class="rnd-dlg-team-group away">
                                  <img class="rnd-dlg-logo" src="/pics/club_logos/${awayLogoId}_140.png" onerror="this.style.display='none'">
                                  <div class="rnd-dlg-team-info">
                                    <span class="rnd-dlg-team">${awayClub}</span>
                                    <div class="rnd-dlg-chips">${buildChips('away')}</div>
                                  </div>
                                </div>
                              </div>
                              <div class="rnd-dlg-head-time">
                                <span class="rnd-live-min" id="rnd-live-min-head">${matchIsFuture ? '⏳' : '0:00'}</span>
                                ${matchIsFuture ? '' : `<div class="rnd-live-progress"><div class="rnd-live-progress-fill" id="rnd-live-progress-head" style="width:0%"></div></div>
                                <div class="rnd-live-filter-group">
                                  <button class="rnd-live-filter-btn" data-filter="all">All</button>
                                  <button class="rnd-live-filter-btn${matchIsLive ? '' : ' active'}" data-filter="key">Key</button>
                                  ${matchIsLive ? '<button class="rnd-live-filter-btn live-btn active" data-filter="live">Live</button>' : ''}
                                </div>
                                <button class="rnd-live-btn" id="rnd-live-play-head" title="Play / Pause">▶</button>
                                <button class="rnd-live-btn" id="rnd-live-skip-head" title="Skip to end">⏭</button>`}
                              </div>
                            </div>
                        </div>
                        <div class="rnd-tabs">
                            ${matchIsFuture ? `
                            <div class="rnd-tab active" data-tab="lineups">Expected Lineups</div>
                            <div class="rnd-tab" data-tab="venue">Venue</div>
                            <div class="rnd-tab" data-tab="h2h">H2H</div>
                            ` : `
                            <div class="rnd-tab" data-tab="details">Details</div>
                            <div class="rnd-tab" data-tab="statistics">Statistics</div>
                            <div class="rnd-tab" data-tab="report">Report</div>
                            <div class="rnd-tab active" data-tab="lineups">Lineups</div>
                            <div class="rnd-tab" data-tab="venue">Venue</div>
                            <div class="rnd-tab" data-tab="h2h">H2H</div>
                            `}
                        </div>
                        <div class="rnd-dlg-body" id="rnd-dlg-body"></div>
                    </div>
                </div>
            `);

            $('body').append(overlay).css('overflow', 'hidden');

            const closeDialog = () => {
                if (liveState && liveState.timer) clearTimeout(liveState.timer);
                liveState = null;
                overlay.remove();
                $('body').css('overflow', '');
            };

            // Close handlers
            overlay.on('click', '#rnd-dlg-close', closeDialog);
            overlay.on('click', (e) => { if (e.target === overlay[0]) closeDialog(); });
            $(document).one('keydown.rndDlg', (e) => {
                if (e.key === 'Escape') { closeDialog(); $(document).off('keydown.rndDlg'); }
            });

            // Live replay controls & filters (skip for future matches)
            if (!matchIsFuture) {
                overlay.on('click', '#rnd-live-play-head', liveToggle);
                overlay.on('click', '#rnd-live-skip-head', liveSkip);
                overlay.on('click', '.rnd-live-filter-btn', function () {
                    const mode = $(this).data('filter');
                    if (!liveState || liveState.filterMode === mode) return;
                    liveState.filterMode = mode;
                    overlay.find('.rnd-live-filter-btn').removeClass('active');
                    $(this).addClass('active');
                    if (unityState.activeMinute !== null || unityState.playing) {
                        liveState.pendingFilterSwitch = mode;
                        console.log('[RND] Filter switch deferred until animation finishes');
                        return;
                    }
                    applyFilterSwitch(mode);
                });
            }

            // Tab switching
            overlay.on('click', '.rnd-tab', function () {
                overlay.find('.rnd-tab').removeClass('active');
                $(this).addClass('active');
                renderDialogTab($(this).attr('data-tab'), mData);
            });

            // Render default tab + start live replay
            // Add rnd-live-active class to header when live
            if (!matchIsFuture) {
                $('#rnd-overlay .rnd-dlg-head').addClass('rnd-live-active');
            }
            // Disable ALL/KEY filter buttons if not on match page
            if (!isMatchPage) {
                overlay.find('.rnd-live-filter-btn').prop('disabled', true);
            }
            renderDialogTab('lineups', mData);
            if (!matchIsFuture) {
                updateLiveHeader();
                setTimeout(() => livePlay(), 500);
            }


        };

        if (cached && cached.data) {
            show(cached.data);
        } else {
            // Fetch match data on demand
            $.get(`/ajax/match.ajax.php?id=${matchId}`).done(res => {
                const mData = typeof res === 'string' ? JSON.parse(res) : res;
                show(mData);
            });
        }
    };

    const renderDialogTab = (tab, mData) => {
        // Save Unity canvas before destroying lineups tab DOM
        // Skip for lineups — it handles in-place updates without destroying viewport
        if (tab !== 'lineups') saveUnityCanvas();
        const body = $('#rnd-dlg-body');
        const curMin = liveState ? liveState.min : 999;
        const curEvtIdx = liveState ? liveState.curEvtIdx : 999;
        // For tabs showing parameters (goals/subs/reds), defer until event text is complete
        const paramEvtIdx = (liveState && !liveState.ended && !liveState.curEvtComplete) ? curEvtIdx - 1 : curEvtIdx;
        switch (tab) {
            case 'details': renderDetailsTab(body, mData, curMin, paramEvtIdx); break;
            case 'statistics': renderStatisticsTab(body, mData, curMin, paramEvtIdx); break;
            case 'report': renderReportTab(body, mData, curMin, curEvtIdx); break;
            case 'lineups': renderLineupsTab(body, mData, curMin, paramEvtIdx); break;
            case 'venue': renderVenueTab(body, mData); break;
            case 'h2h': renderH2HTab(body, mData); break;
        }
    };

    // ── Helper: build player name lookup ──
    const buildPlayerNames = (mData) => {
        const names = {};
        ['home', 'away'].forEach(side => {
            Object.values(mData.lineup[side]).forEach(p => {
                names[p.player_id] = p.nameLast || p.name;
            });
        });
        return names;
    };

    // ── Helper: resolve [player=ID] tags in text ──
    const resolvePlayerTags = (text, playerNames) => {
        return text.replace(/\[player=(\d+)\]/g, (_, id) => {
            const name = playerNames[id] || id;
            return `<span class="rnd-player-name">${name}</span>`;
        });
    };

    const renderDetailsTab = (body, mData, curMin = 999, curEvtIdx = 999) => {
        const playerNames = buildPlayerNames(mData);
        const homeIds = new Set(Object.keys(mData.lineup.home));
        const homeId = String(mData.club.home.id);

        let html = '<div style="max-width:900px;margin:0 auto">';

        // ── Parse key events from report (filtered by current step) ──
        const events = [];
        const report = mData.report || {};
        Object.keys(report).sort((a, b) => Number(a) - Number(b)).forEach(minKey => {
            const min = Number(minKey);
            report[minKey].forEach((evt, si) => {
                if (!isEventVisible(min, si, curMin, curEvtIdx)) return;
                if (!evt.parameters) return;
                evt.parameters.forEach(param => {
                    if (param.goal) {
                        events.push({
                            min, type: 'goal',
                            isHome: String(evt.club) === homeId,
                            player: playerNames[param.goal.player] || '?',
                            assist: param.goal.assist ? (playerNames[param.goal.assist] || null) : null
                        });
                    }
                    if (param.yellow) {
                        events.push({
                            min, type: 'yellow',
                            isHome: homeIds.has(String(param.yellow)),
                            player: playerNames[param.yellow] || '?'
                        });
                    }
                    if (param.yellow_red) {
                        events.push({
                            min, type: 'yellowred',
                            isHome: homeIds.has(String(param.yellow_red)),
                            player: playerNames[param.yellow_red] || '?'
                        });
                    }
                    if (param.sub) {
                        const isHome = homeIds.has(String(param.sub.player_in)) || homeIds.has(String(param.sub.player_out));
                        events.push({
                            min, type: 'sub', isHome,
                            playerIn: playerNames[param.sub.player_in] || '?',
                            playerOut: playerNames[param.sub.player_out] || '?'
                        });
                    }
                    if (param.injury) {
                        const pid = String(param.injury);
                        events.push({
                            min, type: 'injury',
                            isHome: homeIds.has(pid),
                            player: playerNames[pid] || '?'
                        });
                    }
                });
            });
        });

        // ── Build event text (icon placement depends on side) ──
        const evtText = (evt, side) => {
            const icons = { goal: '⚽', yellow: '🟨', yellowred: '🟥', sub: '🔄', injury: '<span style="color:#ff3c3c;font-weight:800">✚</span>' };
            const icon = icons[evt.type];
            let text = '';
            if (evt.type === 'goal') {
                text = `<strong style="color:#f0fce0">${evt.player}</strong>`;
                if (evt.assist) text += ` <span style="color:#90b878;font-size:11px">(${evt.assist})</span>`;
            } else if (evt.type === 'sub') {
                text = `<span style="color:#80d848">↑ ${evt.playerIn}</span>  <span style="color:#c07050">↓ ${evt.playerOut}</span>`;
            } else if (evt.type === 'injury') {
                text = `<span style="color:#ff8c3c">${evt.player}</span>`;
            } else {
                text = evt.player;
            }
            return side === 'home' ? `${text} ${icon}` : `${icon} ${text}`;
        };

        // ── Render timeline ──
        html += '<div class="rnd-timeline">';
        events.forEach(evt => {
            const cls = evt.type === 'goal' ? ' rnd-tl-goal' : '';
            html += `<div class="rnd-tl-row${cls}">`;
            html += `<div class="rnd-tl-home">${evt.isHome ? evtText(evt, 'home') : ''}</div>`;
            html += `<div class="rnd-tl-min">${evt.min}'</div>`;
            html += `<div class="rnd-tl-away">${!evt.isHome ? evtText(evt, 'away') : ''}</div>`;
            html += `</div>`;
        });
        html += '</div></div>';

        body.html(html);
    };

    const renderReportTab = (body, mData, curMin = 999, curEvtIdx = 999) => {
        const playerNames = buildPlayerNames(mData);
        const homeId = String(mData.club.home.id);
        const report = mData.report || {};
        const allMinutes = Object.keys(report).sort((a, b) => Number(a) - Number(b));

        let html = '<div style="max-width:900px;margin:0 auto"><div id="rnd-report-timeline" class="rnd-timeline">';

        allMinutes.forEach(minKey => {
            const min = Number(minKey);
            report[minKey].forEach((evt, evtIdx) => {
                if (!isEventVisible(min, evtIdx, curMin, curEvtIdx)) return;
                html += buildReportEventHtml(evt, min, evtIdx, playerNames, homeId);
            });
        });

        html += '</div></div>';
        body.html(html);

        // Accordion toggle (delegated)
        body.off('click.rndacc').on('click.rndacc', '.rnd-acc-head', function () {
            $(this).closest('.rnd-acc').toggleClass('open');
        });
    };

    const renderStatisticsTab = (body, mData, curMin = 999, curEvtIdx = 999) => {
        const md = mData.match_data;
        const homeClub = mData.club.home.club_name;
        const awayClub = mData.club.away.club_name;
        const homeId = String(mData.club.home.id);
        const awayId = String(mData.club.away.id);
        const homeLogo = `/pics/club_logos/${homeId}_140.png`;
        const awayLogo = `/pics/club_logos/${awayId}_140.png`;

        // ── Count stats from report (filtered by current step) ──
        const stats = { homeYellow: 0, awayYellow: 0, homeRed: 0, awayRed: 0, homeShots: 0, awayShots: 0, homeSoT: 0, awaySoT: 0, homeSetPieces: 0, awaySetPieces: 0, homePenalties: 0, awayPenalties: 0 };
        const homeIds = new Set(Object.keys(mData.lineup.home));
        const report = mData.report || {};
        Object.keys(report).forEach(minKey => {
            const min = Number(minKey);
            report[minKey].forEach((evt, si) => {
                if (!isEventVisible(min, si, curMin, curEvtIdx)) return;
                if (!evt.parameters) return;
                evt.parameters.forEach(param => {
                    if (param.yellow) {
                        if (homeIds.has(String(param.yellow))) stats.homeYellow++; else stats.awayYellow++;
                    }
                    if (param.yellow_red) {
                        if (homeIds.has(String(param.yellow_red))) stats.homeRed++; else stats.awayRed++;
                    }
                    if (param.red) {
                        if (homeIds.has(String(param.red))) stats.homeRed++; else stats.awayRed++;
                    }
                    if (param.shot) {
                        const isHomeSide = String(param.shot.team) === homeId;
                        if (isHomeSide) { stats.homeShots++; if (param.shot.target === 'on') stats.homeSoT++; }
                        else { stats.awayShots++; if (param.shot.target === 'on') stats.awaySoT++; }
                    }
                    if (param.set_piece) {
                        if (homeIds.has(String(param.set_piece))) stats.homeSetPieces++; else stats.awaySetPieces++;
                    }
                });
                // Penalty detection: "penalty" is a separate parameter, not inside goal
                const isPen = evt.parameters.some(p => p.penalty);
                const hasGoalP = evt.parameters.some(p => p.goal);
                if (isPen && hasGoalP) {
                    const gp = evt.parameters.find(p => p.goal);
                    if (homeIds.has(String(gp.goal.player))) stats.homePenalties++; else stats.awayPenalties++;
                }
            });
        });

        let html = '<div class="rnd-stats-wrap">';

        // Team header with logos
        html += '<div class="rnd-stats-team-header">';
        html += `<div class="rnd-stats-team-side home"><img class="rnd-stats-team-logo" src="${homeLogo}" onerror="this.style.display='none'"><span class="rnd-stats-team-name">${homeClub}</span></div>`;
        html += '<span class="rnd-stats-vs">vs</span>';
        html += `<div class="rnd-stats-team-side away"><img class="rnd-stats-team-logo" src="${awayLogo}" onerror="this.style.display='none'"><span class="rnd-stats-team-name">${awayClub}</span></div>`;
        html += '</div>';

        // Helper: single combined bar row
        const barRow = (label, hVal, aVal, highlight = false) => {
            const hNum = typeof hVal === 'string' ? parseFloat(hVal) : hVal;
            const aNum = typeof aVal === 'string' ? parseFloat(aVal) : aVal;
            const total = hNum + aNum;
            const hPct = total === 0 ? 50 : Math.round(hNum / total * 100);
            const aPct = 100 - hPct;
            const hLead = hNum > aNum ? ' leading' : '';
            const aLead = aNum > hNum ? ' leading' : '';
            const cls = highlight ? 'rnd-stat-row rnd-stat-row-highlight' : 'rnd-stat-row';
            return `<div class="${cls}">
                <div class="rnd-stat-header">
                    <span class="rnd-stat-val home${hLead}">${hVal}</span>
                    <span class="rnd-stat-label">${label}</span>
                    <span class="rnd-stat-val away${aLead}">${aVal}</span>
                </div>
                <div class="rnd-stat-bar-wrap">
                    <div class="rnd-stat-seg home" style="width:${hPct}%"></div>
                    <div class="rnd-stat-seg away" style="width:${aPct}%"></div>
                </div>
            </div>`;
        };

        // Possession (only available at full time)
        const matchEnded = !liveState || liveState.ended;
        if (md.possession && matchEnded) {
            const h = Number(md.possession.home), a = Number(md.possession.away);
            html += barRow('Possession', h + '%', a + '%', true);
        }

        html += '<div class="rnd-stat-divider"></div>';
        html += barRow('Shots', stats.homeShots, stats.awayShots);
        html += barRow('On Target', stats.homeSoT, stats.awaySoT);
        html += '<div class="rnd-stat-divider"></div>';
        html += barRow('Yellow Cards', stats.homeYellow, stats.awayYellow);
        html += barRow('Red Cards', stats.homeRed, stats.awayRed);
        html += '<div class="rnd-stat-divider"></div>';
        html += barRow('Set Pieces', stats.homeSetPieces, stats.awaySetPieces);
        if (stats.homePenalties || stats.awayPenalties) {
            html += barRow('Penalties', stats.homePenalties, stats.awayPenalties);
        }

        // ── Advanced Stats: Attacking Styles ──
        const ATTACK_STYLES = [
            { key: 'cou', label: 'Direct' },
            { key: 'kco', label: 'Direct' },
            { key: 'klo', label: 'Long Balls' },
            { key: 'sho', label: 'Short Passing' },
            { key: 'thr', label: 'Through Balls' },
            { key: 'lon', label: 'Long Balls' },
            { key: 'win', label: 'Wings' },
            { key: 'doe', label: 'Corners' },
            { key: 'dire', label: 'Free Kicks' },
        ];
        const STYLE_ORDER = ['Direct', 'Short Passing', 'Through Balls', 'Long Balls', 'Wings', 'Corners', 'Free Kicks', 'Penalties'];
        const SKIP_PREFIXES = new Set(['card', 'cod', 'inj']);
        const playerNames = buildPlayerNames(mData);

        // Collect per-style, per-side data
        const advData = { home: {}, away: {} };
        STYLE_ORDER.forEach(s => { advData.home[s] = { a: 0, l: 0, sh: 0, g: 0, events: [] }; advData.away[s] = { a: 0, l: 0, sh: 0, g: 0, events: [] }; });

        const sortedMins = Object.keys(report).map(Number).sort((a, b) => a - b);
        for (const min of sortedMins) {
            const evts = report[String(min)] || [];
            for (let si = 0; si < evts.length; si++) {
                const evt = evts[si];
                if (!evt.type) continue;
                if (!isEventVisible(min, si, curMin, curEvtIdx)) continue;
                const prefix = evt.type.replace(/[0-9]+.*/, '');

                // Handle penalty events (type starts with p_)
                const isPenEvt = /^p_/.test(evt.type);
                const hasShot = evt.parameters?.some(p => p.shot);
                const hasGoal = evt.parameters?.some(p => p.goal);
                const hasPenParam = evt.parameters?.some(p => p.penalty);

                if (isPenEvt && hasPenParam && hasGoal) {
                    const club = String(evt.club);
                    const side = club === homeId ? 'home' : 'away';
                    const pd = advData[side]['Penalties'];
                    pd.a++; pd.g++; pd.sh++;
                    pd.events.push({ min, evt, evtIdx: si, result: 'goal' });
                    continue;
                } else if (isPenEvt && hasShot && !hasGoal) {
                    const club = String(evt.club);
                    const side = club === homeId ? 'home' : 'away';
                    const pd = advData[side]['Penalties'];
                    pd.a++; pd.sh++;
                    pd.events.push({ min, evt, evtIdx: si, result: 'shot' });
                    continue;
                }

                if (SKIP_PREFIXES.has(prefix)) continue;
                const styleEntry = ATTACK_STYLES.find(s => s.key === prefix);
                if (!styleEntry) continue;
                const label = styleEntry.label;
                const club = String(evt.club);
                const side = club === homeId ? 'home' : 'away';
                const d = advData[side][label];
                d.a++;
                let result = 'lost';
                if (hasGoal) { d.g++; d.sh++; result = 'goal'; }
                else if (hasShot) { d.sh++; result = 'shot'; }
                else { d.l++; }
                d.events.push({ min, evt, evtIdx: si, result });
            }
        }

        // Build advanced section HTML
        html += '<div class="rnd-adv-section">';
        html += '<div class="rnd-adv-title">Attacking Styles</div>';

        const buildAdvTable = (teamName, side, sideClass) => {
            let t = `<div class="rnd-adv-team-label" style="color:${sideClass === 'home' ? '#80e048' : '#5ba8f0'}">${teamName}</div>`;
            t += '<table class="rnd-adv-table">';
            t += '<tr><th>Style</th><th>Att</th><th>Lost</th><th>Shot</th><th>Goal</th><th>Conv%</th></tr>';
            let totA = 0, totL = 0, totSh = 0, totG = 0;
            STYLE_ORDER.forEach(style => {
                const d = advData[side][style];
                totA += d.a; totL += d.l; totSh += d.sh; totG += d.g;
                const pct = d.a ? Math.round(d.g / d.a * 100) + '%' : '-';
                const cls = (v, type) => v === 0 ? 'adv-zero' : type;
                const rowId = `adv-${sideClass}-${style.replace(/\s/g, '-')}`;
                const hasEvents = d.events.length > 0;
                t += `<tr class="rnd-adv-row${hasEvents ? '' : ' rnd-adv-total'}" ${hasEvents ? 'data-adv-target="' + rowId + '"' : ''}>`;
                t += `<td>${style}${hasEvents ? ' <span class="adv-arrow">&#9654;</span>' : ''}</td>`;
                t += `<td class="${cls(d.a, '')}">${d.a}</td>`;
                t += `<td class="${cls(d.l, 'adv-lost')}">${d.l}</td>`;
                t += `<td class="${cls(d.sh, 'adv-shot')}">${d.sh}</td>`;
                t += `<td class="${cls(d.g, 'adv-goal')}">${d.g}</td>`;
                t += `<td class="${cls(d.a ? d.g : 0, '')}">${pct}</td>`;
                t += '</tr>';
                if (hasEvents) {
                    t += `<tr class="rnd-adv-events" id="${rowId}"><td colspan="6"><div class="rnd-adv-evt-list">`;
                    d.events.forEach(e => {
                        t += `<div class="rnd-adv-evt"><span class="adv-result-tag ${e.result}">${e.result}</span>${buildReportEventHtml(e.evt, e.min, e.evtIdx, playerNames, homeId)}</div>`;
                    });
                    t += '</div></td></tr>';
                }
            });
            // Total row
            const totPct = totA ? Math.round(totG / totA * 100) + '%' : '-';
            t += '<tr class="rnd-adv-row rnd-adv-total">';
            t += `<td>Total</td><td>${totA}</td><td>${totL}</td><td>${totSh}</td><td>${totG}</td><td>${totPct}</td>`;
            t += '</tr>';
            t += '</table>';
            return t;
        };

        html += buildAdvTable(homeClub, 'home', 'home');
        html += buildAdvTable(awayClub, 'away', 'away');
        html += '</div>';

        // ── Player Statistics (from video segments) ──
        const PASS_VIDS = /^(short|preshort|through|longball|gkthrow|gkkick)/;
        const CROSS_VIDS = /^(wing(?!start)|cornerkick|freekick)/;
        const DEFWIN_VIDS = /^defwin/;
        const FINISH_VIDS = /^(finish|finishlong|header|acrobat)/;
        const RUN_DUEL_VIDS = /^finrun/;
        const homeIdsSet = new Set(Object.keys(mData.lineup.home));
        const pStats = {};  // playerId → { sp, up, sc, uc, sh, sv, g, a, dw, dl, events[] }
        const ensureP = (id) => { if (!pStats[id]) pStats[id] = { sp: 0, up: 0, sc: 0, uc: 0, sh: 0, sv: 0, g: 0, a: 0, dw: 0, dl: 0, events: [] }; return pStats[id]; };

        for (const min of sortedMins) {
            const evts = report[String(min)] || [];
            for (let si = 0; si < evts.length; si++) {
                const evt = evts[si];
                if (!isEventVisible(min, si, curMin, curEvtIdx)) continue;
                const vids = evt.chance?.video;
                const evtHasShot = evt.parameters?.some(p => p.shot);
                if (vids && Array.isArray(vids)) {
                    for (let vi = 0; vi < vids.length; vi++) {
                        const v = vids[vi];
                        if (PASS_VIDS.test(v.video)) {
                            const passerId = /^gk(throw|kick)/.test(v.video) ? v.gk : v.att1;
                            if (passerId) {
                                // For preshort videos, only count if the passer is mentioned in the text
                                const isPreshort = /^preshort/.test(v.video);
                                const textLines = (evt.chance?.text?.[vi] || []);
                                if (isPreshort && !textLines.some(l => l.includes('[player=' + passerId + ']'))) {
                                    // Skip — att1 in preshort is just part of buildup, not the actual passer
                                } else {
                                    const p = ensureP(passerId);
                                    const failed = vi + 1 < vids.length && DEFWIN_VIDS.test(vids[vi + 1].video);
                                    if (failed) { p.up++; p.events.push({ min, evtIdx: si, evt, action: 'pass_fail' }); }
                                    else { p.sp++; p.events.push({ min, evtIdx: si, evt, action: 'pass_ok' }); }
                                }
                            }
                        }
                        if (CROSS_VIDS.test(v.video) && v.att1) {
                            // Free kick shot on goal → count as shot, not cross
                            if (/^freekick/.test(v.video) && evtHasShot) {
                                ensureP(v.att1).sh++;
                                ensureP(v.att1).events.push({ min, evtIdx: si, evt, action: 'shot' });
                            } else {
                                const p = ensureP(v.att1);
                                const failed = vi + 1 < vids.length && DEFWIN_VIDS.test(vids[vi + 1].video);
                                if (failed) { p.uc++; p.events.push({ min, evtIdx: si, evt, action: 'cross_fail' }); }
                                else { p.sc++; p.events.push({ min, evtIdx: si, evt, action: 'cross_ok' }); }
                            }
                        }
                        if (FINISH_VIDS.test(v.video) && v.att1) {
                            // Only count if next segment is NOT also a finish (avoid double-counting acrobat7+acrobat8 etc.)
                            const nextIsAlsoFinish = vi + 1 < vids.length && FINISH_VIDS.test(vids[vi + 1].video);
                            if (!nextIsAlsoFinish) {
                                const hasGoalForShooter = evt.parameters?.some(p => p.goal && String(p.goal.player) === String(v.att1));
                                ensureP(v.att1).sh++;
                                if (!hasGoalForShooter) {
                                    ensureP(v.att1).events.push({ min, evtIdx: si, evt, action: 'shot' });
                                }
                            }
                        }
                        if (DEFWIN_VIDS.test(v.video)) {
                            const tLines = (evt.chance?.text || []).flat();
                            const winner = [v.def1, v.def2].find(d => d && tLines.some(l => l.includes('[player=' + d + ']')));
                            if (winner) { ensureP(winner).dw++; ensureP(winner).events.push({ min, evtIdx: si, evt, action: 'duel_won' }); }
                        }
                        if (RUN_DUEL_VIDS.test(v.video)) {
                            const nextIsDefwin = vi + 1 < vids.length && DEFWIN_VIDS.test(vids[vi + 1].video);
                            if (!nextIsDefwin) {
                                const tLines = (evt.chance?.text || []).flat();
                                [v.def1, v.def2].forEach(d => {
                                    if (d && tLines.some(l => l.includes('[player=' + d + ']'))) {
                                        ensureP(d).dl++;
                                        ensureP(d).events.push({ min, evtIdx: si, evt, action: 'duel_lost' });
                                    }
                                });
                            }
                        }
                        if (/^save/.test(v.video) && v.gk) {
                            ensureP(v.gk).sv++;
                            ensureP(v.gk).events.push({ min, evtIdx: si, evt, action: 'save' });
                        }
                    }
                }
                if (evt.parameters) {
                    evt.parameters.forEach(param => {
                        if (param.goal) {
                            const scorer = String(param.goal.player);
                            ensureP(scorer).g++;
                            ensureP(scorer).events.push({ min, evtIdx: si, evt, action: 'goal' });
                            if (param.goal.assist) {
                                const assistP = String(param.goal.assist);
                                ensureP(assistP).a++;
                                ensureP(assistP).events.push({ min, evtIdx: si, evt, action: 'assist' });
                            }
                        }
                    });
                }
            }
        }

        html += '<div class="rnd-adv-section">';
        html += '<div class="rnd-adv-title">Player Statistics</div>';

        const ACTION_LABELS = { pass_ok: 'pass \u2713', pass_fail: 'pass \u2717', cross_ok: 'cross \u2713', cross_fail: 'cross \u2717', shot: 'shot', save: 'save', goal: 'goal', assist: 'assist', duel_won: 'duel \u2713', duel_lost: 'duel \u2717' };
        const ACTION_CLS = { pass_ok: 'shot', pass_fail: 'lost', cross_ok: 'shot', cross_fail: 'lost', shot: 'shot', save: 'shot', goal: 'goal', assist: 'goal', duel_won: 'shot', duel_lost: 'lost' };

        // Build sub events for minutes-played calculation
        const subEvents = {};  // playerId → { subInMin, subOutMin }
        for (const min of sortedMins) {
            (report[String(min)] || []).forEach(evt => {
                if (!evt.parameters) return;
                evt.parameters.forEach(param => {
                    if (param.sub) {
                        const inId = String(param.sub.player_in);
                        const outId = String(param.sub.player_out);
                        if (!subEvents[inId]) subEvents[inId] = {};
                        subEvents[inId].subInMin = min;
                        if (!subEvents[outId]) subEvents[outId] = {};
                        subEvents[outId].subOutMin = min;
                    }
                });
            });
        }
        const matchEndMin = mData.match_data?.regular_last_min || Math.max(...sortedMins, 90);
        const posOrder = { gk: 0, dl: 1, dcl: 2, dc: 3, dcr: 4, dr: 5, dml: 6, dmcl: 7, dmc: 8, dmcr: 9, dmr: 10, ml: 11, mcl: 12, mc: 13, mcr: 14, mr: 15, oml: 16, omcl: 17, omc: 18, omcr: 19, omr: 20, fcl: 21, fc: 22, fcr: 23 };
        const ratClr = (r) => {
            if (!r || r === 0) return '#5a7a48';
            const v = Number(r);
            if (v >= 9.0) return '#00c040';
            if (v >= 8.5) return '#00dd50';
            if (v >= 8.0) return '#22e855';
            if (v >= 7.5) return '#44ee55';
            if (v >= 7.0) return '#66dd44';
            if (v >= 6.5) return '#88cc33';
            if (v >= 6.0) return '#99bb22';
            if (v >= 5.5) return '#aacc00';
            if (v >= 5.0) return '#bbcc00';
            if (v >= 4.5) return '#dd9900';
            if (v >= 4.0) return '#ee7733';
            if (v >= 3.5) return '#ee5533';
            if (v >= 3.0) return '#dd3333';
            if (v >= 2.0) return '#cc2222';
            return '#bb1111';
        };

        const buildPlayerTable = (teamName, side, sideClass) => {
            const lineup = mData.lineup[side];
            const starters = [], playedSubs = [];
            Object.entries(lineup).forEach(([id, p]) => {
                const isSub = p.position.includes('sub');
                const se = subEvents[String(p.player_id)] || {};
                if (isSub && !se.subInMin) return;  // Sub who never played — skip
                let minsPlayed;
                if (isSub) {
                    const endMin = se.subOutMin || matchEndMin;
                    minsPlayed = endMin - se.subInMin;
                } else {
                    const endMin = se.subOutMin || matchEndMin;
                    minsPlayed = endMin;
                }
                const entry = { id: String(p.player_id), p, minsPlayed };
                if (isSub) playedSubs.push(entry);
                else starters.push(entry);
            });
            starters.sort((a, b) => (posOrder[a.p.position] ?? 99) - (posOrder[b.p.position] ?? 99));
            playedSubs.sort((a, b) => (subEvents[a.id]?.subInMin || 99) - (subEvents[b.id]?.subInMin || 99));
            const players = [...starters, ...playedSubs];

            let t = `<div class="rnd-adv-team-label" style="color:${sideClass === 'home' ? '#80e048' : '#5ba8f0'}">${teamName}</div>`;
            t += '<table class="rnd-adv-table">';
            const colCount = matchEnded ? 12 : 11;
            t += '<tr><th>Player</th><th title="Minutes Played">Min</th><th title="Successful Passes">SP</th><th title="Unsuccessful Passes">UP</th><th title="Successful Crosses">SC</th><th title="Unsuccessful Crosses">UC</th><th title="Shots / Saves">Sh</th><th>G</th><th>A</th><th title="Duels Won">DW</th><th title="Duels Lost">DL</th>' + (matchEnded ? '<th>Rat</th>' : '') + '</tr>';
            let totSP = 0, totUP = 0, totSC = 0, totUC = 0, totSh = 0, totG = 0, totA = 0, totDW = 0, totDL = 0;
            players.forEach(({ id, p, minsPlayed }) => {
                const s = pStats[id] || { sp: 0, up: 0, sc: 0, uc: 0, sh: 0, sv: 0, g: 0, a: 0, dw: 0, dl: 0, events: [] };
                const isGK = p.position === 'gk';
                totSP += s.sp; totUP += s.up; totSC += s.sc; totUC += s.uc; totSh += (isGK ? s.sv : s.sh); totG += s.g; totA += s.a; totDW += s.dw; totDL += s.dl;
                const rowId = `plr-${sideClass}-${id}`;
                const hasEvts = s.events.length > 0;
                const cls = (v, type) => v === 0 ? 'adv-zero' : type;
                const isSub = p.position.includes('sub');
                t += `<tr class="rnd-adv-row${hasEvts ? '' : ' rnd-adv-total'}" ${hasEvts ? 'data-adv-target="' + rowId + '"' : ''}>`;
                t += `<td>${isSub ? '<span style="color:#6a9a58;font-size:9px">↑</span> ' : ''}${playerNames[id] || id}${hasEvts ? ' <span class="adv-arrow">&#9654;</span>' : ''}</td>`;
                t += `<td style="color:#8aac72">${minsPlayed}'</td>`;
                t += `<td class="${cls(s.sp, '')}">${s.sp}</td>`;
                t += `<td class="${cls(s.up, 'adv-lost')}">${s.up}</td>`;
                t += `<td class="${cls(s.sc, '')}">${s.sc}</td>`;
                t += `<td class="${cls(s.uc, 'adv-lost')}">${s.uc}</td>`;
                t += isGK ? `<td class="${cls(s.sv, 'adv-shot')}" title="Saves">${s.sv} 🧤</td>` : `<td class="${cls(s.sh, 'adv-shot')}">${s.sh}</td>`;
                t += `<td class="${cls(s.g, 'adv-goal')}">${s.g}</td>`;
                t += `<td class="${cls(s.a, 'adv-goal')}">${s.a}</td>`;
                t += `<td class="${cls(s.dw, '')}">${s.dw}</td>`;
                t += `<td class="${cls(s.dl, 'adv-lost')}">${s.dl}</td>`;
                if (matchEnded) {
                    const rFmt = p.rating ? Number(p.rating).toFixed(2) : '-';
                    t += `<td style="font-weight:700;color:${ratClr(p.rating)}">${rFmt}</td>`;
                }
                t += '</tr>';
                if (hasEvts) {
                    t += `<tr class="rnd-adv-events" id="${rowId}"><td colspan="${colCount}"><div class="rnd-adv-evt-list">`;
                    s.events.forEach(ev => {
                        const acls = ACTION_CLS[ev.action] || '';
                        const albl = ACTION_LABELS[ev.action] || ev.action;
                        t += `<div class="rnd-adv-evt"><span class="adv-result-tag ${acls}">${albl}</span>${buildReportEventHtml(ev.evt, ev.min, ev.evtIdx, playerNames, homeId)}</div>`;
                    });
                    t += '</div></td></tr>';
                }
            });
            const clsT = (v, type) => v === 0 ? 'adv-zero' : type;
            t += '<tr class="rnd-adv-row rnd-adv-total">';
            t += `<td>Total</td><td></td><td>${totSP}</td><td class="${clsT(totUP, 'adv-lost')}">${totUP}</td><td>${totSC}</td><td class="${clsT(totUC, 'adv-lost')}">${totUC}</td><td>${totSh}</td><td class="${clsT(totG, 'adv-goal')}">${totG}</td><td class="${clsT(totA, 'adv-goal')}">${totA}</td><td>${totDW}</td><td class="${clsT(totDL, 'adv-lost')}">${totDL}</td>` + (matchEnded ? '<td></td>' : '');
            t += '</tr>';
            t += '</table>';
            return t;
        };

        html += buildPlayerTable(homeClub, 'home', 'home');
        html += buildPlayerTable(awayClub, 'away', 'away');
        html += '</div>';

        html += '</div>';
        body.html(html);

        // Wire up expand/collapse for adv rows
        body.find('.rnd-adv-row[data-adv-target]').on('click', function () {
            const targetId = $(this).data('adv-target');
            const evtRow = document.getElementById(targetId);
            if (evtRow) {
                $(this).toggleClass('expanded');
                $(evtRow).toggleClass('visible');
            }
        });
        // Wire up accordion toggle for embedded report events
        body.off('click.rndacc').on('click.rndacc', '.rnd-acc-head', function (e) {
            e.stopPropagation();
            $(this).closest('.rnd-acc').toggleClass('open');
        });
    };

    const renderVenueTab = (body, mData) => {
        const md = mData.match_data;
        const venue = md.venue;
        const weatherBase = (venue.weather || '').replace(/\d+/g, '');
        const weatherMap = {
            sunny: { icon: '☀️', text: 'Sunny', desc: 'Clear skies' },
            cloudy: { icon: '⛅', text: 'Cloudy', desc: 'Partly cloudy' },
            rainy: { icon: '🌧️', text: 'Rain', desc: 'Wet conditions' },
            snow: { icon: '❄️', text: 'Snow', desc: 'Snowy pitch' },
            overcast: { icon: '☁️', text: 'Overcast', desc: 'Heavy clouds' }
        };
        const w = weatherMap[weatherBase] || { icon: '🌤️', text: weatherBase.charAt(0).toUpperCase() + weatherBase.slice(1), desc: '' };

        const capacity = Number(venue.capacity) || 0;
        const attendance = Number(md.attendance) || 0;
        const attPct = capacity ? Math.round(attendance / capacity * 100) : 0;
        const pitchPct = Number(venue.pitch_condition) || 0;

        // Stadium SVG illustration
        const stadiumSvg = `<svg class="rnd-venue-stadium-svg" width="220" height="80" viewBox="0 0 220 80" fill="none" xmlns="http://www.w3.org/2000/svg">
            <ellipse cx="110" cy="65" rx="100" ry="12" fill="#2a5a16" stroke="#4a9030" stroke-width="1"/>
            <path d="M25 65 L25 30 Q25 22 33 20 L55 16 Q60 15 60 20 L60 65" fill="#1e4412" stroke="#4a9030" stroke-width="0.8"/>
            <path d="M60 65 L60 22 Q60 14 68 12 L102 6 Q110 5 110 12 L110 65" fill="#1a3d0f" stroke="#4a9030" stroke-width="0.8"/>
            <path d="M110 65 L110 12 Q110 5 118 6 L152 12 Q160 14 160 22 L160 65" fill="#1a3d0f" stroke="#4a9030" stroke-width="0.8"/>
            <path d="M160 65 L160 20 Q160 15 165 16 L187 20 Q195 22 195 30 L195 65" fill="#1e4412" stroke="#4a9030" stroke-width="0.8"/>
            <rect x="70" y="50" width="80" height="15" rx="2" fill="#2d5e1a" stroke="#4a9030" stroke-width="0.6"/>
            <line x1="110" y1="50" x2="110" y2="65" stroke="#4a9030" stroke-width="0.4"/>
            <circle cx="110" cy="57" r="4" stroke="#4a9030" stroke-width="0.4" fill="none"/>
            <rect x="72" y="53" width="12" height="9" rx="1" fill="none" stroke="#4a9030" stroke-width="0.4"/>
            <rect x="136" y="53" width="12" height="9" rx="1" fill="none" stroke="#4a9030" stroke-width="0.4"/>
            ${[35, 50, 65, 80, 140, 155, 170, 185].map(x => `<rect x="${x - 1}" y="${x < 110 ? 28 : 28}" width="2" height="4" rx="0.5" fill="#6cc048" opacity="0.5"/>`).join('')}
            <ellipse cx="45" cy="26" rx="14" ry="3" fill="none" stroke="#4a9030" stroke-width="0.4" opacity="0.4"/>
            <ellipse cx="175" cy="26" rx="14" ry="3" fill="none" stroke="#4a9030" stroke-width="0.4" opacity="0.4"/>
        </svg>`;

        let html = '<div class="rnd-venue-wrap">';

        // Hero section with stadium
        html += '<div class="rnd-venue-hero">';
        html += stadiumSvg;
        html += `<div class="rnd-venue-name">${venue.name}</div>`;
        html += `<div class="rnd-venue-city">📍 ${venue.city}</div>`;
        html += `<div class="rnd-venue-tournament"><span>🏆 ${venue.tournament}</span></div>`;
        html += '</div>';

        // Capacity & Attendance cards
        html += '<div class="rnd-venue-cards">';
        html += `<div class="rnd-venue-card">
            <div class="rnd-venue-card-icon">🏟️</div>
            <div class="rnd-venue-card-value">${capacity.toLocaleString()}</div>
            <div class="rnd-venue-card-label">Capacity</div>
        </div>`;
        html += `<div class="rnd-venue-card">
            <div class="rnd-venue-card-icon">👥</div>
            <div class="rnd-venue-card-value">${attendance ? attendance.toLocaleString() : '—'}</div>
            <div class="rnd-venue-card-label">Attendance</div>
        </div>`;
        html += '</div>';

        // Attendance gauge bar
        if (attendance && capacity) {
            html += '<div class="rnd-venue-gauge-wrap">';
            html += '<div class="rnd-venue-gauge-header">';
            html += '<span class="rnd-venue-gauge-title">Stadium Fill</span>';
            html += `<span class="rnd-venue-gauge-value">${attPct}%</span>`;
            html += '</div>';
            html += `<div class="rnd-venue-gauge-bar"><div class="rnd-venue-gauge-fill attendance" style="width:${attPct}%"></div></div>`;
            html += '</div>';
        }

        // Weather card
        html += '<div class="rnd-venue-weather">';
        html += `<div class="rnd-venue-weather-icon">${w.icon}</div>`;
        html += '<div class="rnd-venue-weather-info">';
        html += `<div class="rnd-venue-weather-text">${w.text}</div>`;
        html += `<div class="rnd-venue-weather-sub">${w.desc}</div>`;
        html += '</div></div>';

        // Pitch condition gauge
        html += '<div class="rnd-venue-gauge-wrap">';
        html += '<div class="rnd-venue-gauge-header">';
        html += '<span class="rnd-venue-gauge-title">Pitch Condition</span>';
        html += `<span class="rnd-venue-gauge-value">${pitchPct}%</span>`;
        html += '</div>';
        const pitchColor = pitchPct >= 80 ? '#4a9030' : pitchPct >= 50 ? '#b8a030' : '#a04030';
        html += `<div class="rnd-venue-gauge-bar"><div class="rnd-venue-gauge-fill" style="width:${pitchPct}%;background:linear-gradient(90deg,${pitchColor},${pitchColor}dd)"></div></div>`;
        html += '</div>';

        // Facilities grid
        html += '<div class="rnd-venue-facilities">';
        const facilities = [
            { key: 'sprinklers', icon: '💧', label: 'Sprinklers' },
            { key: 'draining', icon: '🚰', label: 'Draining' },
            { key: 'pitchcover', icon: '🛡️', label: 'Pitch Cover' },
            { key: 'heating', icon: '🔥', label: 'Heating' },
        ];
        facilities.forEach(f => {
            const active = venue[f.key] ? 'active' : '';
            html += `<div class="rnd-venue-facility ${active}">
                <div class="rnd-venue-facility-icon">${f.icon}</div>
                <div class="rnd-venue-facility-label">${f.label}</div>
                <div class="rnd-venue-facility-status">${venue[f.key] ? '✓ Yes' : '✗ No'}</div>
            </div>`;
        });
        html += '</div>';

        html += '</div>';
        body.html(html);
    };
    const renderLineupsTab = (body, mData, curMin = 999, curEvtIdx = 999) => {
        const matchEnded = !liveState || liveState.ended;
        const homeColor = '#' + (mData.club.home.colors?.club_color1 || '4a9030');
        const awayColor = '#' + (mData.club.away.colors?.club_color1 || '5b9bff');
        const homeId = mData.club.home.id;
        const awayId = mData.club.away.id;

        // Split starters and subs
        const splitLineup = (lineup) => {
            const starters = [], subs = [];
            Object.values(lineup).forEach(p => {
                if (p.position.includes('sub')) subs.push(p); else starters.push(p);
            });
            // Sort starters by position order
            const posOrder = { gk: 0, dl: 1, dcl: 2, dc: 3, dcr: 4, dr: 5, dml: 6, dmcl: 7, dmc: 8, dmcr: 9, dmr: 10, ml: 11, mcl: 12, mc: 13, mcr: 14, mr: 15, oml: 16, omcl: 17, omc: 18, omcr: 19, omr: 20, fcl: 21, fc: 22, fcr: 23 };
            starters.sort((a, b) => (posOrder[a.position] ?? 99) - (posOrder[b.position] ?? 99));
            subs.sort((a, b) => Number(a.position.replace('sub', '')) - Number(b.position.replace('sub', '')));
            return { starters, subs };
        };

        const home = splitLineup(mData.lineup.home);
        const away = splitLineup(mData.lineup.away);

        // Build event stats per player from report (filtered by current step)
        const pEvents = {};  // player_id → { goals, assists, yellows, reds, subIn, subOut, injured }
        const initPE = () => ({ goals: 0, assists: 0, yellows: 0, reds: 0, subIn: false, subOut: false, injured: false });
        const report = mData.report || {};
        Object.keys(report).forEach(minKey => {
            const eMin = Number(minKey);
            report[minKey].forEach((evt, si) => {
                if (!isEventVisible(eMin, si, curMin, curEvtIdx)) return;
                if (!evt.parameters) return;
                evt.parameters.forEach(param => {
                    if (param.goal) {
                        const pid = String(param.goal.player);
                        if (!pEvents[pid]) pEvents[pid] = initPE();
                        pEvents[pid].goals++;
                        if (param.goal.assist) {
                            const aid = String(param.goal.assist);
                            if (!pEvents[aid]) pEvents[aid] = initPE();
                            pEvents[aid].assists++;
                        }
                    }
                    if (param.yellow) {
                        const pid = String(param.yellow);
                        if (!pEvents[pid]) pEvents[pid] = initPE();
                        pEvents[pid].yellows++;
                    }
                    if (param.yellow_red) {
                        const pid = String(param.yellow_red);
                        if (!pEvents[pid]) pEvents[pid] = initPE();
                        pEvents[pid].reds++; pEvents[pid].yellows++;
                    }
                    if (param.red) {
                        const pid = String(param.red);
                        if (!pEvents[pid]) pEvents[pid] = initPE();
                        pEvents[pid].reds++;
                    }
                    if (param.injury) {
                        const pid = String(param.injury);
                        if (!pEvents[pid]) pEvents[pid] = initPE();
                        pEvents[pid].injured = true;
                    }
                    if (param.sub) {
                        const inId = String(param.sub.player_in);
                        const outId = String(param.sub.player_out);
                        if (!pEvents[inId]) pEvents[inId] = initPE();
                        if (!pEvents[outId]) pEvents[outId] = initPE();
                        pEvents[inId].subIn = true;
                        pEvents[outId].subOut = true;
                    }
                });
            });
        });

        // Build event icons string for a player
        const eventIcons = (pid) => {
            const e = pEvents[String(pid)];
            if (!e) return '';
            let s = '';
            if (e.goals) s += (e.goals > 1 ? e.goals + '×' : '') + '⚽';
            if (e.assists) s += (e.assists > 1 ? e.assists + '×' : '') + '👟';
            if (e.yellows) s += (e.yellows > 1 ? e.yellows + '×' : '') + '🟨';
            if (e.reds) s += (e.reds > 1 ? e.reds + '×' : '') + '🟥';
            if (e.injured) s += '<span style="color:#ff3c3c;font-size:13px;font-weight:800">✚</span>';
            if (e.subIn) s += '🔼';
            if (e.subOut) s += '🔽';
            return s;
        };

        // Format name: "M. Radic" from "V. Tutić" or nameLast="Tutić", name="V. Tutić"
        const fmtName = (p) => {
            const full = p.name || '';
            const last = p.nameLast || '';
            if (last && full) {
                const firstChar = full.charAt(0);
                return `${firstChar}. ${last}`;
            }
            return last || full;
        };

        // Match rating color (1-10 scale, 5.0 = cutoff between red and green)
        const ratingColor = (r) => {
            if (!r || r === 0) return '#5a7a48';
            const v = Number(r);
            if (v >= 9.0) return '#00c040';
            if (v >= 8.5) return '#00dd50';
            if (v >= 8.0) return '#22e855';
            if (v >= 7.5) return '#44ee55';
            if (v >= 7.0) return '#66dd44';
            if (v >= 6.5) return '#88cc33';
            if (v >= 6.0) return '#99bb22';
            if (v >= 5.5) return '#aacc00';
            if (v >= 5.0) return '#bbcc00';
            if (v >= 4.5) return '#dd9900';
            if (v >= 4.0) return '#ee7733';
            if (v >= 3.5) return '#ee5533';
            if (v >= 3.0) return '#dd3333';
            if (v >= 2.0) return '#cc2222';
            return '#bb1111';
        };
        const r5Color = (v) => {
            if (!v) return '#5a7a48';
            // Below 95: piecewise HSL tiers
            // 95-120: explicit per-integer colors for max visual differentiation
            const hsl2rgb = (h, s, l) => {
                s /= 100; l /= 100;
                const c = (1 - Math.abs(2 * l - 1)) * s;
                const x = c * (1 - Math.abs((h / 60) % 2 - 1));
                const m = l - c / 2;
                let r, g, b;
                if (h < 60) { r = c; g = x; b = 0; }
                else if (h < 120) { r = x; g = c; b = 0; }
                else { r = 0; g = c; b = x; }
                return '#' + [r + m, g + m, b + m].map(v => Math.round(v * 255).toString(16).padStart(2, '0')).join('');
            };
            // Explicit lookup for 95-120 — each integer has a unique, distinguishable color
            const topColors = {
                95: '#8db024', // olive-yellow-green
                96: '#7aad22', // yellow-green
                97: '#68a820', // limey green
                98: '#57a31e', // lime green
                99: '#479e1c', // green-lime
                100: '#38991a', // medium green
                101: '#2e9418', // green
                102: '#258e16', // rich green
                103: '#1d8814', // deeper green
                104: '#168212', // forest green
                105: '#107c10', // vivid forest
                106: '#0c720e', // dark vivid green
                107: '#09680c', // dark green
                108: '#075e0a', // darker green
                109: '#055408', // very dark green
                110: '#044a07', // deep emerald
                111: '#034106', // deepest green
                112: '#033905', // near-black green
                113: '#023204', //
                114: '#022c04', //
                115: '#022603', // almost black-green
                116: '#012103', //
                117: '#011d02', //
                118: '#011902', // darkest
            };
            const rounded = Math.round(v);
            if (rounded >= 95) return topColors[Math.min(118, rounded)] || topColors[118];
            // Below 95: HSL tiers
            const tiers = [
                [25, 50, 0, 10, 65, 68, 28, 32],
                [50, 70, 10, 25, 68, 72, 34, 40],
                [70, 80, 25, 42, 72, 75, 42, 46],
                [80, 90, 42, 58, 75, 78, 46, 48],
                [90, 95, 58, 78, 78, 80, 48, 46],
            ];
            const clamped = Math.max(25, Math.min(95, v));
            let hue = 0, sat = 65, lit = 28;
            for (const [from, to, h0, h1, s0, s1, l0, l1] of tiers) {
                if (clamped <= to) {
                    const t = (clamped - from) / (to - from);
                    hue = h0 + t * (h1 - h0);
                    sat = s0 + t * (s1 - s0);
                    lit = l0 + t * (l1 - l0);
                    break;
                }
            }
            return hsl2rgb(hue, sat, lit);
        };

        // Captain IDs from match_data
        const captains = mData.match_data.captain || {};
        const homeCaptainId = String(captains.home || '');
        const awayCaptainId = String(captains.away || '');

        const renderList = (team, color, clubName, logoId, side) => {
            const captainId = side === 'home' ? homeCaptainId : awayCaptainId;
            let h = '';
            team.starters.forEach(p => {
                const pid = String(p.player_id);
                const evts = eventIcons(p.player_id);
                const isCaptain = pid === captainId;
                const isMom = matchEnded && Number(p.mom) === 1;
                h += `<div class="rnd-lu-player rnd-lu-clickable" data-pid="${pid}">`;
                h += `<div class="rnd-lu-no" style="background:${color};color:#fff">${p.no}</div>`;
                h += `<span class="rnd-lu-name">${fmtName(p)}`;
                if (isCaptain) h += ` <span class="rnd-lu-captain" title="Captain">©</span>`;
                if (isMom) h += ` <span class="rnd-lu-mom" title="Man of the Match">⭐</span>`;
                h += `</span>`;
                if (evts) h += `<span class="rnd-lu-events">${evts}</span>`;
                h += `<span class="rnd-lu-pos">${p.position.toUpperCase()}</span>`;
                if (matchEnded) {
                    const rFmt = p.rating ? Number(p.rating).toFixed(2) : '-';
                    h += `<span class="rnd-lu-rating" style="color:${ratingColor(p.rating)}">${rFmt}</span>`;
                }
                h += `<span class="rnd-lu-r5" data-pid="${p.player_id}">···</span>`;
                h += `</div>`;
            });
            h += `<div class="rnd-lu-sub-header">Substitutes</div>`;
            team.subs.forEach(p => {
                const pid = String(p.player_id);
                const evts = eventIcons(p.player_id);
                const isCaptain = pid === captainId;
                const isMom = matchEnded && Number(p.mom) === 1;
                h += `<div class="rnd-lu-player rnd-lu-clickable" data-pid="${pid}">`;
                h += `<div class="rnd-lu-no" style="background:${color};color:#fff;opacity:0.6">${p.no}</div>`;
                h += `<span class="rnd-lu-name" style="color:#7a9a68">${fmtName(p)}`;
                if (isCaptain) h += ` <span class="rnd-lu-captain" title="Captain">©</span>`;
                if (isMom) h += ` <span class="rnd-lu-mom" title="Man of the Match">⭐</span>`;
                h += `</span>`;
                if (evts) h += `<span class="rnd-lu-events">${evts}</span>`;
                h += `<span class="rnd-lu-pos">${(p.fp || '').split(',')[0].toUpperCase()}</span>`;
                if (matchEnded) {
                    const rFmtS = p.rating ? Number(p.rating).toFixed(2) : '-';
                    h += `<span class="rnd-lu-rating" style="color:${ratingColor(p.rating)}">${rFmtS}</span>`;
                }
                h += `<span class="rnd-lu-r5" data-pid="${p.player_id}">···</span>`;
                h += `</div>`;
            });
            return h;
        };

        // ── Position → grid cell [row, col] (1-based) ──
        // Grid: 12 columns (home GK=1 ... away GK=12), 5 rows (L=1, CL=2, C=3, CR=4, R=5)
        // Home cols: GK=1, D=2, DM=3, M=4, OM=5, FC=6
        // Away cols: FC=7, OM=8, M=9, DM=10, D=11, GK=12
        // Rows: L=1, CL=2, C=3, CR=4, R=5  (for home DL is bottom=row5, DR is top=row1)
        const homePosMap = {
            gk: [3, 1],
            dl: [5, 2], dcl: [4, 2], dc: [3, 2], dcr: [2, 2], dr: [1, 2],
            dml: [5, 3], dmcl: [4, 3], dmc: [3, 3], dmcr: [2, 3], dmr: [1, 3],
            ml: [5, 4], mcl: [4, 4], mc: [3, 4], mcr: [2, 4], mr: [1, 4],
            oml: [5, 5], omcl: [4, 5], omc: [3, 5], omcr: [2, 5], omr: [1, 5],
            fcl: [4, 6], fc: [3, 6], fcr: [2, 6]
        };
        const awayPosMap = {
            gk: [3, 12],
            dl: [1, 11], dcl: [2, 11], dc: [3, 11], dcr: [4, 11], dr: [5, 11],
            dml: [1, 10], dmcl: [2, 10], dmc: [3, 10], dmcr: [4, 10], dmr: [5, 10],
            ml: [1, 9], mcl: [2, 9], mc: [3, 9], mcr: [4, 9], mr: [5, 9],
            oml: [1, 8], omcl: [2, 8], omc: [3, 8], omcr: [4, 8], omr: [5, 8],
            fcl: [2, 7], fc: [3, 7], fcr: [4, 7]
        };

        // SVG pitch markings (horizontal: 150 wide x 100 tall, ratio 3:2 matches container)
        const lw = 0.4, clr = 'rgba(255,255,255,0.22)', clr2 = 'rgba(255,255,255,0.3)';
        const pitchSVG = `<svg class="rnd-pitch-lines" viewBox="0 0 150 100" preserveAspectRatio="xMidYMid meet">
            <!-- outer boundary -->
            <rect x="0" y="0" width="150" height="100" fill="none" stroke="${clr}" stroke-width="0.5"/>
            <!-- halfway line -->
            <line x1="75" y1="0" x2="75" y2="100" stroke="${clr}" stroke-width="${lw}"/>
            <!-- center circle & spot -->
            <circle cx="75" cy="50" r="13" fill="none" stroke="${clr}" stroke-width="${lw}"/>
            <circle cx="75" cy="50" r="1.2" fill="${clr2}"/>
            <!-- LEFT penalty area (24 deep, 60 wide centered) -->
            <rect x="0" y="20" width="24" height="60" fill="none" stroke="${clr}" stroke-width="${lw}"/>
            <!-- LEFT goal area (8 deep, 28 wide centered) -->
            <rect x="0" y="36" width="8" height="28" fill="none" stroke="${clr}" stroke-width="${lw}"/>
            <!-- LEFT penalty spot -->
            <circle cx="16" cy="50" r="1.2" fill="${clr2}"/>
            <!-- LEFT penalty arc (D) -->
            <path d="M 24 39.75 A 13 13 0 0 1 24 60.25" fill="none" stroke="${clr}" stroke-width="${lw}"/>
            <!-- RIGHT penalty area -->
            <rect x="126" y="20" width="24" height="60" fill="none" stroke="${clr}" stroke-width="${lw}"/>
            <!-- RIGHT goal area -->
            <rect x="142" y="36" width="8" height="28" fill="none" stroke="${clr}" stroke-width="${lw}"/>
            <!-- RIGHT penalty spot -->
            <circle cx="134" cy="50" r="1.2" fill="${clr2}"/>
            <!-- RIGHT penalty arc (D) -->
            <path d="M 126 39.75 A 13 13 0 0 0 126 60.25" fill="none" stroke="${clr}" stroke-width="${lw}"/>
            <!-- corner arcs -->
            <path d="M 0 1.5 A 1.5 1.5 0 0 1 1.5 0" fill="none" stroke="${clr}" stroke-width="${lw}"/>
            <path d="M 0 98.5 A 1.5 1.5 0 0 0 1.5 100" fill="none" stroke="${clr}" stroke-width="${lw}"/>
            <path d="M 148.5 0 A 1.5 1.5 0 0 1 150 1.5" fill="none" stroke="${clr}" stroke-width="${lw}"/>
            <path d="M 150 98.5 A 1.5 1.5 0 0 0 148.5 100" fill="none" stroke="${clr}" stroke-width="${lw}"/>
        </svg>`;

        // Build face image URL from udseende2 data
        const faceUrl = (p, clubColor) => {
            const u = p.udseende2 || {};
            const clrHex = clubColor.replace('#', '');
            return `https://trophymanager.com/pics/player_pic2.php?face=${u.face || 1}&nose=${u.nose || 1}&eyes=${u.eyes || 1}&ears=${u.ears || 1}&mouth=${u.mouth || 1}&brows=${u.brows || 1}&hcolor=${u.hair_color || 1}&scolor=${u.skin_color || 1}&hair=${u.hair || 1}&age=${p.age || 25}&rgb=${clrHex}&w=96`;
        };
        const faceNode = (p, clubColor) => {
            const url = faceUrl(p, clubColor);
            return `<div class="rnd-pitch-face" style="border:2.5px solid ${clubColor}">`
                + `<img src="${url}" alt="${p.no}"></div>`;
        };

        // Build grid cells: 5 rows × 12 cols = 60 cells
        // Use active roster (subs in, reds out) for pitch placement
        const roster = computeActiveRoster(mData, curMin, curEvtIdx);
        const allLineup = { ...mData.lineup.home, ...mData.lineup.away };

        const cellMap = {};  // "row-col" → html
        const cellPidMap = {};  // "row-col" → player id
        const placeNode = (pid, posMap, color, overridePos) => {
            const p = allLineup[pid];
            if (!p) return;
            const posKey = overridePos || p.position;
            const pos = posMap[posKey];
            if (!pos) return;
            const [row, col] = pos;
            const key = `${row}-${col}`;
            const evts = eventIcons(p.player_id);
            const rFmt = (matchEnded && p.rating) ? Number(p.rating).toFixed(1) : '';
            const isCaptain = String(p.player_id) === homeCaptainId || String(p.player_id) === awayCaptainId;
            const isMom = matchEnded && Number(p.mom) === 1;
            cellPidMap[key] = pid;
            let h = faceNode(p, color);
            // Captain armband indicator on face
            if (isCaptain) h += `<div class="rnd-pitch-captain">C</div>`;
            // MOM star on face
            if (isMom) h += `<div class="rnd-pitch-mom">⭐</div>`;
            h += `<div class="rnd-pitch-info">`;
            h += `<div class="rnd-pitch-label">${p.nameLast || fmtName(p)}</div>`;
            if (rFmt) h += `<div class="rnd-pitch-rating" style="color:${ratingColor(p.rating)}">${rFmt}</div>`;
            if (evts) h += `<div class="rnd-pitch-events">${evts}</div>`;
            h += `</div>`;
            cellMap[key] = h;
        };
        roster.homeActive.forEach(pid => {
            const overridePos = roster.subbedPositions.get(pid);
            placeNode(pid, homePosMap, homeColor, overridePos);
        });
        roster.awayActive.forEach(pid => {
            const overridePos = roster.subbedPositions.get(pid);
            placeNode(pid, awayPosMap, awayColor, overridePos);
        });

        // Build grid HTML
        let gridHTML = '';
        for (let r = 1; r <= 5; r++) {
            for (let c = 1; c <= 12; c++) {
                const key = `${r}-${c}`;
                const pidAttr = cellPidMap[key] ? ` data-pid="${cellPidMap[key]}"` : '';
                gridHTML += `<div class="rnd-pitch-cell"${pidAttr}>${cellMap[key] || ''}</div>`;
            }
        }

        let html = '';

        // ── Build per-side tactics HTML ──
        const md = mData.match_data;
        const mentalityMap = { 1: 'Very Defensive', 2: 'Defensive', 3: 'Slightly Defensive', 4: 'Normal', 5: 'Slightly Attacking', 6: 'Attacking', 7: 'Very Attacking' };
        const styleMap = { 1: 'Balanced', 2: 'Direct', 3: 'Wings', 4: 'Short Passing', 5: 'Long Balls', 6: 'Through Balls' };
        const focusMap = { 1: 'Balanced', 2: 'Left', 3: 'Central', 4: 'Right' };
        const focusIcons = { Balanced: '⚖️', Left: '⬅️', Central: '⬆️', Right: '➡️' };

        // Compute live mentality (apply mentality_change events up to current minute)
        const homeClubId = String(mData.club.home.id);
        const awayClubId = String(mData.club.away.id);
        const liveMentality = {
            home: Number(md.mentality ? md.mentality.home : 4),
            away: Number(md.mentality ? md.mentality.away : 4)
        };
        {
            const rpt = mData.report || {};
            Object.keys(rpt).forEach(minKey => {
                const eMin = Number(minKey);
                (rpt[minKey] || []).forEach((evt, si) => {
                    if (!isEventVisible(eMin, si, curMin, curEvtIdx)) return;
                    if (!evt.parameters) return;
                    evt.parameters.forEach(param => {
                        if (param.mentality_change) {
                            const mc = param.mentality_change;
                            const teamId = String(mc.team);
                            if (teamId === homeClubId) liveMentality.home = Number(mc.mentality);
                            else if (teamId === awayClubId) liveMentality.away = Number(mc.mentality);
                        }
                    });
                });
            });
        }

        const buildTactics = (side) => {
            const future = isMatchFuture(mData);
            let t = '<div class="rnd-tactics-section"><div class="rnd-tactics-grid">';
            // Avg R5 (filled async)
            t += `<div class="rnd-tactic-row r5-row" data-avg-r5="${side}">
                <span class="rnd-tactic-icon">⭐</span>
                <span class="rnd-tactic-label">Avg R5</span>
                <div class="rnd-tactic-meter"><div class="rnd-r5-side-meter-fill ${side}" style="width:0%"></div></div>
                <span class="rnd-r5-side-val" style="font-size:11px;font-weight:800;color:#e0f0cc;min-width:36px;text-align:right">···</span>
            </div>`;
            // Mentality (live) — skip for future matches
            if (!future) {
                const lvl = liveMentality[side];
                const val = mentalityMap[lvl] || lvl;
                t += `<div class="rnd-tactic-row">
                    <span class="rnd-tactic-icon">⚔️</span>
                    <span class="rnd-tactic-label">Mentality</span>
                    <div class="rnd-tactic-meter"><div class="rnd-tactic-meter-fill ${side}" style="width:${Math.round(lvl / 7 * 100)}%"></div></div>
                    <span class="rnd-tactic-value">${val}</span>
                </div>`;
            }
            // Attacking Style — skip for future matches
            if (!future && md.attacking_style) {
                const sVal = styleMap[md.attacking_style[side]] || md.attacking_style[side];
                t += `<div class="rnd-tactic-row">
                    <span class="rnd-tactic-icon">🎯</span>
                    <span class="rnd-tactic-label">Style</span>
                    <span class="rnd-tactic-value-pill ${side}">${sVal}</span>
                </div>`;
            }
            // Focus Side — skip for future matches
            if (!future && md.focus_side) {
                const fVal = focusMap[md.focus_side[side]] || md.focus_side[side];
                const fIcon = focusIcons[fVal] || '⬆️';
                t += `<div class="rnd-tactic-row">
                    <span class="rnd-tactic-icon">◎</span>
                    <span class="rnd-tactic-label">Focus</span>
                    <span class="rnd-tactic-value-pill ${side}">${fIcon} ${fVal}</span>
                </div>`;
            }
            t += '</div></div>';
            return t;
        };

        const isLive = liveState && !liveState.ended;

        // During live: if rnd-lu-wrap already exists, update ONLY the wrap content
        // This avoids destroying/recreating the Unity viewport (no blink)
        const existingWrap = body.find('.rnd-lu-wrap');
        if (isLive && existingWrap.length) {
            // Build only the wrap content (lists + pitch)
            let wrapHtml = '';
            wrapHtml += `<div class="rnd-lu-list">${renderList(home, homeColor, mData.club.home.club_name, homeId, 'home')}${buildTactics('home')}</div>`;
            wrapHtml += `<div class="rnd-pitch-wrap">
              <div class="rnd-pitch">${pitchSVG}<div class="rnd-pitch-grid">${gridHTML}</div></div>
            </div>`;
            wrapHtml += `<div class="rnd-lu-list">${renderList(away, awayColor, mData.club.away.club_name, awayId, 'away')}${buildTactics('away')}</div>`;
            existingWrap.html(wrapHtml);
        } else {
            // First render or match ended: full rebuild — save canvas first
            saveUnityCanvas();
            if (isLive) {
                html += '<div class="rnd-lu-outer">';
            }
            // Unity viewport row: feed | viewport | stats (hide when match ended)
            if (isMatchPage && isLive) {
                html += '<div class="rnd-unity-row">';
                html += '<div class="rnd-unity-feed" id="rnd-unity-feed"></div>';
                html += '<div id="rnd-unity-viewport" class="rnd-unity-viewport" style="display:none;flex:1 1 auto;"></div>';
                html += '<div class="rnd-unity-stats" id="rnd-unity-stats"></div>';
                html += '</div>';
            }
            html += '<div class="rnd-lu-wrap">';
            html += `<div class="rnd-lu-list">${renderList(home, homeColor, mData.club.home.club_name, homeId, 'home')}${buildTactics('home')}</div>`;
            html += `<div class="rnd-pitch-wrap">
              <div class="rnd-pitch">${pitchSVG}<div class="rnd-pitch-grid">${gridHTML}</div></div>
            </div>`;
            html += `<div class="rnd-lu-list">${renderList(away, awayColor, mData.club.away.club_name, awayId, 'away')}${buildTactics('away')}</div>`;
            html += '</div>';
            if (isLive) html += '</div>';

            body.html(html);

            // Player card dialog click handler
            body.on('click', '.rnd-lu-clickable', function () {
                const clickedPid = $(this).data('pid');
                if (!clickedPid) return;
                // Read live state at click time, not at render time
                const cMin = liveState ? liveState.min : 999;
                const cIdx = liveState ? liveState.curEvtIdx : 999;
                const cParamIdx = (liveState && !liveState.ended && !liveState.curEvtComplete) ? cIdx - 1 : cIdx;
                showPlayerDialog(clickedPid, mData, cMin, cParamIdx);
            });

            // Initialize stats panel with zeros
            updateUnityStats();

            // ── Pitch hover tooltip ──
            const GK_SKILL_NAMES = ['Str', 'Pac', 'Jum', 'Sta', 'One', 'Ref', 'Aer', 'Com', 'Kic', 'Thr', 'Han'];
            const FIELD_SKILL_NAMES = ['Str', 'Sta', 'Pac', 'Mar', 'Tac', 'Wor', 'Pos', 'Pas', 'Cro', 'Tec', 'Hea', 'Fin', 'Lon', 'Set'];
            let pitchTooltipEl = null;
            let pitchTooltipTimer = null;

            const removePitchTooltip = () => {
                clearTimeout(pitchTooltipTimer);
                if (pitchTooltipEl) { pitchTooltipEl.remove(); pitchTooltipEl = null; }
            };

            const skillValColor = (v) => {
                const n = parseInt(v);
                if (n >= 20) return '#d4af37';
                if (n >= 19) return '#c0c0c0';
                if (n >= 16) return '#66dd44';
                if (n >= 12) return '#cccc00';
                if (n >= 8) return '#ee9900';
                return '#ee6633';
            };

            body.on('mouseenter', '.rnd-pitch-cell[data-pid], .rnd-lu-player[data-pid]', function (e) {
                const cell = $(this);
                const pid = String(cell.data('pid'));
                removePitchTooltip();

                // Create tooltip with loading state
                pitchTooltipEl = $('<div class="rnd-pitch-tooltip"><div class="rnd-pitch-tooltip-loading">Loading...</div></div>').appendTo('body');
                const tt = pitchTooltipEl;

                // Position tooltip
                const rect = this.getBoundingClientRect();
                const ttLeft = rect.right + 8;
                const ttTop = rect.top;
                tt.css({ left: ttLeft + 'px', top: ttTop + 'px' });

                // Show after brief delay
                pitchTooltipTimer = setTimeout(() => {
                    tt.addClass('visible');
                }, 80);

                // Fetch player data
                fetchTooltip(pid).then(rawData => {
                    if (!pitchTooltipEl || pitchTooltipEl !== tt) return; // tooltip was removed
                    const player = JSON.parse(JSON.stringify(rawData.player));
                    const lineupP = allLineup[pid];
                    if (routineMap.has(pid)) player.routine = String(routineMap.get(pid));
                    if (positionMap.has(pid)) player.favposition = positionMap.get(pid);

                    const asi = parseNum(player.skill_index);
                    const xp = parseNum(player.routine);
                    const positions = player.favposition.split(',');
                    const posIdx = getPositionIndex(positions[0]);
                    const skills = extractSkills(player, posIdx);
                    const isGK = posIdx === 9;
                    const skillNames = isGK ? GK_SKILL_NAMES : FIELD_SKILL_NAMES;

                    let r5 = Number(calculateR5(posIdx, skills, asi, xp));
                    let rec = Number(calculateRemainders(posIdx, skills, asi).rec);
                    if (positions.length > 1) {
                        const posIdx2 = getPositionIndex(positions[1]);
                        if (posIdx2 !== posIdx) {
                            const r5_2 = Number(calculateR5(posIdx2, skills, asi, xp));
                            const rec_2 = Number(calculateRemainders(posIdx2, skills, asi).rec);
                            if (r5_2 > r5) r5 = r5_2;
                            if (rec_2 > rec) rec = rec_2;
                        }
                    }

                    let h = '<div class="rnd-pitch-tooltip-header">';
                    h += `<div><div class="rnd-pitch-tooltip-name">${player.name || lineupP?.name || ''}</div>`;
                    const ageYears = Number(player.age || lineupP?.age || 0);
                    const ageMonths = Number(player.months || 0);
                    const ageDisplay = ageMonths ? `${ageYears}.${ageMonths}` : String(ageYears || '?');
                    let infoLine = `${(lineupP?.position || '').toUpperCase()} · #${lineupP?.no || ''} · Age ${ageDisplay}`;
                    if (player.country) {
                        const flagUrl = `https://trophymanager.com/pics/flags/gradient/${player.country}.png`;
                        infoLine += ` · <img src="${flagUrl}" style="height:11px;vertical-align:-1px;margin:0 2px" onerror="this.style.display='none'">`;
                    }
                    h += `<div class="rnd-pitch-tooltip-pos">${infoLine}</div></div>`;
                    h += '<div class="rnd-pitch-tooltip-badges">';
                    h += `<span class="rnd-pitch-tooltip-badge" style="color:${r5Color(r5)}">R5 ${r5.toFixed(2)}</span>`;
                    h += '</div></div>';

                    // Skills two-column layout
                    const fieldLeft = [0, 1, 2, 3, 4, 5, 6];    // Str,Sta,Pac,Mar,Tac,Wor,Pos
                    const fieldRight = [7, 8, 9, 10, 11, 12, 13]; // Pas,Cro,Tec,Hea,Fin,Lon,Set
                    const gkLeft = [0, 3, 1];                // Str,Sta,Pac
                    const gkRight = [10, 4, 5, 6, 2, 7, 8, 9];    // Han,One,Ref,Aer,Jum,Com,Kic,Thr
                    const leftIdx = isGK ? gkLeft : fieldLeft;
                    const rightIdx = isGK ? gkRight : fieldRight;

                    const renderCol = (indices) => {
                        let c = '<div class="rnd-pitch-tooltip-skills-col">';
                        indices.forEach(i => {
                            const val = typeof skills[i] === 'number' ? skills[i] : parseInt(skills[i]);
                            const display = val >= 20 ? '★' : val >= 19 ? '★' : String(val);
                            c += `<div class="rnd-pitch-tooltip-skill">`;
                            c += `<span class="rnd-pitch-tooltip-skill-name">${skillNames[i] || ''}</span>`;
                            c += `<span class="rnd-pitch-tooltip-skill-val" style="color:${skillValColor(val)}">${display}</span>`;
                            c += '</div>';
                        });
                        c += '</div>';
                        return c;
                    };
                    h += '<div class="rnd-pitch-tooltip-skills">';
                    h += renderCol(leftIdx);
                    h += renderCol(rightIdx);
                    h += '</div>';

                    // Footer: ASI, REC, Routine, Rating
                    h += '<div class="rnd-pitch-tooltip-footer">';
                    h += `<div class="rnd-pitch-tooltip-stat"><div class="rnd-pitch-tooltip-stat-val" style="color:#e0f0cc">${parseNum(player.skill_index).toLocaleString()}</div><div class="rnd-pitch-tooltip-stat-lbl">ASI</div></div>`;
                    h += `<div class="rnd-pitch-tooltip-stat"><div class="rnd-pitch-tooltip-stat-val" style="color:${getColor(rec, REC_THRESHOLDS)}">${rec}</div><div class="rnd-pitch-tooltip-stat-lbl">REC</div></div>`;
                    h += `<div class="rnd-pitch-tooltip-stat"><div class="rnd-pitch-tooltip-stat-val" style="color:#8abc78">${parseFloat(player.routine).toFixed(1)}</div><div class="rnd-pitch-tooltip-stat-lbl">Routine</div></div>`;
                    h += '</div>';

                    tt.html(h);

                    // Reposition if off-screen
                    const ttRect = tt[0].getBoundingClientRect();
                    if (ttRect.right > window.innerWidth - 10) {
                        tt.css('left', (rect.left - ttRect.width - 8) + 'px');
                    }
                    if (ttRect.bottom > window.innerHeight - 10) {
                        tt.css('top', Math.max(10, window.innerHeight - ttRect.height - 10) + 'px');
                    }
                }).catch(() => {
                    if (pitchTooltipEl === tt) {
                        tt.html('<div class="rnd-pitch-tooltip-loading">No data</div>');
                    }
                });
            });

            body.on('mouseleave', '.rnd-pitch-cell[data-pid], .rnd-lu-player[data-pid]', removePitchTooltip);

            // ── Unity: move canvas into viewport on match page ──
            if (isMatchPage && unityState.available) {
                setTimeout(() => {
                    const vp = document.getElementById('rnd-unity-viewport');
                    if (vp) {
                        moveUnityCanvas();
                        vp.style.display = 'block';
                    }
                }, 100);
            }
        }



        // Async: fetch R5 for all players
        const routineMap = new Map();
        const positionMap = new Map();
        const allPlayers = [...Object.values(mData.lineup.home), ...Object.values(mData.lineup.away)];
        allPlayers.forEach(p => {
            routineMap.set(p.player_id, parseFloat(p.routine));
            if (p.fp) positionMap.set(p.player_id, p.fp);
        });
        // Active roster IDs for avg R5 (based on current live minute — subs/reds applied)
        const homeActiveIds = roster.homeActive;
        const awayActiveIds = roster.awayActive;
        Promise.all(allPlayers.map(p =>
            getPlayerData(p.player_id, routineMap, positionMap)
                .then(d => ({ id: p.player_id, r5: d.R5 }))
                .catch(() => ({ id: p.player_id, r5: null }))
        )).then(results => {
            const homeR5s = [], awayR5s = [];
            results.forEach(({ id, r5 }) => {
                const el = body.find(`.rnd-lu-r5[data-pid="${id}"]`);
                if (el.length && r5 !== null) {
                    el.text(r5.toFixed(2)).css('background', r5Color(r5));
                }
                // Only currently active players count for avg R5
                if (r5 !== null) {
                    if (homeActiveIds.has(String(id))) homeR5s.push(r5);
                    else if (awayActiveIds.has(String(id))) awayR5s.push(r5);
                }
            });
            // Fill avg R5 bars (always /11 even if red card reduced count)
            const fillAvg = (side, vals) => {
                if (!vals.length) return;
                const avg = vals.reduce((a, b) => a + b, 0) / 11;
                const pct = Math.min(100, Math.max(0, Math.round((avg - 40) / (120 - 40) * 100)));
                const card = body.find(`[data-avg-r5="${side}"]`);
                card.find('.rnd-r5-side-meter-fill').css('width', pct + '%');
                card.find('.rnd-r5-side-val').text(avg.toFixed(2)).css('color', r5Color(avg));
            };
            fillAvg('home', homeR5s);
            fillAvg('away', awayR5s);
            // Update header R5 chips
            const headerR5 = (side, vals) => {
                if (!vals.length) return;
                const avg = (vals.reduce((a, b) => a + b, 0) / 11).toFixed(2);
                $(`#rnd-chip-r5-${side} .chip-val`).text(avg);
            };
            headerR5('home', homeR5s);
            headerR5('away', awayR5s);
        });
    };

    // ── Player Card Dialog (Lineups click) ──
    const showPlayerDialog = (playerId, mData, curMin = 999, curEvtIdx = 999) => {
        // Remove any existing dialog
        $('.rnd-plr-overlay').remove();

        const pid = String(playerId);
        const homeId = mData.club.home.id;
        const isHome = !!mData.lineup.home[pid];
        const lineup = isHome ? mData.lineup.home : mData.lineup.away;
        const p = lineup[pid];
        if (!p) return;

        const clubColor = '#' + ((isHome ? mData.club.home : mData.club.away).colors?.club_color1 || '4a9030');
        const clubName = (isHome ? mData.club.home : mData.club.away).club_name || '';

        // Face URL
        const u = p.udseende2 || {};
        const clrHex = clubColor.replace('#', '');
        const fUrl = `https://trophymanager.com/pics/player_pic2.php?face=${u.face || 1}&nose=${u.nose || 1}&eyes=${u.eyes || 1}&ears=${u.ears || 1}&mouth=${u.mouth || 1}&brows=${u.brows || 1}&hcolor=${u.hair_color || 1}&scolor=${u.skin_color || 1}&hair=${u.hair || 1}&age=${p.age || 25}&rgb=${clrHex}&w=96`;

        // Rating color helper
        const ratClr = (r) => {
            if (!r || r === 0) return '#5a7a48';
            const v = Number(r);
            if (v >= 9.0) return '#00c040';
            if (v >= 8.5) return '#00dd50';
            if (v >= 8.0) return '#22e855';
            if (v >= 7.5) return '#44ee55';
            if (v >= 7.0) return '#66dd44';
            if (v >= 6.5) return '#88cc33';
            if (v >= 6.0) return '#99bb22';
            if (v >= 5.5) return '#aacc00';
            if (v >= 5.0) return '#bbcc00';
            if (v >= 4.5) return '#dd9900';
            if (v >= 4.0) return '#ee7733';
            if (v >= 3.5) return '#ee5533';
            if (v >= 3.0) return '#dd3333';
            if (v >= 2.0) return '#cc2222';
            return '#bb1111';
        };

        // Player names map for accordion rendering
        const playerNames = buildPlayerNames(mData);

        // ── Compute player stats from video segments ──
        const PASS_VIDS = /^(short|preshort|through|longball|gkthrow|gkkick)/;
        const CROSS_VIDS = /^(wing(?!start)|cornerkick|freekick)/;
        const DEFWIN_VIDS = /^defwin/;
        const FINISH_VIDS = /^(finish|finishlong|header|acrobat)/;
        const RUN_DUEL_VIDS = /^finrun/;
        const report = mData.report || {};
        const sortedMins = Object.keys(report).map(Number).sort((a, b) => a - b);
        const st = {
            sp: 0, up: 0, sc: 0, uc: 0,                     // passes & crosses
            sh: 0, sot: 0, shf: 0, sotf: 0, gf: 0,         // shooting (foot)
            shh: 0, soth: 0, gh: 0,                          // shooting (head)
            sv: 0, g: 0, a: 0, kp: 0,                        // goals, assists, key passes
            dw: 0, dl: 0,                                     // duels
            int: 0, tkl: 0, hc: 0, tf: 0,                    // defending
            fouls: 0, yc: 0, rc: 0,                           // discipline
        };
        const isGK = p.position === 'gk';
        const playerEvents = []; // all events where this player was involved

        for (const min of sortedMins) {
            const evts = report[String(min)] || [];
            for (let si = 0; si < evts.length; si++) {
                if (!isEventVisible(min, si, curMin, curEvtIdx)) continue;
                const evt = evts[si];
                const vids = evt.chance?.video;
                const evtHasShot = evt.parameters?.some(pr => pr.shot);
                const evtShotOnTarget = evt.parameters?.find(pr => pr.shot)?.shot?.target === 'on';
                let involved = false;
                let action = '';

                if (vids && Array.isArray(vids)) {
                    for (let vi = 0; vi < vids.length; vi++) {
                        const v = vids[vi];
                        if (PASS_VIDS.test(v.video)) {
                            const passerId = /^gk(throw|kick)/.test(v.video) ? String(v.gk) : String(v.att1);
                            if (passerId === pid) {
                                const isPreshort = /^preshort/.test(v.video);
                                const textLines = (evt.chance?.text?.[vi] || []);
                                if (isPreshort && !textLines.some(l => l.includes('[player=' + passerId + ']'))) {
                                    // Skip
                                } else {
                                    const failed = vi + 1 < vids.length && DEFWIN_VIDS.test(vids[vi + 1].video);
                                    if (failed) { st.up++; involved = true; action = 'pass_fail'; }
                                    else {
                                        st.sp++;
                                        if (evtHasShot) st.kp++;
                                        involved = true; action = 'pass_ok';
                                    }
                                }
                            }
                        }
                        if (CROSS_VIDS.test(v.video) && String(v.att1) === pid) {
                            if (/^freekick/.test(v.video) && evtHasShot) {
                                st.sh++; st.shf++;
                                if (evtShotOnTarget) { st.sot++; st.sotf++; }
                                involved = true; action = 'shot';
                            } else {
                                const failed = vi + 1 < vids.length && DEFWIN_VIDS.test(vids[vi + 1].video);
                                if (failed) { st.uc++; involved = true; action = 'cross_fail'; }
                                else {
                                    st.sc++;
                                    if (evtHasShot) st.kp++;
                                    involved = true; action = 'cross_ok';
                                }
                            }
                        }
                        if (FINISH_VIDS.test(v.video) && String(v.att1) === pid) {
                            const nextIsAlsoFinish = vi + 1 < vids.length && FINISH_VIDS.test(vids[vi + 1].video);
                            if (!nextIsAlsoFinish) {
                                const isHead = /^header/.test(v.video);
                                st.sh++;
                                if (isHead) {
                                    st.shh++;
                                    if (evtShotOnTarget) { st.sot++; st.soth++; }
                                } else {
                                    st.shf++;
                                    if (evtShotOnTarget) { st.sot++; st.sotf++; }
                                }
                                const hasGoalForShooter = evt.parameters?.some(p => p.goal && String(p.goal.player) === pid);
                                if (!hasGoalForShooter) { involved = true; action = 'shot'; }
                            }
                        }
                        if (DEFWIN_VIDS.test(v.video)) {
                            const tLines = (evt.chance?.text || []).flat();
                            const winner = [v.def1, v.def2].find(d => d && tLines.some(l => l.includes('[player=' + d + ']')));
                            if (winner && String(winner) === pid) {
                                const prevVideo = vi > 0 ? vids[vi - 1].video : '';
                                const isFinrunBefore = RUN_DUEL_VIDS.test(prevVideo);
                                const isCornerkickBefore = /^cornerkick/.test(prevVideo);
                                if (isFinrunBefore) { st.dw++; }
                                else if (!isCornerkickBefore) { st.dw++; }
                                // Defense classification
                                const defwinTextLines = evt.chance?.text?.[vi] || [];
                                const isHeader = defwinTextLines.some(l => /\bheader\b|\bhead(ed|s)?\b/i.test(l));
                                if (/^defwin5$/.test(v.video) || isHeader) { st.hc++; involved = true; action = 'header_clear'; }
                                else if (/^defwin(3|6)$/.test(v.video)) { st.tkl++; involved = true; action = 'tackle'; }
                                else { st.int++; involved = true; action = 'intercept'; }
                            }
                        }
                        if (RUN_DUEL_VIDS.test(v.video)) {
                            const nextIsDefwin = vi + 1 < vids.length && DEFWIN_VIDS.test(vids[vi + 1].video);
                            if (!nextIsDefwin) {
                                const prevVideo = vi > 0 ? vids[vi - 1].video : '';
                                if (!/^cornerkick/.test(prevVideo)) {
                                    const tLines = (evt.chance?.text || []).flat();
                                    [v.def1, v.def2].forEach(d => {
                                        if (d && String(d) === pid && tLines.some(l => l.includes('[player=' + d + ']'))) {
                                            st.dl++; involved = true; action = 'duel_lost';
                                        }
                                    });
                                }
                                // Tackle failed
                                const nextVid = vi + 1 < vids.length ? vids[vi + 1].video : '';
                                if (FINISH_VIDS.test(nextVid) && v.def1 && String(v.def1) === pid) {
                                    st.tf++; involved = true; action = 'tackle_fail';
                                }
                            }
                        }
                        if (/^save/.test(v.video) && String(v.gk) === pid) {
                            st.sv++; involved = true; action = 'save';
                        }
                        if (/^foulcall/.test(v.video) && v.def1 && String(v.def1) === pid) {
                            st.fouls++; involved = true; action = 'foul';
                        }
                    }
                }
                // Goals, assists, cards from parameters
                if (evt.parameters) {
                    evt.parameters.forEach(param => {
                        if (param.goal && String(param.goal.player) === pid) {
                            st.g++; involved = true; action = 'goal';
                            // Foot vs head goal
                            const isPenGoal = evt.parameters.some(p => p.penalty);
                            if (!isPenGoal) {
                                const evtVids = evt.chance?.video;
                                const isHeaderGoal = evtVids && Array.isArray(evtVids) && evtVids.some(v => /^header/.test(v.video));
                                if (isHeaderGoal) st.gh++;
                                else st.gf++;
                            }
                        }
                        if (param.goal && String(param.goal.assist) === pid) {
                            st.a++; involved = true; action = 'assist';
                        }
                        if (param.yellow && String(param.yellow) === pid) { st.yc++; involved = true; action = 'yellow'; }
                        if (param.yellow_red && String(param.yellow_red) === pid) { st.yc++; st.rc++; involved = true; action = 'red'; }
                        if (param.red && String(param.red) === pid) { st.rc++; involved = true; action = 'red'; }
                    });
                }
                if (involved) {
                    playerEvents.push({ min, evtIdx: si, evt, action });
                }
            }
        }

        // ── Minutes played ──
        const isSub = p.position.includes('sub');
        let minsPlayed;
        const subEvts = {};
        for (const min of sortedMins) {
            (report[String(min)] || []).forEach(evt => {
                if (!evt.parameters) return;
                evt.parameters.forEach(param => {
                    if (param.sub) {
                        if (String(param.sub.player_in) === pid) subEvts.subInMin = min;
                        if (String(param.sub.player_out) === pid) subEvts.subOutMin = min;
                    }
                });
            });
        }
        const matchEndMin = mData.match_data?.regular_last_min || Math.max(...sortedMins, 90);
        if (isSub) {
            minsPlayed = subEvts.subInMin ? (subEvts.subOutMin || matchEndMin) - subEvts.subInMin : 0;
        } else {
            minsPlayed = subEvts.subOutMin || matchEndMin;
        }

        // ── Position display ──
        const posDisplay = isSub ? (p.fp || '').split(',')[0].toUpperCase() : p.position.toUpperCase();

        // ── Build HTML ──
        const ACTION_LABELS = { pass_ok: 'pass ✓', pass_fail: 'pass ✗', cross_ok: 'cross ✓', cross_fail: 'cross ✗', shot: 'shot', save: 'save', goal: 'goal', assist: 'assist', duel_won: 'duel ✓', duel_lost: 'duel ✗', intercept: 'INT', tackle: 'TKL', header_clear: 'HC', tackle_fail: 'TF', foul: 'foul', yellow: '🟨', red: '🟥' };
        const ACTION_CLS = { pass_ok: 'shot', pass_fail: 'lost', cross_ok: 'shot', cross_fail: 'lost', shot: 'shot', save: 'shot', goal: 'goal', assist: 'goal', duel_won: 'shot', duel_lost: 'lost', intercept: 'shot', tackle: 'shot', header_clear: 'shot', tackle_fail: 'lost', foul: 'lost', yellow: 'lost', red: 'lost' };

        const playerUrl = `https://trophymanager.com/players/${pid}/#/page/history/`;
        const matchEnded = !liveState || liveState.ended;

        let html = '<div class="rnd-plr-overlay"><div class="rnd-plr-dialog" style="position:relative">';
        html += '<button class="rnd-plr-close">&times;</button>';

        // Header: face + info + R5 (loaded async)
        html += '<div class="rnd-plr-header">';
        html += `<div class="rnd-plr-face"><img src="${fUrl}" alt="${p.no}"></div>`;
        html += '<div class="rnd-plr-info">';
        html += '<div class="rnd-plr-name-row">';
        html += `<a class="rnd-plr-name" href="${playerUrl}" target="_blank">${p.name || p.nameLast || ''}</a>`;
        html += `<a class="rnd-plr-link" href="${playerUrl}" target="_blank" title="Open player profile">&#x1F517;</a>`;
        html += '</div>';
        html += '<div class="rnd-plr-badges">';
        html += `<span class="rnd-plr-badge"><span class="badge-icon">👕</span> #${p.no}</span>`;
        html += `<span class="rnd-plr-badge"><span class="badge-icon">📍</span> ${posDisplay}</span>`;
        html += `<span class="rnd-plr-badge" id="rnd-plr-age-badge-${pid}"><span class="badge-icon">🎂</span> ${p.age || '?'}</span>`;
        if (matchEnded) html += `<span class="rnd-plr-badge"><span class="badge-icon">⏱️</span> ${minsPlayed}'</span>`;
        html += '</div></div>';
        if (matchEnded && p.rating) {
            const rVal = Number(p.rating).toFixed(2);
            html += '<div class="rnd-plr-rating-wrap">';
            html += `<div class="rnd-plr-rating-big" style="color:${ratClr(p.rating)}">${rVal}</div>`;
            html += '<div class="rnd-plr-rating-label">Rating</div>';
            html += '</div>';
        }
        html += '</div>';

        // Body: profile + stats + chances
        html += '<div class="rnd-plr-body">';

        // ── Player Profile (skills, ASI, R5, REC, Routine) — loaded async ──
        html += '<div class="rnd-plr-section-title"><span class="sec-icon">🧑</span> Player Profile</div>';
        html += `<div class="rnd-plr-profile-wrap" id="rnd-plr-profile-${pid}"><div class="rnd-plr-profile-loading">⏳ Loading player data...</div></div>`;

        // ── Shooting ──
        html += '<div class="rnd-plr-section-title"><span class="sec-icon">🎯</span> Shooting</div>';
        html += '<div class="rnd-plr-stats-row">';
        const convPct = st.sh > 0 ? Math.round(st.g / st.sh * 100) : 0;
        const onTargetPct = st.sh > 0 ? Math.round(st.sot / st.sh * 100) : 0;
        if (isGK) {
            [{ icon: '🧤', lbl: 'Saves', val: st.sv, cls: st.sv > 0 ? 'green' : '' },
             { icon: '⚽', lbl: 'Goals', val: st.g, cls: st.g > 0 ? 'gold' : '' },
             { icon: '👟', lbl: 'Assists', val: st.a, cls: st.a > 0 ? 'gold' : '' },
             { icon: '🔑', lbl: 'Key Pass', val: st.kp, cls: st.kp > 0 ? '' : '' },
             { icon: '🎯', lbl: 'Shots', val: st.sh, cls: '' },
            ].forEach(s => {
                html += `<div class="rnd-plr-stat-card ${s.cls}"><div class="rnd-plr-stat-icon">${s.icon}</div><div class="rnd-plr-stat-val">${s.val}</div><div class="rnd-plr-stat-lbl">${s.lbl}</div></div>`;
            });
        } else {
            [{ icon: '⚽', lbl: 'Goals', val: st.g, cls: st.g > 0 ? 'gold' : '' },
             { icon: '🎯', lbl: 'Shots', val: st.sh, cls: '' },
             { icon: '✅', lbl: 'On Target', val: st.sot, cls: st.sot > 0 ? 'green' : '' },
             { icon: '🦶', lbl: 'Foot G', val: st.gf, cls: st.gf > 0 ? 'gold' : '' },
             { icon: '🗣️', lbl: 'Head G', val: st.gh, cls: st.gh > 0 ? 'gold' : '' },
            ].forEach(s => {
                html += `<div class="rnd-plr-stat-card ${s.cls}"><div class="rnd-plr-stat-icon">${s.icon}</div><div class="rnd-plr-stat-val">${s.val}</div><div class="rnd-plr-stat-lbl">${s.lbl}</div></div>`;
            });
        }
        html += '</div>';

        // ── Passing & Creativity ──
        html += '<div class="rnd-plr-section-title"><span class="sec-icon">📊</span> Passing & Creativity</div>';
        html += '<div class="rnd-plr-stats-row">';
        const totalPasses = st.sp + st.up;
        const passAcc = totalPasses > 0 ? Math.round(st.sp / totalPasses * 100) : 0;
        const totalCross = st.sc + st.uc;
        const crossAcc = totalCross > 0 ? Math.round(st.sc / totalCross * 100) : 0;
        [{ icon: '👟', lbl: 'Assists', val: st.a, cls: st.a > 0 ? 'gold' : '' },
         { icon: '🔑', lbl: 'Key Pass', val: st.kp, cls: st.kp > 0 ? '' : '' },
         { icon: '📨', lbl: `Pass ${passAcc}%`, val: `${st.sp}/${totalPasses}`, cls: passAcc >= 70 ? 'green' : totalPasses > 0 ? 'red' : '' },
         { icon: '↗️', lbl: `Cross ${crossAcc}%`, val: `${st.sc}/${totalCross}`, cls: crossAcc >= 50 ? 'green' : totalCross > 0 ? 'red' : '' },
         { icon: '📈', lbl: 'Total', val: totalPasses + totalCross, cls: '' },
        ].forEach(s => {
            html += `<div class="rnd-plr-stat-card ${s.cls}"><div class="rnd-plr-stat-icon">${s.icon}</div><div class="rnd-plr-stat-val">${s.val}</div><div class="rnd-plr-stat-lbl">${s.lbl}</div></div>`;
        });
        html += '</div>';

        // ── Defending & Duels ──
        html += '<div class="rnd-plr-section-title"><span class="sec-icon">🛡️</span> Defending & Duels</div>';
        html += '<div class="rnd-plr-stats-row">';
        const totalDuels = st.dw + st.dl;
        const duelPct = totalDuels > 0 ? Math.round(st.dw / totalDuels * 100) : 0;
        [{ icon: '👁️', lbl: 'INT', val: st.int, cls: st.int > 0 ? 'green' : '' },
         { icon: '🦵', lbl: 'TKL', val: st.tkl, cls: st.tkl > 0 ? 'green' : '' },
         { icon: '🗣️', lbl: 'HC', val: st.hc, cls: st.hc > 0 ? 'green' : '' },
         { icon: '❌', lbl: 'TF', val: st.tf, cls: st.tf > 0 ? 'red' : '' },
         { icon: '⚠️', lbl: 'Fouls', val: st.fouls, cls: st.fouls > 0 ? 'red' : '' },
        ].forEach(s => {
            html += `<div class="rnd-plr-stat-card ${s.cls}"><div class="rnd-plr-stat-icon">${s.icon}</div><div class="rnd-plr-stat-val">${s.val}</div><div class="rnd-plr-stat-lbl">${s.lbl}</div></div>`;
        });
        html += '</div>';

        // Chances list
        if (playerEvents.length) {
            html += '<div class="rnd-plr-section-title"><span class="sec-icon">⚡</span> Chances Involved (' + playerEvents.length + ')</div>';
            html += '<div class="rnd-adv-evt-list">';
            playerEvents.forEach(ev => {
                const acls = ACTION_CLS[ev.action] || '';
                const albl = ACTION_LABELS[ev.action] || '';
                html += '<div class="rnd-adv-evt">';
                if (albl) html += `<span class="adv-result-tag ${acls}">${albl}</span>`;
                html += buildReportEventHtml(ev.evt, ev.min, ev.evtIdx, playerNames, homeId);
                html += '</div>';
            });
            html += '</div>';
        } else {
            html += '<div style="text-align:center;padding:12px;color:#4a6a38;font-size:12px">No recorded chances</div>';
        }

        html += '</div></div></div>';

        // Append to body
        const $overlay = $(html).appendTo('body');

        // Close handlers
        $overlay.find('.rnd-plr-close').on('click', () => $overlay.remove());
        $overlay.on('click', (e) => { if ($(e.target).hasClass('rnd-plr-overlay')) $overlay.remove(); });

        // Wire accordion for embedded events
        $overlay.on('click', '.rnd-acc-head', function (e) {
            e.stopPropagation();
            $(this).closest('.rnd-acc').toggleClass('open');
        });

        // ── Async: load player profile (skills, ASI, R5, REC, Routine) ──
        const profileEl = $overlay.find(`#rnd-plr-profile-${pid}`);
        const routineMap = new Map();
        const positionMap = new Map();
        // Populate from lineup data
        const allLineupForCard = { ...mData.lineup.home, ...mData.lineup.away };
        Object.entries(allLineupForCard).forEach(([id, lp]) => {
            routineMap.set(id, Number(lp.routine));
            if (!lp.position.includes('sub')) positionMap.set(id, lp.position);
        });

        fetchTooltip(pid).then(rawData => {
            if (!profileEl.length || !profileEl.closest('body').length) return;
            const player = JSON.parse(JSON.stringify(rawData.player));
            if (routineMap.has(pid)) player.routine = String(routineMap.get(pid));
            if (positionMap.has(pid)) player.favposition = positionMap.get(pid);

            const asi = parseNum(player.skill_index);
            const xp = parseNum(player.routine);
            const positions = player.favposition.split(',');
            const posIdx = getPositionIndex(positions[0]);
            const skills = extractSkills(player, posIdx);
            const isGKProfile = posIdx === 9;

            const GK_NAMES = ['Str', 'Pac', 'Jum', 'Sta', 'One', 'Ref', 'Aer', 'Com', 'Kic', 'Thr', 'Han'];
            const FIELD_NAMES = ['Str', 'Sta', 'Pac', 'Mar', 'Tac', 'Wor', 'Pos', 'Pas', 'Cro', 'Tec', 'Hea', 'Fin', 'Lon', 'Set'];
            const skillNames = isGKProfile ? GK_NAMES : FIELD_NAMES;

            let r5 = Number(calculateR5(posIdx, skills, asi, xp));
            let rec = Number(calculateRemainders(posIdx, skills, asi).rec);
            if (positions.length > 1) {
                const posIdx2 = getPositionIndex(positions[1]);
                if (posIdx2 !== posIdx) {
                    const r5_2 = Number(calculateR5(posIdx2, skills, asi, xp));
                    const rec_2 = Number(calculateRemainders(posIdx2, skills, asi).rec);
                    if (r5_2 > r5) r5 = r5_2;
                    if (rec_2 > rec) rec = rec_2;
                }
            }

            const svc = (v) => {
                const n = parseInt(v);
                if (n >= 20) return '#d4af37';
                if (n >= 19) return '#c0c0c0';
                if (n >= 16) return '#66dd44';
                if (n >= 12) return '#cccc00';
                if (n >= 8) return '#ee9900';
                return '#ee6633';
            };

            // Build skills grid
            const leftIdx = isGKProfile ? [0, 3, 1] : [0, 1, 2, 3, 4, 5, 6];
            const rightIdx = isGKProfile ? [10, 4, 5, 6, 2, 7, 8, 9] : [7, 8, 9, 10, 11, 12, 13];
            const maxLen = Math.max(leftIdx.length, rightIdx.length);

            let ph = '';
            // Country row
            if (player.country) {
                const flagUrl = `https://trophymanager.com/pics/flags/gradient/${player.country}.png`;
                const countryName = player.country_name || player.country || '';
                ph += `<div class="rnd-plr-country-row">`;
                ph += `<img class="rnd-plr-country-flag" src="${flagUrl}" onerror="this.style.display='none'">`;
                ph += `<span class="rnd-plr-country-name">${countryName}</span>`;
                ph += `</div>`;
            }

            ph += '<div class="rnd-plr-skills-grid">';
            // Left column
            ph += '<div>';
            leftIdx.forEach(i => {
                const val = typeof skills[i] === 'number' ? skills[i] : parseInt(skills[i]);
                let display, starCls = '';
                if (val >= 20) { display = '★'; starCls = ' rnd-plr-skill-star'; }
                else if (val >= 19) { display = '★'; starCls = ' rnd-plr-skill-star silver'; }
                else display = String(val);
                ph += `<div class="rnd-plr-skill-row">`;
                ph += `<span class="rnd-plr-skill-name">${skillNames[i] || ''}</span>`;
                ph += `<span class="rnd-plr-skill-val${starCls}" style="color:${svc(val)}">${display}</span>`;
                ph += '</div>';
            });
            ph += '</div>';
            // Right column
            ph += '<div>';
            rightIdx.forEach(i => {
                const val = typeof skills[i] === 'number' ? skills[i] : parseInt(skills[i]);
                let display, starCls = '';
                if (val >= 20) { display = '★'; starCls = ' rnd-plr-skill-star'; }
                else if (val >= 19) { display = '★'; starCls = ' rnd-plr-skill-star silver'; }
                else display = String(val);
                ph += `<div class="rnd-plr-skill-row">`;
                ph += `<span class="rnd-plr-skill-name">${skillNames[i] || ''}</span>`;
                ph += `<span class="rnd-plr-skill-val${starCls}" style="color:${svc(val)}">${display}</span>`;
                ph += '</div>';
            });
            ph += '</div>';
            ph += '</div>';

            // Footer: ASI, R5, REC, Routine
            ph += '<div class="rnd-plr-profile-footer">';
            ph += `<div class="rnd-plr-profile-stat"><div class="rnd-plr-profile-stat-val" style="color:#e0f0cc">${asi.toLocaleString()}</div><div class="rnd-plr-profile-stat-lbl">ASI</div></div>`;
            ph += `<div class="rnd-plr-profile-stat"><div class="rnd-plr-profile-stat-val" style="color:${getColor(r5, R5_THRESHOLDS)}">${r5.toFixed(2)}</div><div class="rnd-plr-profile-stat-lbl">R5</div></div>`;
            ph += `<div class="rnd-plr-profile-stat"><div class="rnd-plr-profile-stat-val" style="color:${getColor(rec, REC_THRESHOLDS)}">${rec}</div><div class="rnd-plr-profile-stat-lbl">REC</div></div>`;
            ph += `<div class="rnd-plr-profile-stat"><div class="rnd-plr-profile-stat-val" style="color:#8abc78">${parseFloat(player.routine).toFixed(1)}</div><div class="rnd-plr-profile-stat-lbl">Routine</div></div>`;
            ph += '</div>';

            profileEl.html(ph);

            // Update age badge with months
            const ageBadge = $(`#rnd-plr-age-badge-${pid}`);
            if (ageBadge.length) {
                const ageMonths = Number(player.months || 0);
                const ageYears = Number(player.age || 0);
                const ageDisplay = ageMonths ? `${ageYears}.${ageMonths}` : String(ageYears || '?');
                ageBadge.html(`<span class="badge-icon">🎂</span> ${ageDisplay}`);
            }
        }).catch(() => {
            profileEl.html('<div class="rnd-plr-profile-loading" style="color:#aa5533">Failed to load profile</div>');
        });
    };

    const renderH2HTab = (body, mData) => {
        body.html('<div style="text-align:center;padding:20px;color:#5a7a48">⏳ Loading H2H...</div>');

        const homeId = String(mData.club.home.id);
        const awayId = String(mData.club.away.id);
        const homeName = mData.club.home.club_name;
        const awayName = mData.club.away.club_name;
        const homeLogo = mData.club.home.logo || `/pics/club_logos/${homeId}_140.png`;
        const awayLogo = mData.club.away.logo || `/pics/club_logos/${awayId}_140.png`;
        const kickoff = mData.match_data.venue.kickoff || Math.floor(Date.now() / 1000);

        $.get(`/ajax/match_h2h.ajax.php?home_team=${homeId}&away_team=${awayId}&date=${kickoff}`).done(res => {
            const data = typeof res === 'string' ? JSON.parse(res) : res;

            // Compute totals for record strip
            const allStats = data.all || {};
            const hWins = allStats[homeId]?.w || 0;
            const aWins = allStats[awayId]?.w || 0;
            const draws = allStats[homeId]?.d || 0;
            const hGoalsTotal = allStats[homeId]?.gf || 0;
            const aGoalsTotal = allStats[awayId]?.gf || 0;

            let html = '<div class="rnd-h2h-wrap">';

            // Record strip: logo name [W] [D] [W] name logo
            html += `<div class="rnd-h2h-record">
                <div class="rnd-h2h-record-side">
                    <img class="rnd-h2h-record-logo" src="${homeLogo}" onerror="this.style.display='none'">
                    <span class="rnd-h2h-record-name">${homeName}</span>
                </div>
                <div class="rnd-h2h-record-stat">
                    <span class="rnd-h2h-record-num home">${hWins}</span>
                    <span class="rnd-h2h-record-label">Wins</span>
                </div>
                <div class="rnd-h2h-record-stat">
                    <span class="rnd-h2h-record-num draw">${draws}</span>
                    <span class="rnd-h2h-record-label">Draws</span>
                </div>
                <div class="rnd-h2h-record-stat">
                    <span class="rnd-h2h-record-num away">${aWins}</span>
                    <span class="rnd-h2h-record-label">Wins</span>
                </div>
                <div class="rnd-h2h-record-side away">
                    <img class="rnd-h2h-record-logo" src="${awayLogo}" onerror="this.style.display='none'">
                    <span class="rnd-h2h-record-name">${awayName}</span>
                </div>
            </div>`;

            // Goals summary line
            if (hGoalsTotal || aGoalsTotal) {
                html += `<div class="rnd-h2h-goals-summary">Goals: <span>${hGoalsTotal}</span> – <span>${aGoalsTotal}</span></div>`;
            }

            // Match history grouped by season (newest first)
            html += '<div class="rnd-h2h-matches">';
            if (data.matches) {
                const seasons = Object.keys(data.matches).sort((a, b) => Number(b) - Number(a));
                const currentSeason = SESSION?.season;
                const clubNames = {};
                clubNames[homeId] = homeName;
                clubNames[awayId] = awayName;

                seasons.forEach(season => {
                    html += `<div class="rnd-h2h-season">Season ${season}</div>`;
                    data.matches[season].forEach(m => {
                        const [hGoals, aGoals] = (m.result || '0-0').split('-').map(Number);
                        const mHomeId = String(m.hometeam);
                        const hName = clubNames[mHomeId] || m.hometeam;
                        const aName = clubNames[String(m.awayteam)] || m.awayteam;
                        const hWin = hGoals > aGoals;
                        const aWin = aGoals > hGoals;
                        const isDraw = hGoals === aGoals;
                        // Determine result class from perspective of the "home" club in H2H
                        let resultClass = 'h2h-draw';
                        if (hWin && mHomeId === homeId || aWin && mHomeId !== homeId) resultClass = 'h2h-win';
                        else if (!isDraw) resultClass = 'h2h-loss';
                        let div = m.division ? `Division ${m.division}` : (m.matchtype || '');
                        if (m.matchtype === "fl") {
                            div = "Friendly league";
                        }
                        if (m.matchtype === "f") {
                            div = "Quick match";
                        } else if (["p1", "p2", "p3", "p4", "p5", "p6", "p7", "p8", "p9"].includes(m.matchtype)) {
                            div = "Cup";
                        } else if (["ueg", "ue1", "ue2"].includes(m.matchtype)) {
                            div = "Conference League";
                        } else if (["clg", "cl1", "cl2"].includes(m.matchtype)) {
                            div = "Champions League";
                        }
                        const isOldSeason = Number(season) !== currentSeason;
                        html += `<div class="rnd-h2h-match ${resultClass}${isOldSeason ? ' h2h-readonly' : ''}" data-mid="${m.id}" data-season="${season}">`;
                        html += `<div class="rnd-h2h-date">${m.date || ''}</div>`;
                        if (div) html += `<span class="rnd-h2h-type-badge">${div}</span>`;
                        html += `<div class="rnd-h2h-home${hWin ? ' winner' : ''}">${hName}</div>`;
                        html += `<div class="rnd-h2h-result">${m.result}</div>`;
                        html += `<div class="rnd-h2h-away${aWin ? ' winner' : ''}">${aName}</div>`;
                        if (m.attendance_format) html += `<div class="rnd-h2h-att">🏟 ${m.attendance_format}</div>`;
                        html += `</div>`;
                    });
                });
            }
            html += '</div></div>';

            body.html(html);

            // ── Tooltip cache & hover logic ──
            const tooltipCache = {};
            let tooltipEl = null;
            let tooltipTimer = null;
            let tooltipHideTimer = null;
            const currentSeasonNum = SESSION?.season || 0;

            // ── Tooltip from tooltip.ajax.php (older seasons, same layout as rich) ──
            const buildTooltipContent = (d) => {
                const hName = d.hometeam_name || '';
                const aName = d.awayteam_name || '';
                // Try to get team IDs for logos from the H2H context
                const hLogoId = d.hometeam || '';
                const aLogoId = d.awayteam || '';
                const hLogoUrl = hLogoId ? `/pics/club_logos/${hLogoId}_140.png` : '';
                const aLogoUrl = aLogoId ? `/pics/club_logos/${aLogoId}_140.png` : '';

                let t = '';
                // Header with logos (identical to rich tooltip)
                t += `<div class="rnd-h2h-tooltip-header">`;
                if (hLogoUrl) t += `<img class="rnd-h2h-tooltip-logo" src="${hLogoUrl}" onerror="this.style.display='none'">`;
                t += `<span class="rnd-h2h-tooltip-team">${hName}</span>`;
                t += `<span class="rnd-h2h-tooltip-score">${d.result || ''}</span>`;
                t += `<span class="rnd-h2h-tooltip-team">${aName}</span>`;
                if (aLogoUrl) t += `<img class="rnd-h2h-tooltip-logo" src="${aLogoUrl}" onerror="this.style.display='none'">`;
                t += `</div>`;

                // Meta
                t += `<div class="rnd-h2h-tooltip-meta">`;
                if (d.date) t += `<span>📅 ${d.date}</span>`;
                if (d.attendance_format) t += `<span>🏟 ${d.attendance_format}</span>`;
                t += `</div>`;

                // Events: goals & cards (same structure as rich)
                const report = d.report || {};
                const hTeamId = String(d.hometeam || hLogoId);
                const goals = [];
                const cards = [];
                Object.keys(report).forEach(k => {
                    if (k === 'mom' || k === 'mom_name') return;
                    const e = report[k];
                    if (!e || !e.minute) return;
                    const sc = e.score;
                    const isHome = String(e.team_scores) === hTeamId;
                    if (sc === 'yellow' || sc === 'red' || sc === 'orange') {
                        cards.push({ ...e, isHome });
                    } else {
                        goals.push({ ...e, isHome });
                    }
                });
                goals.sort((a, b) => Number(a.minute) - Number(b.minute));
                cards.sort((a, b) => Number(a.minute) - Number(b.minute));

                if (goals.length || cards.length) {
                    t += `<div class="rnd-h2h-tooltip-events">`;
                    goals.forEach(e => {
                        const sideClass = e.isHome ? '' : ' away-evt';
                        t += `<div class="rnd-h2h-tooltip-evt${sideClass}">`;
                        t += `<span class="rnd-h2h-tooltip-evt-min">${e.minute}'</span>`;
                        t += `<span class="rnd-h2h-tooltip-evt-icon">⚽</span>`;
                        t += `<span class="rnd-h2h-tooltip-evt-text">${e.scorer_name || ''}`;
                        if (e.assist_id && e.assist_id !== '') {
                            t += ` <span class="rnd-h2h-tooltip-evt-assist">(${e.score})</span>`;
                        } else {
                            t += ` <span class="rnd-h2h-tooltip-evt-assist">${e.score}</span>`;
                        }
                        t += `</span></div>`;
                    });
                    if (goals.length && cards.length) t += `<div class="rnd-h2h-tooltip-divider"></div>`;
                    cards.forEach(e => {
                        const icon = e.score === 'yellow' ? '🟡' : e.score === 'orange' ? '🟡🟡→🔴' : '🔴';
                        const sideClass = e.isHome ? '' : ' away-evt';
                        t += `<div class="rnd-h2h-tooltip-evt${sideClass}">`;
                        t += `<span class="rnd-h2h-tooltip-evt-min">${e.minute}'</span>`;
                        t += `<span class="rnd-h2h-tooltip-evt-icon">${icon}</span>`;
                        t += `<span class="rnd-h2h-tooltip-evt-text">${e.scorer_name || ''}</span>`;
                        t += `</div>`;
                    });
                    t += `</div>`;
                }

                // MOM
                if (report.mom_name) {
                    t += `<div class="rnd-h2h-tooltip-mom">⭐ Man of the Match: <span>${report.mom_name}</span></div>`;
                }
                return t;
            };

            // ── Rich tooltip from match.ajax.php (current season) ──
            const buildRichTooltip = (mData) => {
                const md = mData.match_data || {};
                const club = mData.club || {};
                const hName = club.home?.club_name || '';
                const aName = club.away?.club_name || '';
                const hId = String(club.home?.id || '');
                const aId = String(club.away?.id || '');
                const hLogo = club.home?.logo || `/pics/club_logos/${hId}_140.png`;
                const aLogo = club.away?.logo || `/pics/club_logos/${aId}_140.png`;
                const report = mData.report || {};

                // Find final score from report
                let finalScore = '0 - 0';
                const allMins = Object.keys(report).map(Number).sort((a, b) => a - b);
                for (let i = allMins.length - 1; i >= 0; i--) {
                    const evts = report[allMins[i]];
                    if (!Array.isArray(evts)) continue;
                    for (let j = evts.length - 1; j >= 0; j--) {
                        const p = evts[j].parameters;
                        if (p) {
                            const goal = Array.isArray(p) ? p.find(x => x.goal) : p.goal ? p : null;
                            if (goal) {
                                const g = goal.goal || goal;
                                if (g.score) { finalScore = g.score.join(' - '); break; }
                            }
                        }
                    }
                    if (finalScore !== '0 - 0') break;
                }
                // If halftime has score, at least use that
                if (finalScore === '0 - 0' && md.halftime?.chance?.text) {
                    const htText = md.halftime.chance.text.flat().join(' ');
                    const sm = htText.match(/(\d+)-(\d+)/);
                    if (sm) finalScore = sm[1] + ' - ' + sm[2];
                }

                let t = '';
                // Header with logos
                t += `<div class="rnd-h2h-tooltip-header">`;
                t += `<img class="rnd-h2h-tooltip-logo" src="${hLogo}" onerror="this.style.display='none'">`;
                t += `<span class="rnd-h2h-tooltip-team">${hName}</span>`;
                t += `<span class="rnd-h2h-tooltip-score">${finalScore}</span>`;
                t += `<span class="rnd-h2h-tooltip-team">${aName}</span>`;
                t += `<img class="rnd-h2h-tooltip-logo" src="${aLogo}" onerror="this.style.display='none'">`;
                t += `</div>`;

                // Meta
                t += `<div class="rnd-h2h-tooltip-meta">`;
                const ko = md.venue?.kickoff_readable || '';
                if (ko) {
                    const d = new Date(ko.replace(' ', 'T'));
                    t += `<span>📅 ${d.toLocaleDateString('en-GB', { day: 'numeric', month: 'short', year: 'numeric' })}</span>`;
                }
                if (md.venue?.name) t += `<span>🏟 ${md.venue.name}</span>`;
                if (md.attendance) t += `<span>👥 ${Number(md.attendance).toLocaleString()}</span>`;
                t += `</div>`;

                // Key events: goals, cards, subs — extracted from report
                const keyEvents = [];
                allMins.forEach(min => {
                    const evts = report[min];
                    if (!Array.isArray(evts)) return;
                    evts.forEach(evt => {
                        if (!evt.parameters) return;
                        const params = Array.isArray(evt.parameters) ? evt.parameters : [evt.parameters];
                        const clubId = String(evt.club || '');
                        const isHome = clubId === hId;
                        params.forEach(p => {
                            if (p.goal) {
                                const scorer = mData.lineup?.home?.[p.goal.player] || mData.lineup?.away?.[p.goal.player];
                                const assistPlayer = mData.lineup?.home?.[p.goal.assist] || mData.lineup?.away?.[p.goal.assist];
                                keyEvents.push({
                                    min, type: 'goal', isHome,
                                    name: scorer?.nameLast || scorer?.name || '?',
                                    assist: assistPlayer?.nameLast || assistPlayer?.name || '',
                                    score: p.goal.score ? p.goal.score.join('-') : ''
                                });
                            }
                            if (p.yellow) {
                                const pl = mData.lineup?.home?.[p.yellow] || mData.lineup?.away?.[p.yellow];
                                keyEvents.push({ min, type: 'yellow', isHome, name: pl?.nameLast || pl?.name || '?' });
                            }
                            if (p.yellow_red) {
                                const pl = mData.lineup?.home?.[p.yellow_red] || mData.lineup?.away?.[p.yellow_red];
                                keyEvents.push({ min, type: 'red', isHome, name: pl?.nameLast || pl?.name || '?' });
                            }
                            if (p.red) {
                                const pl = mData.lineup?.home?.[p.red] || mData.lineup?.away?.[p.red];
                                keyEvents.push({ min, type: 'red', isHome, name: pl?.nameLast || pl?.name || '?' });
                            }
                            if (p.sub) {
                                const plIn = mData.lineup?.home?.[p.sub.player_in] || mData.lineup?.away?.[p.sub.player_in];
                                const plOut = mData.lineup?.home?.[p.sub.player_out] || mData.lineup?.away?.[p.sub.player_out];
                                keyEvents.push({
                                    min, type: 'sub', isHome,
                                    nameIn: plIn?.nameLast || plIn?.name || '?',
                                    nameOut: plOut?.nameLast || plOut?.name || '?'
                                });
                            }
                        });
                    });
                });

                // Goals & cards section
                const goals = keyEvents.filter(e => e.type === 'goal');
                const cards = keyEvents.filter(e => e.type === 'yellow' || e.type === 'red');

                if (goals.length || cards.length) {
                    t += `<div class="rnd-h2h-tooltip-events">`;
                    goals.forEach(e => {
                        const sideClass = e.isHome ? '' : ' away-evt';
                        t += `<div class="rnd-h2h-tooltip-evt${sideClass}">`;
                        t += `<span class="rnd-h2h-tooltip-evt-min">${e.min}'</span>`;
                        t += `<span class="rnd-h2h-tooltip-evt-icon">⚽</span>`;
                        t += `<span class="rnd-h2h-tooltip-evt-text">${e.name}`;
                        if (e.assist) t += ` <span class="rnd-h2h-tooltip-evt-assist">(${e.assist})</span>`;
                        t += `</span>`;
                        t += `</div>`;
                    });
                    if (goals.length && cards.length) t += `<div class="rnd-h2h-tooltip-divider"></div>`;
                    cards.forEach(e => {
                        const icon = e.type === 'yellow' ? '🟡' : '🔴';
                        const sideClass = e.isHome ? '' : ' away-evt';
                        t += `<div class="rnd-h2h-tooltip-evt${sideClass}">`;
                        t += `<span class="rnd-h2h-tooltip-evt-min">${e.min}'</span>`;
                        t += `<span class="rnd-h2h-tooltip-evt-icon">${icon}</span>`;
                        t += `<span class="rnd-h2h-tooltip-evt-text">${e.name}</span>`;
                        t += `</div>`;
                    });
                    t += `</div>`;
                }

                // Stats: possession, shots
                const poss = md.possession;
                const statsData = md.statistics || {};
                const shotsH = statsData.home_shots || 0;
                const shotsA = statsData.away_shots || 0;
                const onTargetH = statsData.home_on_target || 0;
                const onTargetA = statsData.away_on_target || 0;

                if (poss || shotsH || shotsA) {
                    t += `<div class="rnd-h2h-tooltip-stats">`;
                    if (poss) {
                        const hP = poss.home || 0, aP = poss.away || 0;
                        t += `<span class="rnd-h2h-tooltip-stat-home${hP > aP ? ' leading' : ''}">${hP}%</span>`;
                        t += `<span class="rnd-h2h-tooltip-stat-label">Possession</span>`;
                        t += `<span class="rnd-h2h-tooltip-stat-away${aP > hP ? ' leading' : ''}">${aP}%</span>`;
                    }
                    if (shotsH || shotsA) {
                        t += `<span class="rnd-h2h-tooltip-stat-home${shotsH > shotsA ? ' leading' : ''}">${shotsH}</span>`;
                        t += `<span class="rnd-h2h-tooltip-stat-label">Shots</span>`;
                        t += `<span class="rnd-h2h-tooltip-stat-away${shotsA > shotsH ? ' leading' : ''}">${shotsA}</span>`;
                    }
                    if (onTargetH || onTargetA) {
                        t += `<span class="rnd-h2h-tooltip-stat-home${onTargetH > onTargetA ? ' leading' : ''}">${onTargetH}</span>`;
                        t += `<span class="rnd-h2h-tooltip-stat-label">On Target</span>`;
                        t += `<span class="rnd-h2h-tooltip-stat-away${onTargetA > onTargetH ? ' leading' : ''}">${onTargetA}</span>`;
                    }
                    t += `</div>`;
                }

                // MOM
                const allPlayers = [...Object.values(mData.lineup?.home || {}), ...Object.values(mData.lineup?.away || {})];
                const mom = allPlayers.find(p => p.mom === 1 || p.mom === '1');
                if (mom) {
                    t += `<div class="rnd-h2h-tooltip-mom">⭐ Man of the Match: <span>${mom.nameLast || mom.name}</span> (${parseFloat(mom.rating).toFixed(1)})</div>`;
                }

                return t;
            };

            const showTooltip = (el, mid, season) => {
                clearTimeout(tooltipHideTimer);
                if (tooltipEl) tooltipEl.remove();
                const isCurrentSeason = Number(season) === currentSeasonNum;
                tooltipEl = $('<div class="rnd-h2h-tooltip"></div>');
                $(el).append(tooltipEl);

                if (tooltipCache[mid]) {
                    const cached = tooltipCache[mid];
                    tooltipEl.html(cached._rich ? buildRichTooltip(cached) : buildTooltipContent(cached));
                    requestAnimationFrame(() => tooltipEl.addClass('visible'));
                } else {
                    tooltipEl.html('<div class="rnd-h2h-tooltip-loading">⏳ Loading...</div>');
                    requestAnimationFrame(() => tooltipEl.addClass('visible'));
                    if (isCurrentSeason) {
                        // Current season → full match data endpoint
                        $.get(`/ajax/match.ajax.php?id=${mid}`)
                            .done(r => {
                                const d = typeof r === 'string' ? JSON.parse(r) : r;
                                d._rich = true;
                                tooltipCache[mid] = d;
                                if (tooltipEl && tooltipEl.closest('.rnd-h2h-match').data('mid') == mid) {
                                    tooltipEl.html(buildRichTooltip(d));
                                }
                            })
                            .fail(() => {
                                if (tooltipEl) tooltipEl.html('<div class="rnd-h2h-tooltip-loading" style="color:#ff6b6b">Failed</div>');
                            });
                    } else {
                        console.log('Fetching tooltip data for older season match', mid);
                        // Older season → tooltip endpoint
                        $.post('/ajax/tooltip.ajax.php', { type: 'match', match_id: mid, season: season })
                            .done(r => {
                                const d = typeof r === 'string' ? JSON.parse(r) : r;
                                // Attach team IDs from H2H context for logos
                                d._homeId = homeId;
                                d._awayId = awayId;
                                tooltipCache[mid] = d;
                                if (tooltipEl && tooltipEl.closest('.rnd-h2h-match').data('mid') == mid) {
                                    tooltipEl.html(buildTooltipContent(d));
                                }
                            })
                            .fail(() => {
                                if (tooltipEl) tooltipEl.html('<div class="rnd-h2h-tooltip-loading" style="color:#ff6b6b">Failed</div>');
                            });
                    }
                }
            };

            const hideTooltip = () => {
                tooltipHideTimer = setTimeout(() => {
                    if (tooltipEl) { tooltipEl.remove(); tooltipEl = null; }
                }, 100);
            };

            body.on('mouseenter', '.rnd-h2h-match', function () {
                const el = this;
                const mid = $(el).data('mid');
                const season = $(el).data('season');
                clearTimeout(tooltipTimer);
                tooltipTimer = setTimeout(() => showTooltip(el, mid, season), 300);
            });
            body.on('mouseleave', '.rnd-h2h-match', function () {
                clearTimeout(tooltipTimer);
                hideTooltip();
            });

            // Click on match → open in new tab (current season only)
            body.on('click', '.rnd-h2h-match', function () {
                if ($(this).hasClass('h2h-readonly')) return;
                const mid = $(this).data('mid');
                if (mid) window.open('/matches/' + mid, '_blank');
            });
        }).fail(() => {
            body.html('<div style="text-align:center;padding:20px;color:#ff6b6b">Failed to load H2H data</div>');
        });
    };

    // ─── Loading indicator ───────────────────────────────────────────────
    const cleanupPage = () => {
        if (liveState && liveState.timer) clearTimeout(liveState.timer);
        liveState = null;
        $('#rnd-overlay').remove();
        $('body').css('overflow', '');
        unityState = {
            available: false, ready: false, playing: false,
            pendingMinute: null, loadedMinutes: [], playedMinutes: [],
            canvasParent: null, tmPaused: false,
            clipTextQueue: [], clipTextCursor: 0,
            clipTextGroups: [], clipGroupCursor: 0,
            clipPostQueue: [], activeMinute: null,
            clipFirstShown: false, clipSkippedFirst: false
        };
    };

    const initForCurrentPage = () => {
        cleanupPage();
        injectStyles();
        initUnity();
        const matchId = window.location.pathname.match(/\/matches\/(\d+)/)?.[1];
        if (!matchId) return;
        // Remove TM's default Rounds widget and ad placeholder
        try { $('.banner_placeholder.rectangle')[0].parentNode.removeChild($('.banner_placeholder.rectangle')[0]); } catch (e) { }
        try { $('.column3_a .box').has('h2').filter(function () { return $(this).find('h2').text().trim().toUpperCase() === 'ROUNDS'; }).remove(); } catch (e) { }
        const pollInterval = setInterval(() => {
            if ($('body').length && document.readyState !== 'loading') {
                clearInterval(pollInterval);
                openMatchDialog(matchId);
            }
        }, 500);
    };

    initForCurrentPage();
})();