KeyMod [BETA]

KeyMod for RocketGoal.io — full mod suite by @keydopz

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

// ==UserScript==
// @name         KeyMod [BETA]
// @namespace    https://rocketgoal.io
// @version      0.1.1
// @description  KeyMod for RocketGoal.io — full mod suite by @keydopz
// @match        *://rocketgoal.io/*
// @match        *://www.rocketgoal.io/*
// @match        *://www.crazygames.com/game/rocketgoal-io/*
// @match        *://rocketgoalio.com/*
// @match        *://hotgames.io/rocket-goal*
// @match        *://taproad.io/rocket-goal*
// @match        *://sites.google.com/view/classroom6x/rocket-goal*
// @grant        none
// @run-at       document-start
// @license      CC BY-NC-ND 4.0
// ==/UserScript==

(function () {
  'use strict';

  // ─── KEYBOARD INTERCEPT ───────────────────────────────────────────────────
  // Runs immediately at document-start before Unity registers any listeners.
  // We wrap EventTarget.prototype.addEventListener so every keydown handler
  // Unity adds goes through our wrapper first — giving us F2/backtick/1.
  // Only fire on keydown (not keyup) and only when NOT held (repeat=false)
  var _kmKeyLock = false;

  function kmLog(msg) {
    var entry = { level:'hi', line:'[KeyMod] ' + msg };
    consoleLogs.push(entry);
    if (consoleLogs.length > 500) consoleLogs.shift();
    appendConsoleRow(entry);
    _log('[KeyMod] ' + msg);
  }
  (function() {
    var _ael = EventTarget.prototype.addEventListener;
    EventTarget.prototype.addEventListener = function(type, fn, opts) {
      if ((type === 'keydown' || type === 'keyup') && typeof fn === 'function') {
        var _fn = fn;
        var wrapped = function(e) {
          if (e.key === 'F2') {
            e.preventDefault();
            e.stopImmediatePropagation();
            // keydown fires once, keyup resets lock
            if (type === 'keydown' && !e.repeat && !_kmKeyLock) {
              _kmKeyLock = true;
              if (typeof toggleMenu === 'function') toggleMenu();
            }
            if (type === 'keyup') _kmKeyLock = false;
            return;
          }
          return _fn.apply(this, arguments);
        };
        return _ael.call(this, type, wrapped, opts);
      }
      return _ael.call(this, type, fn, opts);
    };
  })();

  // ─── RANKS ────────────────────────────────────────────────────────────────
  var RANKS = [
    { name:'Bronze I',        min:0,    color:'#cd7f32', emoji:'🟤' },
    { name:'Bronze II',       min:175,  color:'#cd7f32', emoji:'🟤' },
    { name:'Bronze III',      min:225,  color:'#cd7f32', emoji:'🟤' },
    { name:'Silver I',        min:275,  color:'#aaaaaa', emoji:'⚪' },
    { name:'Silver II',       min:350,  color:'#aaaaaa', emoji:'⚪' },
    { name:'Silver III',      min:425,  color:'#aaaaaa', emoji:'⚪' },
    { name:'Gold I',          min:475,  color:'#f5c518', emoji:'🟡' },
    { name:'Gold II',         min:550,  color:'#f5c518', emoji:'🟡' },
    { name:'Gold III',        min:625,  color:'#f5c518', emoji:'🟡' },
    { name:'Platinum I',      min:700,  color:'#22d3ee', emoji:'🔵' },
    { name:'Platinum II',     min:775,  color:'#22d3ee', emoji:'🔵' },
    { name:'Platinum III',    min:850,  color:'#22d3ee', emoji:'🔵' },
    { name:'Diamond I',       min:925,  color:'#818cf8', emoji:'💎' },
    { name:'Diamond II',      min:1000, color:'#818cf8', emoji:'💎' },
    { name:'Diamond III',     min:1075, color:'#818cf8', emoji:'💎' },
    { name:'Champion I',      min:1150, color:'#c084fc', emoji:'🟣' },
    { name:'Champion II',     min:1225, color:'#c084fc', emoji:'🟣' },
    { name:'Champion III',    min:1300, color:'#c084fc', emoji:'🟣' },
    { name:'Grand Champ I',   min:1375, color:'#f97316', emoji:'🔶' },
    { name:'Grand Champ II',  min:1450, color:'#f97316', emoji:'🔶' },
    { name:'Grand Champ III', min:1525, color:'#f97316', emoji:'🔶' },
    { name:'SSL',             min:1600, color:'#ff0055', emoji:'🔴' },
  ];
  function getRank(mmr) {
    for (var i = RANKS.length-1; i >= 0; i--) { if (mmr >= RANKS[i].min) return RANKS[i]; }
    return RANKS[0];
  }
  function getRankProgress(mmr) {
    var cur = getRank(mmr), idx = RANKS.indexOf(cur);
    if (idx >= RANKS.length-1) return 100;
    var next = RANKS[idx+1];
    return Math.round(((mmr - cur.min) / (next.min - cur.min)) * 100);
  }

  // ─── CONSTANTS ────────────────────────────────────────────────────────────
  var SKINS = {
    'body.0': 'Vortex',
    'body.1': 'Overdrive',
    'body.2': 'Crimson',
    'body.3': 'Specter',
    'body.4': 'Frostbite',
    'body.5': 'Pulsewave',
    'body.6': 'Blaze',
    'body.7': 'Nitron',
  };
  var GOAL_SOUNDS = {
    'None': null
  };
  // Tab list is encoded in HTML data-tab attributes

  // ─── STORAGE ──────────────────────────────────────────────────────────────
  function lsGet(k,d) {
    try { var v=localStorage.getItem('km5_'+k); return v!==null?JSON.parse(v):d; } catch(e){return d;}
  }
  function lsSet(k,v) { try{localStorage.setItem('km5_'+k,JSON.stringify(v));}catch(e){} }

  // ─── THEME ────────────────────────────────────────────────────────────────
  var THEMES = {
    arch:    { name:'Arch Linux',      bg:'rgba(10,10,10,0.97)',  tabBg:'rgba(15,15,15,0.99)', blur:'none',              border:'rgba(23,147,209,0.3)',   accent:'#1793d1', text:'#d0d0d0' },
    dark:    { name:'Default',            bg:'rgba(8,10,16,0.96)',   tabBg:'rgba(12,14,20,0.98)', blur:'none',              border:'rgba(255,255,255,0.08)', accent:'#3b82f6', text:'#e2e8f0' },
    glass:   { name:'Liquid Glass',    bg:'rgba(15,20,40,0.45)',  tabBg:'rgba(20,25,45,0.55)', blur:'blur(32px) saturate(200%)', border:'rgba(255,255,255,0.18)', accent:'#60a5fa', text:'#f0f4ff' },
    aero:    { name:'Futuristic Aero', bg:'rgba(0,20,40,0.72)',   tabBg:'rgba(0,15,35,0.82)', blur:'blur(24px) saturate(180%)', border:'rgba(100,200,255,0.25)', accent:'#00d4ff', text:'#e0f4ff' },
    black:   { name:'All Black',       bg:'rgba(0,0,0,0.97)',     tabBg:'rgba(5,5,5,0.99)',    blur:'none',              border:'rgba(255,255,255,0.06)', accent:'#3b82f6', text:'#c8d0dc' },
    windows: { name:'Windows 11',      bg:'rgba(32,32,32,0.92)',  tabBg:'rgba(44,44,44,0.95)', blur:'blur(20px) saturate(150%)', border:'rgba(255,255,255,0.12)', accent:'#0078d4', text:'#ffffff' },
    minimal: { name:'Minimal',         bg:'rgba(6,6,8,0.98)',     tabBg:'rgba(9,9,12,0.99)',   blur:'none',              border:'rgba(255,255,255,0.05)', accent:'#ffffff',  text:'#888888',  minimal:true },
    gta:     { name:'GTA V',           bg:'rgba(0,0,0,0.97)',     tabBg:'rgba(5,8,5,0.99)',    blur:'none',              border:'rgba(255,200,0,0.25)',   accent:'#f5c518',  text:'#c8c8a0',  gta:true },
    rl:      { name:'Rocket League',   bg:'rgba(4,8,20,0.97)',    tabBg:'rgba(6,12,28,0.99)',  blur:'none',              border:'rgba(0,150,255,0.25)',   accent:'#0096ff',  text:'#d0e8ff',  rl:true },
    esports: { name:'Pro Esports',      bg:'rgba(2,2,6,0.98)',     tabBg:'rgba(4,4,10,0.99)',   blur:'none',              border:'rgba(255,60,0,0.3)',     accent:'#ff3c00',  text:'#ffffff',  esports:true },
    faze:    { name:'FaZe Clan',         bg:'rgba(6,0,0,0.98)',     tabBg:'rgba(10,2,2,0.99)',   blur:'none',              border:'rgba(210,0,0,0.4)',      accent:'#d40000',  text:'#ffffff' },
    nrg:     { name:'NRG',               bg:'rgba(0,4,12,0.98)',    tabBg:'rgba(0,6,18,0.99)',   blur:'none',              border:'rgba(0,168,255,0.4)',    accent:'#00a8ff',  text:'#e8f4ff' },
    vitality:{ name:'Team Vitality',     bg:'rgba(4,4,0,0.98)',     tabBg:'rgba(8,8,0,0.99)',    blur:'none',              border:'rgba(255,220,0,0.4)',    accent:'#ffdc00',  text:'#fffff0' },
    kcorp:   { name:'Karmine Corp',      bg:'rgba(0,2,12,0.98)',    tabBg:'rgba(0,3,18,0.99)',   blur:'none',              border:'rgba(0,80,255,0.4)',     accent:'#0050ff',  text:'#e0eaff' },
    bakkesmod:{ name:'BakkesMod',           bg:'rgba(18,18,18,0.99)',  tabBg:'rgba(22,22,22,1.0)',  blur:'none',              border:'rgba(0,110,255,0.25)',   accent:'#006eff',  text:'#d4d4d4',  bakkesmod:true },
    macos14: { name:'macOS Sonoma',         bg:'rgba(28,28,30,0.88)',  tabBg:'rgba(36,36,38,0.92)', blur:'blur(40px) saturate(180%)', border:'rgba(255,255,255,0.14)', accent:'#0a84ff',  text:'#f2f2f7',  macos14:true },
  };

  var theme = lsGet('theme5', {
    preset:   'bakkesmod',
    accent:   '#006eff',
    text:     '#d4d4d4',
    opacity:  0.99,
    loading:  'black',
  });

  function getPreset() { return THEMES[theme.preset] || THEMES.dark; }

  // ─── STATE ────────────────────────────────────────────────────────────────
  var playerData  = null;
  var consoleLogs = [];
  var menuOpen    = false;
  var activeTab   = 'ranks';
  var dismissed   = false;
  var lastPing    = null;
  var authToken   = null;
  var inMatch     = false;
  var matchEndProcessed = false;

  var matchPlayers = { me:null, meId:null, opponent:null, opponentId:null, myTeam:null, oppTeam:null };
  var matchScore   = { me:0, opp:0 };
  var trophyRange  = { lo:null, hi:null };
  var opponentMMR  = null;
  var currentMode  = null;
  var lastOpponentId = null;

  // Session — pure memory
  var session = {
    goals:0, conceded:0, saves:0, shots:0,
    matches:0, wins:0, losses:0,
    streak:0, maxStreak:0, comebacks:0,
    perfectGames:0, xpGained:0,
    startTime:Date.now(), wasLosing:false,
    longestSession: lsGet('longestSession',0),
    lastMode: null,
  };

  // Daily
  var today = new Date().toDateString();
  var dailyStored = lsGet('daily5',{date:'',wins:0,goal:5});
  if (dailyStored.date !== today) dailyStored = {date:today,wins:0,goal:lsGet('dailyGoal5',5)};
  var daily = dailyStored;

  // Persistent data
  var peakMMR      = lsGet('peakMMR5',{});
  var matchHistory = lsGet('matchHistory5',[]);  // max 50
  var rankHistory  = lsGet('rankHistory5',[]);   // rank up/down events
  var opponentLog  = lsGet('opponentLog5',[]);   // full opponent history
  var rivals       = lsGet('rivals5',{});        // name -> { wins, losses, count }
  var xpHistory    = lsGet('xpHistory5',[]);     // { time, gained, total }
  var heatmap      = lsGet('heatmap5',{});
  var clipMarks    = [];
  var selectedGoalSound = 'None';

  // Toggles
  var toggles = lsGet('toggles5',{
    leaderboard:true, goalMsg:true, endMsg:true,
    oppMMR:true, matchNotifs:true, hotStreak:true,
    coldStreak:true, rivalNotif:true, perfectNotif:true,
    rankNotif:true,
  });
  // Ensure defaults for older saved settings
  toggles.leaderboard = toggles.leaderboard === false ? false : true;
  toggles.goalMsg = toggles.goalMsg === false ? false : true;
  toggles.endMsg = toggles.endMsg === false ? false : true;
  toggles.oppMMR = toggles.oppMMR === false ? false : true;
  toggles.matchNotifs = toggles.matchNotifs === false ? false : true;
  toggles.hotStreak = toggles.hotStreak === false ? false : true;
  toggles.coldStreak = toggles.coldStreak === false ? false : true;
  toggles.rivalNotif = toggles.rivalNotif === false ? false : true;
  toggles.perfectNotif = toggles.perfectNotif === false ? false : true;
  toggles.rankNotif = toggles.rankNotif === false ? false : true;

  // Custom messages
  var customMsgs = lsGet('customMsgs5',{goal:'GOAL!',win:'GG EZ',loss:'GG'});

  // Party code
  var currentPartyCode = null;

  // Tips
  var TIPS = [
    'Tip: Press F2 to toggle the KeyMod menu anytime',
    'Tip: Install plugins from the Plugin Store tab',
    'Tip: Your MMR is tracked after every match automatically',
    'Tip: Click the Discord button in the header to copy your party invite',
    'Tip: Set a daily win goal in the Competitive tab',
    'Tip: Rival players appear after 3+ matches against them',
    'Tip: The Momentum Meter plugin tracks your session feel',
    'Tip: Use AutoGG plugin to auto-copy GG on match end',
    'Tip: Session Lock plugin stops tilt queuing automatically',
    'Tip: Rank Anxiety plugin warns you near a rank drop',
    'Tip: Your match history stores the last 50 matches',
    'Tip: Speed Run plugin tracks your fastest first goal',
    'Tip: Drag the menu header to move it anywhere on screen',
    'Tip: Resize the menu by dragging the bottom-right corner',
    'Tip: The heatmap shows your best hours to play',
    'Tip: Export your profile card for Discord from the Profile tab',
    'Tip: Night Mode Auto plugin switches theme after a set hour',
  ];
  var currentTip = TIPS[Math.floor(Math.random() * TIPS.length)];
  var menuTipIdx = lsGet('menuTipIdx5', 0);

  // Plugin system
  var installedPlugins = (function() {
    var v = lsGet('plugins5', []);
    // Normalize — old format was object {}, new format is array []
    if (!Array.isArray(v)) {
      v = Object.keys(v);
      lsSet('plugins5', v);
    }
    return v;
  })();
  var pluginSettings   = lsGet('pluginSettings5', {});

  // ── Safety: force dangerous optimizer tweaks off on every load ────────────
  // These settings were previously defaulted 'on' and can cause black screens.
  // We always reset them to 'off' regardless of what's saved in localStorage.
  (function() {
    var dangerous = ['frustum','stopload','bodyhwacc','gpucanvas','vislock',
                     'nooverflow','noblur','canvasfocus','overflowbody',
                     'scrolltop','csscontain','nocontextmenu','silencelog'];
    if (!pluginSettings.optimizer) pluginSettings.optimizer = {};
    dangerous.forEach(function(id) {
      pluginSettings.optimizer[id] = 'off';
    });
    // Also reset VFX to 'off' - a saved custom filter can black the canvas
    if (!pluginSettings.visualFX) pluginSettings.visualFX = {};
    // Only reset if preset was left on something potentially harmful
    var vfxPreset = pluginSettings.visualFX.preset;
    // Don't reset 'off' or unset - only reset 'custom' if it has aggressive values
    if (vfxPreset === 'custom') {
      var vib = parseInt(pluginSettings.visualFX.vibrance) || 100;
      var con = parseInt(pluginSettings.visualFX.contrast) || 100;
      if (vib > 180 || con > 160) {
        pluginSettings.visualFX.preset = 'off';
      }
    }
    // Force-reset troll mode enabled to 'off' on every page load
    if (!pluginSettings.trollPlugin) pluginSettings.trollPlugin = {};
    pluginSettings.trollPlugin.enabled = 'off';
    lsSet('pluginSettings5', pluginSettings);
  })();
  var customHotkey     = lsGet('customHotkey5', 'F2');

  // Plugin definitions
  var PLUGINS = [
    { id:'tiltGuard',      name:'Tilt Guard',         icon:'🛡️',  version:'1.0', category:'Utility',
      desc:'Detects tilt patterns. If you lose 3 in a row, shows a cooldown overlay to force a break.',
      settings:[{id:'lossLimit',label:'Loss Limit',type:'number',default:3,min:1,max:10}] },
    { id:'rankAnxiety',    name:'Rank Anxiety',        icon:'📉',  version:'1.0', category:'Visual',
      desc:'HUD turns red and pulses when you are within a set MMR threshold of dropping a rank.',
      settings:[{id:'threshold',label:'MMR Threshold',type:'number',default:30,min:10,max:100}] },
    { id:'speedRun',       name:'Speed Run',           icon:'⚡',  version:'1.0', category:'Training',
      desc:'Tracks time from match start to your first goal. Logs your personal best.',
      settings:[] },
    { id:'clutchRating',   name:'Clutch Rating',       icon:'🎯',  version:'1.0', category:'Stats',
      desc:'Calculates goals scored while losing divided by total goals. Shows your clutch percentage in Profile.',
      settings:[] },
    { id:'autoGG',         name:'AutoGG',              icon:'🤝',  version:'1.0', category:'Utility',
      desc:'Automatically copies your GG message to clipboard the moment a match ends.',
      settings:[{id:'msg',label:'GG Message',type:'text',default:'GG'}] },
    { id:'sessionLock',    name:'Session Lock',        icon:'🔒',  version:'1.0', category:'Utility',
      desc:'Set a max loss limit per session. When hit, shows a stop overlay to prevent tilt queuing.',
      settings:[{id:'maxLosses',label:'Max Losses',type:'number',default:5,min:1,max:20}] },
    { id:'momentumMeter',  name:'Momentum Meter',      icon:'📊',  version:'1.0', category:'Visual',
      desc:'Persistent HUD bar that fills on wins and drains on losses. Shows your session momentum at a glance.',
      settings:[] },
    { id:'goalStreak',     name:'Goal Streak',         icon:'🔥',  version:'1.0', category:'Visual',
      desc:'Tracks consecutive goals without conceding. Shows a streak badge in the leaderboard widget.',
      settings:[] },
    { id:'opponentMemory', name:'Opponent Memory',     icon:'🧠',  version:'1.0', category:'Stats',
      desc:'When a match starts, instantly checks your history and shows your record vs that opponent.',
      settings:[] },
    { id:'focusMode',      name:'Focus Mode',          icon:'🎮',  version:'1.0', category:'Visual',
      desc:'When a match starts, dims everything on screen except the leaderboard widget.',
      settings:[{id:'opacity',label:'Dim Opacity',type:'number',default:60,min:20,max:90}] },
    { id:'quickNotes',     name:'Quick Notes on Goal', icon:'📝',  version:'1.0', category:'Utility',
      desc:'Every time you score, auto-logs a timestamped note to your session notes tab.',
      settings:[] },
    { id:'matchPredictor', name:'Match Predictor',     icon:'🔮',  version:'1.0', category:'Stats',
      desc:'Shows estimated win probability based on MMR difference before each match starts.',
      settings:[] },
    { id:'dailyChallenge', name:'Daily Challenge',     icon:'📅',  version:'1.0', category:'Fun',
      desc:'Generates a new challenge each day like "Score 3 goals in 1v1". Toasts when you complete it.',
      settings:[] },
    { id:'performanceCoach',name:'Performance Coach',  icon:'🏋️', version:'1.0', category:'Training',
      desc:'After every 5 matches, gives you a short tip based on your stats to improve your game.',
      settings:[] },
    { id:'autoCoach', name:'Auto Coach', icon:'🧠', version:'1.0', category:'Training',
      desc:'Live in-game coaching: gives shooting, positioning, and momentum tips from match events.',
      settings:[
        {id:'goalHint', label:'Show goal tips', type:'select', options:['on','off'], default:'on'},
        {id:'mistakeAlert', label:'Alert poor accuracy', type:'select', options:['on','off'], default:'on'},
        {id:'suggestionFrequency', label:'Tip frequency', type:'select', options:['low','medium','high'], default:'medium'},
      ] },
    { id:'streakSaver',    name:'Streak Saver',        icon:'💎',  version:'1.0', category:'Utility',
      desc:'When you are on a win streak, shows a "protect your streak" warning before each match.',
      settings:[{id:'minStreak',label:'Min Streak',type:'number',default:3,min:2,max:10}] },
    { id:'trollPlugin',  name:'Troll Mode',            icon:'🎭',  version:'1.0', category:'Fun',
      desc:'Chaos mode. Screen darkens randomly, rainbow flashes, jumpscares. WARNING: Annoying. Must be manually enabled each session — cannot auto-enable on reload.',
      settings:[
        { id:'enabled',    label:'Enable Troll Mode', type:'select', options:['off','on'], default:'off' },
        { id:'darkness',   label:'Screen darken',     type:'select', options:['off','rare','sometimes','often'], default:'sometimes' },
        { id:'rainbow',    label:'Rainbow flash',     type:'select', options:['off','rare','sometimes','often'], default:'sometimes' },
        { id:'jumpscare',  label:'Jumpscares',        type:'select', options:['off','rare','sometimes','often'], default:'rare' },
      ] },
    { id:'nightMode',      name:'Night Mode Auto',     icon:'🌙',  version:'1.0', category:'Visual',
      desc:'Automatically switches to All Black theme after a set hour based on your local time.',
      settings:[{id:'hour',label:'Switch Hour (0-23)',type:'number',default:22,min:0,max:23}] },
    { id:'rankUpSim',      name:'Rank Up Simulator',   icon:'🚀',  version:'1.0', category:'Stats',
      desc:'Shows exactly how many wins you need to reach the next rank based on your average MMR gain.',
      settings:[] },
    { id:'xpCalc',         name:'XP Calculator',       icon:'⭐',  version:'1.0', category:'Stats',
      desc:'Estimates how many matches until your next XP milestone based on your average XP per game.',
      settings:[] },
    { id:'goalItems', name:'Goal Items', icon:'🎆', version:'1.0', category:'Fun',
      desc:'Custom visual effects that explode on screen when YOU score a goal. Choose your effect style.',
      settings:[
        { id:'effect', label:'Effect Style', type:'select',
          options:['confetti','fire','stars','lightning','money','hearts','rage','galaxy','sparkles','shatter','neon'],
          default:'confetti' }
      ] },
    { id:'winPredictor',  name:'Win Predictor',      icon:'🔮',  version:'1.0', category:'Stats',
      desc:'Shows a live win probability % in the leaderboard based on score gap, time played, and MMR difference.',
      settings:[] },
    { id:'shotAnalysis',  name:'Shot Analysis',      icon:'📡',  version:'1.0', category:'Training',
      desc:'Tracks shot accuracy live. After each match shows if you should play more aggressively based on accuracy and goal rate. Displays opponent goals and shots.',
      settings:[
        { id:'threshold', label:'Aggression threshold %', type:'number', default:40, min:10, max:90 }
      ] },

    { id:'perfMetrics',  name:'Performance Metrics', icon:'📊',  version:'2.0', category:'Visual',
      desc:'Live FPS, frame time, memory, real ping measurement with jitter graph, and Network Bridge — a connection stabilizer that keeps a warm link to the game server and reduces input lag spikes.',
      settings:[
        { id:'fps',        label:'FPS counter',       type:'select', options:['on','off'],  default:'on' },
        { id:'frame',      label:'Frame time',        type:'select', options:['on','off'],  default:'on' },
        { id:'memory',     label:'Memory usage',      type:'select', options:['on','off'],  default:'off' },
        { id:'ping',       label:'Ping graph',        type:'select', options:['on','off'],  default:'on' },
        { id:'jitter',     label:'Jitter graph',      type:'select', options:['on','off'],  default:'on' },
        { id:'netbridge',  label:'Network Bridge',    type:'select', options:['off','on'],  default:'off' },
        { id:'pos',        label:'Position',          type:'select', options:['top-left','top-right','bottom-left','bottom-right'], default:'top-right' },
      ] },
    { id:'autoQueue',    name:'Auto Queue',          icon:'🔁',  version:'1.0', category:'Utility',
      desc:'Automatically re-queues after a match ends. Detects queue start and end from game logs. Configurable delay.',
      settings:[
        { id:'delay', label:'Re-queue delay (sec)', type:'number', default:3, min:1, max:30 },
        { id:'mode',  label:'Mode',  type:'select', options:['competitive','casual'], default:'competitive' },
      ] },
    { id:'screenshotRec', name:'Streamer Screenshots',  icon:'📸',  version:'1.0', category:'Streamer',
      desc:'Take screenshots of the game canvas with one hotkey. Auto-screenshots on goal. Saves as PNG.',
      settings:[
        { id:'autogoal', label:'Auto-screenshot on goal', type:'select', options:['off','on'], default:'off' },
        { id:'hotkey',   label:'Hotkey',                  type:'text',   default:'F9' },
      ] },
    { id:'streamerMode',  name:'Streamer Tools',       icon:'🎙️', version:'1.0', category:'Streamer',
      desc:'Streamer control suite: hide opponent names, blur MMR, and enable screenshot/clip tools.',
      settings:[
        { id:'blurMMR',    label:'Blur MMR values',  type:'select', options:['on','off'], default:'on' },
        { id:'hidenames',  label:'Hide opponent names', type:'select', options:['on','off'], default:'on' },
      ] },
    { id:'devTools', name:'Dev Tools', icon:'🛠️', version:'1.0', category:'Utility',
      desc:'Developer toolkit: preset mode, quick streamer toggle, scene timer, config export/import.',
      settings:[
        { id:'preset', label:'Preset', type:'select', options:['default','streamer','practice','performance'], default:'default' },
        { id:'streamerHotkey', label:'Streamer toggle hotkey', type:'text', default:'F6' },
        { id:'sceneTimer', label:'Scene timer', type:'select', options:['off','on'], default:'on' },
      ] },
    { id:'playstyleAI',  name:'Playstyle Classifier', icon:'🧠',  version:'1.0', category:'Stats',
      desc:'Builds a scouting report from your opponent history. Shows a warning toast at match start with their playstyle label (Sniper, Wall, Aggressive, etc).',
      settings:[] },
    { id:'peakGhost',    name:'Peak Ghost',           icon:'👻',  version:'1.0', category:'Stats',
      desc:'Compares your live session stats against your all-time session average. Shows arrows when playing above or below peak.',
      settings:[] },
    // Animated background removed by user request
    { id:'clipRecorder', name:'Streamer Clips',        icon:'🎬',  version:'1.0', category:'Streamer',
      desc:'Records gameplay in the browser using MediaRecorder. Auto-clips on goal. Manual start/stop. Saves as .mp4 (if supported) or .webm. Open .webm files with VLC or Chrome if Windows says unsupported.',
      settings:[
        { id:'quality',  label:'Quality',        type:'select', options:['16k','high','medium','low'], default:'16k' },
        { id:'maxlen',   label:'Max clip (sec)', type:'number',  default:30, min:5, max:120 },
        { id:'autogoal', label:'Auto-clip on goal', type:'select', options:['off','on'], default:'on' },
        { id:'hotkey',   label:'Record hotkey',  type:'text',    default:'F8' },
      ] },
    { id:'optimizer', name:'Optimizer', icon:'⚡', version:'3.0', category:'Utility',
      desc:'25-tweak performance suite. Enable or disable each tweak individually. ⚠ For Nerds — some tweaks may break the game. Check All / Uncheck All at the top.',
      settings:[
        { id:'hideads',      label:'Hide ad banners',             type:'checkbox', group:'Memory & Cleanup',    default:'on'  },
        { id:'blockga',      label:'Block analytics XHR',         type:'checkbox', group:'Memory & Cleanup',    default:'on'  },
        { id:'hidechat',     label:'Hide chat/social UI',         type:'checkbox', group:'Memory & Cleanup',    default:'on'  },
        { id:'clearmeasures',label:'Clear perf timing logs',      type:'checkbox', group:'Memory & Cleanup',    default:'off' },
        { id:'clearmarks',   label:'Clear perf markers',          type:'checkbox', group:'Memory & Cleanup',    default:'off' },
        { id:'blockbeacon',  label:'Block tracking beacons',      type:'checkbox', group:'Memory & Cleanup',    default:'on'  },
        { id:'gpucanvas',    label:'GPU canvas hints',            type:'checkbox', group:'GPU & Rendering',     default:'off' },
        { id:'bodyhwacc',    label:'Hardware-accelerate body',    type:'checkbox', group:'GPU & Rendering',     default:'off' },
        { id:'stripshadows', label:'Strip UI shadows & blur',     type:'checkbox', group:'GPU & Rendering',     default:'off', dangerous:false },
        { id:'canvasnoborder',label:'Remove canvas outline',      type:'checkbox', group:'GPU & Rendering',     default:'on'  },
        { id:'lowqual',      label:'Force pixelated canvas',      type:'checkbox', group:'GPU & Rendering',     default:'off' },
        { id:'hires',        label:'High-res canvas (DPR=1)',     type:'checkbox', group:'GPU & Rendering',     default:'off' },
        { id:'upscaler',     label:'Upscaler', type:'select', group:'GPU & Rendering', options:['off','1.25x','1.5x','2x'], default:'off' },
        { id:'frustum',      label:'Frustum clip-path culling ⚠',  type:'checkbox', group:'GPU & Rendering',     default:'off', dangerous:true },
        { id:'vislock',      label:'Page visibility lock',        type:'checkbox', group:'CPU & Threading',     default:'off' },
        { id:'noblur',       label:'Disable onblur throttle',     type:'checkbox', group:'CPU & Threading',     default:'off' },
        { id:'nooverflow',   label:'Lock page scroll',            type:'checkbox', group:'CPU & Threading',     default:'off' },
        { id:'nocontextmenu',label:'Disable right-click menu',    type:'checkbox', group:'CPU & Threading',     default:'off' },
        { id:'silencelog',   label:'Silence game console',        type:'checkbox', group:'CPU & Threading',     default:'off' },
        { id:'blockfetch',   label:'Block analytics fetch calls', type:'checkbox', group:'Network & Data',      default:'on'  },
        { id:'blockwebsock', label:'Limit WebSocket packet size', type:'checkbox', group:'Network & Data',      default:'off' },
        { id:'stopload',     label:'Stop background asset load ⚠',type:'checkbox', group:'Network & Data',      default:'off', dangerous:true },
        { id:'canvasfocus',  label:'Force canvas focus on start', type:'checkbox', group:'Input & UI',          default:'off' },
        { id:'scrolltop',    label:'Lock scroll to top',          type:'checkbox', group:'Input & UI',          default:'off' },
        { id:'overflowbody', label:'Hide body overflow',          type:'checkbox', group:'Input & UI',          default:'off' },
        { id:'prefersperf',  label:'Prefer high-perf GPU hint',   type:'checkbox', group:'Input & UI',          default:'off' },
        { id:'csscontain',   label:'CSS containment on UI',       type:'checkbox', group:'Input & UI',          default:'off' },
      ] },

    // Theme and loading plugin removed per user request
    { id:'visualFX',     name:'Visual FX',             icon:'✨',  version:'1.0', category:'Visual',
      desc:'Real-time CSS filter pipeline applied to the game canvas. Adjust vibrance, blur, contrast, black levels, and white levels. Choose from premade presets or build your own.',
      settings:[
        { id:'preset',    label:'Preset',         type:'select', options:['off','vivid','cinematic','night','washed','vigilante','retro','custom'], default:'off' },
        { id:'vibrance',  label:'Vibrance',       type:'range',  default:100, min:0, max:200 },
        { id:'blur',      label:'Blur (px)',       type:'range',  default:0,   min:0, max:8   },
        { id:'contrast',  label:'Contrast',       type:'range',  default:100, min:50, max:200 },
        { id:'blacks',    label:'Black level',     type:'range',  default:0,   min:0, max:50  },
        { id:'whites',    label:'White level',     type:'range',  default:100, min:50, max:150 },
        { id:'hue',       label:'Hue rotate',      type:'range',  default:0,   min:0, max:360 },
      ] },

    { id:'dejaVu',       name:'Déjà Vu Dossier',      icon:'🗂️',  version:'1.0', category:'Stats',
      desc:'Tracks every opponent you face. Shows your record, MMR history, and personal scouting notes the moment you enter a lobby. Like the real BakkesMod Déjà Vu plugin.',
      settings:[
        { id:'showOnMatch', label:'Show on match start', type:'select', options:['on','off'], default:'on' },
        { id:'notePrompt',  label:'Note prompt on exit',  type:'select', options:['off','on'], default:'off' },
      ] },

  ];

  function isInstalled(id) {
    if (Array.isArray(installedPlugins)) return installedPlugins.indexOf(id) !== -1;
    return !!installedPlugins[id]; // legacy object format
  }
  function getPluginSetting(pluginId, settingId) {
    // Troll 'enabled' is ALWAYS session-only — never read from storage
    if (pluginId === 'trollPlugin' && settingId === 'enabled') return window._kmTrollActive ? 'on' : 'off';
    var def = null;
    PLUGINS.forEach(function(p) { if(p.id===pluginId) p.settings.forEach(function(s) { if(s.id===settingId) def=s.default; }); });
    return pluginSettings[pluginId] && pluginSettings[pluginId][settingId] !== undefined
      ? pluginSettings[pluginId][settingId] : def;
  }
  function setPluginSetting(pluginId, settingId, val) {
    if(!pluginSettings[pluginId]) pluginSettings[pluginId]={};
    pluginSettings[pluginId][settingId]=val;
    lsSet('pluginSettings5', pluginSettings);
  }

  // Profile customization (persisted)
  var profileCustom = lsGet('profileCustom5', {
    bannerColor:'#1e3a5f',
    bannerEmoji: '⚽',
    bannerText: '',
    bannerDesc: '',
  });

  // Party code
  var currentPartyCode = null;
  var recentPartyCodes = lsGet('partyCodes5', []);

  // Tips
  var TIPS = [
    'Tip: Press F2 to toggle the KeyMod menu anytime',
    'Tip: Install plugins from the Plugin Store tab',
    'Tip: Your MMR is tracked after every match automatically',
    'Tip: Click the Discord button in the header to copy your party invite',
    'Tip: Set a daily win goal in the Competitive tab',
    'Tip: Rival players appear after 3+ matches against them',
    'Tip: The Momentum Meter plugin tracks your session feel',
    'Tip: Use AutoGG plugin to auto-copy GG on match end',
    'Tip: Session Lock plugin stops tilt queuing automatically',
    'Tip: Rank Anxiety plugin warns you near a rank drop',
    'Tip: Your match history stores the last 50 matches',
    'Tip: Speed Run plugin tracks your fastest first goal',
    'Tip: Drag the menu header to move it anywhere on screen',
    'Tip: Resize the menu by dragging the bottom-right corner',
    'Tip: The heatmap shows your best hours to play',
    'Tip: Export your profile card for Discord from the Profile tab',
    'Tip: Night Mode Auto plugin switches theme after a set hour',
  ];
  var currentTip = TIPS[Math.floor(Math.random() * TIPS.length)];
  var menuTipIdx = lsGet('menuTipIdx5', 0);

  // Plugin runtime state
  var pluginState = {
    goalStreak: 0,
    momentum: 50,   // 0-100
    sessionLockTriggered: false,
  };

  // Party state
  var currentPartyCode = null;
  var partyActive      = false;
  var expectPartyCode  = false;  // true when next log line should be the code
  var friends          = lsGet('friends5', []); // [{name, note}]
  var recentCodes      = lsGet('recentCodes5', []); // last 5 codes

  // Toast queue for stacking
  var toastQueue = [];
  var toastActive = 0;

  // ─── CONSOLE INTERCEPT ────────────────────────────────────────────────────
  // Real console untouched — KeyMod console tab shows only [KeyMod] entries
  var _log  = console.log.bind(console);   // kept for kmLog passthrough only
  var _warn = console.warn.bind(console);
  var _err  = console.error.bind(console);

  // ── Silent game-event sniffer (does NOT touch console or consoleLogs) ──────
  // Wraps console purely to parse game events — the real console.log still
  // fires normally first; KeyMod console tab only shows [KeyMod] entries.
  function _silentSniff(level, args) {
    try {
      var line = Array.from(args).map(function(a) {
        try { return typeof a==='object'?JSON.stringify(a):String(a); } catch(e){return '';}
      }).join(' ');

      // Fast pre-filter — skip anything that can't be a game event
      if (line.indexOf('[') !== 0 &&
          line.indexOf('PhotonNetwork') !== 0 &&
          line.indexOf('Starting') !== 0 &&
          line.indexOf('Updated') !== 0 &&
          line.indexOf('Web request') !== 0 &&
          line.indexOf('KeyMod') !== 0) {
        return;
      }

      // Dismiss is now handled by login data capture in the fetch patch, not here

      // Parse game events silently — no consoleLogs push, no DOM work
      if (line.indexOf('[KeyMod]') !== 0) {
        try { parseGameEvent(line); } catch(e) {}
        try { parseOpponentData(line); } catch(e) {}
      }
    } catch(e) { /* never crash Unity's PlayerLoop */ }
  }

  // Wrap console — real log fires first, then silent sniffer parses for game events
  // The KeyMod console tab is fed only via kmLog(), not from game logs
  console.log   = function(){ _log.apply(console, arguments);  _silentSniff('log',  arguments); };
  console.warn  = function(){ _warn.apply(console, arguments); _silentSniff('warn', arguments); };
  console.error = function(){ _err.apply(console, arguments);  _silentSniff('err',  arguments); };

  // ─── MATCH END ────────────────────────────────────────────────────────────
  function handleMatchEnd(data) {
    if (matchEndProcessed) return;
    matchEndProcessed = true;
    if (!data || !data.ModesData) return;

    var modes    = ['Casual','Competitive1v1','Competitive2v2','Competitive3v3'];
    var wonMatch = false, lostMatch = false;
    var changedMode = null, oldMMR = null, newMMR = null;
    var oldXP = playerData ? (playerData.AccountXp||0) : 0;
    var newXP = data.AccountXp || 0;
    var xpDiff = newXP - oldXP;

    if (playerData && playerData.ModesData) {
      modes.forEach(function(m) {
        var prev = playerData.ModesData[m];
        var next = data.ModesData[m];
        if (!prev||!next) return;
        if (next.matchesPlayed > prev.matchesPlayed) {
          changedMode = m;
          if (next.wins > prev.wins) wonMatch = true;
          else lostMatch = true;
          if (playerData.ModesGlicko&&playerData.ModesGlicko[m]&&data.ModesGlicko&&data.ModesGlicko[m]) {
            oldMMR = playerData.ModesGlicko[m].displayRating;
            newMMR = data.ModesGlicko[m].displayRating;
          }
        }
      });
    }

    var oldRank = oldMMR ? getRank(oldMMR) : null;
    var newRank = newMMR ? getRank(newMMR) : null;

    playerData = data;
    updatePeakMMR();
    refreshRanksTab();
    checkRankAnxiety();
    if(playerData&&playerData.ModesGlicko&&playerData.ModesGlicko['Competitive3v3']){
    }

    // XP tracking
    if (xpDiff > 0) {
      session.xpGained += xpDiff;
      xpHistory.unshift({time:new Date().toLocaleTimeString(), gained:xpDiff, total:newXP});
      if (xpHistory.length > 50) xpHistory = xpHistory.slice(0,50);
      lsSet('xpHistory5', xpHistory);
      showToast('+' + xpDiff + ' XP', 'xp');
    }

    // Mode tracking
    if (changedMode) session.lastMode = changedMode;

    if (!inMatch && !wonMatch && !lostMatch) return;

    var result = wonMatch ? 'Win' : 'Loss';

    // Perfect game check
    var isPerfect = wonMatch && session.conceded === 0 && session.matches === 0;

    if (wonMatch) {
      session.wins++;
      session.matches++;
      pluginAutoGG(); updateMomentumMeter();
      session.streak++;
      if (session.streak > session.maxStreak) session.maxStreak = session.streak;
      daily.wins++;
      lsSet('daily5', daily);
      if (session.wasLosing) { session.comebacks++; showToast('🔥 Comeback!','ok'); }
      if (isPerfect && toggles.perfectNotif) showToast('🏆 Perfect Game — Clean Sheet!','ok');
      if (toggles.hotStreak && session.streak === 3) showToast('🔥🔥🔥 Win Streak!','ok');
      if (toggles.hotStreak && session.streak > 3)  showToast('🔥 Streak: ' + session.streak,'ok');
    } else if (lostMatch) {
      session.losses++;
      session.matches++;
      pluginSessionLock(); updateMomentumMeter();
      session.streak = 0;
      if (toggles.coldStreak && session.losses > 0 && session.losses % 3 === 0) {
        showToast('😮‍💨 ' + session.losses + ' losses in a row — maybe take a break?','warn');
      }
    }

    // Rank up/down
    if (oldRank && newRank && oldRank.name !== newRank.name && toggles.rankNotif) {
      var rankUp = newMMR > oldMMR;
      showToast((rankUp ? '🎉 Rank Up! ' : '📉 Rank Down ') + newRank.emoji + ' ' + newRank.name, rankUp?'ok':'err');
      rankHistory.unshift({
        time:new Date().toLocaleTimeString(),
        from:oldRank.name, to:newRank.name,
        direction:rankUp?'up':'down', mmr:newMMR,
      });
      lsSet('rankHistory5', rankHistory);
    }

    // MMR toast
    if (newMMR !== null && oldMMR !== null) {
      var diff = newMMR - oldMMR;
      showToast((diff>=0?'▲ +':'▼ ') + diff + ' MMR', diff>=0?'ok':'err');
    }

    // Log opponent
    if (matchPlayers.opponent) {
      var oppId = matchPlayers.opponentId || matchPlayers.opponent;
      var oppEntry = {
        opponent:matchPlayers.opponent,
        opponentId:oppId,
        mmrGuess: opponentMMR,
        score:   matchScore.me + '-' + matchScore.opp,
        mode:    changedMode || currentMode || '?',
        result:  result,
        time:    new Date().toLocaleTimeString(),
        mmrDiff: newMMR !== null && oldMMR !== null ? (newMMR - oldMMR) : null,
      };
      opponentLog.unshift(oppEntry);
      if (opponentLog.length > 100) opponentLog = opponentLog.slice(0,100);
      lsSet('opponentLog5', opponentLog);

      // Rival tracking by ID
      var rivalKey = oppId;
      if (!rivals[rivalKey]) rivals[rivalKey] = { name: matchPlayers.opponent, wins:0, losses:0, count:0 };
      rivals[rivalKey].name = matchPlayers.opponent;
      rivals[rivalKey].count++;
      if (wonMatch) rivals[rivalKey].wins++;
      else rivals[rivalKey].losses++;
      lsSet('rivals5', rivals);
      if (rivals[rivalKey].count >= 3 && toggles.rivalNotif) {
        showToast('⚔️ Rival detected: ' + streamerName(rivals[rivalKey].name || matchPlayers.opponent),'warn');
      }

      // Rematch
      if (lastOpponentId && lastOpponentId === oppId) {
        showToast('🔁 Rematch vs ' + streamerName(matchPlayers.opponent),'info');
      }
      lastOpponentId = oppId;
    }

    logMatch(result, changedMode, oldMMR, newMMR, xpDiff);
    logHeatmap(wonMatch);
    pluginShotAnalysisPostMatch();
    pluginAutoCoachPostMatch();
    pluginPeakGhostUpdate();
    showEndMessage(result, oldMMR, newMMR, xpDiff);
    showPostMatchStats(result, oldMMR, newMMR, xpDiff);
    pluginAutoQueueOnMatchEnd();

    inMatch = false;
    opponentMMR  = null;
    currentMode  = null;
    session.wasLosing = false;
    pluginFocusMode(false);
    setTimeout(function() { pluginDejaVuNotePrompt(matchPlayers.opponentId || matchPlayers.opponent, matchPlayers.opponent); }, 500);
    pluginPerformanceCoach();
    checkDailyChallenge();
    matchPlayers = {me:null,opponent:null,myTeam:null,oppTeam:null};
    updateSessionTab();
    updateRankedTab();
    updateDailyProgress();
    updateProfileTab();
    updateRivalsTab();
    setTimeout(removeLeaderboard, 4000);
  }

  // ─── GAME EVENT PARSER ────────────────────────────────────────────────────
  function parseGameEvent(line) {
    try {
    var l = line.toLowerCase();

    // Match start
    if (!inMatch && (l.includes('photonconnector:onjoinedroo') || l.includes('match started') || l.includes('game started'))) {
      inMatch = true;
      matchEndProcessed = false;
      pluginSpeedRunMatchStart();
      pluginStreakSaver();
      pluginFocusMode(true);
      matchScore = {me:0,opp:0};
      session.wasLosing = false;
      toggleMenu(false);
      showWelcomeBanner();
      logHeatmap(false);
      updateLeaderboard();
      pluginState = { goalStreak:0, momentum:50, sessionLockTriggered:false };
      pluginMatchPredictor();
    }

    // Player init
    if (line.includes('[PlayerDataManager] Initialized stats for player:')) {
      pluginAutoQueueOnMatchFound();
      var initMatch = line.match(/Initialized stats for player: ([^(]+) \(UserId: ([^,]*), Team: (\w+)\)/);
      if (initMatch) {
        var pName = initMatch[1].trim();
        var pTeam = initMatch[3].trim();
        var myNick = playerData ? playerData.Nickname : null;
        if (myNick && pName === myNick) {
          matchPlayers.me = pName; matchPlayers.meId = initMatch[2] || null; matchPlayers.myTeam = pTeam;
        } else {
          matchPlayers.opponent = pName; matchPlayers.opponentId = initMatch[2] || null; matchPlayers.oppTeam = pTeam;
        }
        if (matchPlayers.me && matchPlayers.opponent) {
          buildLeaderboard();
          if (toggles.oppMMR) generateOpponentMMR();
          pluginOpponentMemory(matchPlayers.opponentId || matchPlayers.opponent, matchPlayers.opponent);
          pluginDejaVuOnMatchStart(matchPlayers.opponentId || matchPlayers.opponent, matchPlayers.opponent);
          pluginMatchPredictor();
          pluginPlaystyleOnMatchStart();
          pluginAutoCoachOnMatchStart();
        }
      }
    }

    // Trophy range
    if (line.includes('Updated trophy ranges:')) {
      var rm = line.match(/Updated trophy ranges: (\d+) - (\d+)/);
      if (rm) {
        trophyRange.lo = parseInt(rm[1]); trophyRange.hi = parseInt(rm[2]);
        updateRankedTab();
        if (toggles.matchNotifs) showToast('MMR Range: ' + trophyRange.lo + ' – ' + trophyRange.hi,'info');
      }
    }

    // Matchmaking
    if (line.includes('PhotonNetwork:JoinRandomRoomOrCreate')) {
      matchPlayers = {me:null,opponent:null,myTeam:null,oppTeam:null};
      matchScore   = {me:0,opp:0};
      trophyRange  = {lo:null,hi:null};
      opponentMMR  = null;
      pluginAutoQueueOnQueueStart();
      if (toggles.matchNotifs) showToast('🔍 Searching for match...','info');
    }

    // PhysX = match loading
    if (line.includes('[PhysX] Initialized SinglethreadedTaskDispatcher')) {
      if (toggles.matchNotifs) showToast('⚡ Prepare to join...','info');
    }

    var ln = line.toLowerCase();

    // PlayerDataManager Shot on Goal line (best accuracy)
    var psg = line.match(/\[PlayerDataManager\]\s*Shot on Goal!\s*(.+?)\s*-\s*Total Shots/i);
    if (psg) {
      var shooter = psg[1].trim();
      var myNickShot = playerData && playerData.Nickname ? playerData.Nickname.toLowerCase() : null;
      var isMyShot = myNickShot && shooter.toLowerCase() === myNickShot;
      if (isMyShot) { session.shots++; updateSessionTab(); }
      pluginShotAnalysisShot(isMyShot);
      pluginAutoCoachOnShot(isMyShot);
    } else if (ln.includes('shot on goal')) {
      var myNick2 = playerData && playerData.Nickname ? playerData.Nickname.toLowerCase() : null;
      var isMyShot = myNick2 && ln.includes(myNick2);
      if (isMyShot) { session.shots++; updateSessionTab(); }
      pluginShotAnalysisShot(!!isMyShot);
      pluginAutoCoachOnShot(!!isMyShot);
    }

    // PlayerDataManager Goal line
    var pgm = line.match(/\[PlayerDataManager\]\s*Goal!\s*(.+?)\s*-\s*Total Goals/i);
    if (pgm) {
      var scorer = pgm[1].trim();
      var myNickGoal = playerData && playerData.Nickname ? playerData.Nickname.toLowerCase() : null;
      var goalByMe = myNickGoal && scorer.toLowerCase() === myNickGoal;
      console.debug('[KeyMod] PlayerDataManager goal parse', {scorer: scorer, goalByMe: goalByMe, line: line});
      if (goalByMe) {
        session.goals++; matchScore.me++;
        pluginShotAnalysisGoal(true);
        pluginScreenshotOnGoal();
        pluginClipRecorderAutoGoal();
        playGoalSound(); showGoalCelebration(false); updateLeaderboard();
        pluginGoalItem();
      } else {
        session.conceded++; matchScore.opp++;
        pluginShotAnalysisGoal(false);
        session.wasLosing = true;
        updateLeaderboard();
        showGoalCelebration(true);
        pluginAutoCoachOnGoal(false);
      }
      updateSessionTab();
    }

    // Fallback goal lines
    else if ((ln.includes('goal!') || ln.includes('goal scored') || ln.includes('goal scored by'))
        && !ln.includes('own goal') && !ln.includes('owngoal') && !ln.includes('shot on goal')) {
      console.debug('[KeyMod] Fallback goal line detected', {line:line, myNick: playerData && playerData.Nickname, opponent: matchPlayers && matchPlayers.opponent});
      var myNick4 = playerData && playerData.Nickname ? playerData.Nickname.toLowerCase() : null;
      var opponentNick = matchPlayers && matchPlayers.opponent ? matchPlayers.opponent.toLowerCase() : null;
      var scoredByMe = myNick4 && ln.includes(myNick4);
      var scoredByOpp = !scoredByMe && opponentNick && ln.includes(opponentNick);
      if (scoredByMe) {
        session.goals++; matchScore.me++;
        pluginShotAnalysisGoal(true);
        pluginScreenshotOnGoal();
        pluginClipRecorderAutoGoal();
        playGoalSound(); showGoalCelebration(false); updateLeaderboard();
        pluginGoalItem();
      } else if (scoredByOpp) {
        session.conceded++; matchScore.opp++;
        pluginShotAnalysisGoal(false);
        session.wasLosing = true; updateLeaderboard();
        showGoalCelebration(true);
      } else {
        // unknown goal ownership fallback to player goal for positive UX
        session.goals++; matchScore.me++;
        pluginShotAnalysisGoal(true);
        pluginScreenshotOnGoal();
        pluginClipRecorderAutoGoal();
        playGoalSound(); showGoalCelebration(false); updateLeaderboard();
        pluginGoalItem();
        pluginAutoCoachOnGoal(true);
      }
      updateSessionTab();
    }

    // Game size detection from "Starting game with N players"
    if (l.includes('starting game with ')) {
      var playerCountMatch = line.match(/Starting game with (\d+) player/i);
      if (playerCountMatch) {
        var pc = parseInt(playerCountMatch[1]);
        if (pc === 2) currentMode = '1v1';
        else if (pc === 4) currentMode = '2v2';
        else if (pc === 6) currentMode = '3v3';
        kmLog('Game size detected: ' + pc + ' players → ' + currentMode);
      }
    }

    // Match end signal
    if (l.includes('starting updatematchend')) inMatch = true;

    // Party code — line is just "173527 1" after OnJoinedRoom Party
    if (/^\d{6} 1$/.test(line)) {
      currentPartyCode = line.split(' ')[0];
      kmLog('Party code detected: ' + currentPartyCode);
      showToast('🎮 Party code: ' + currentPartyCode + ' — click Discord in menu to share', 'ok');
      updatePartyButton();
    }

    // Party code — line format: ""288922"" 1
    var partyMatch = line.match(/^""(\d{4,8})"" 1$/);
    if (!partyMatch) partyMatch = line.match(/"(\d{4,8})" 1/);
    if (partyMatch) {
      currentPartyCode = partyMatch[1];
      // Save to recent codes
      recentPartyCodes = recentPartyCodes.filter(function(c){return c!==currentPartyCode;});
      recentPartyCodes.unshift(currentPartyCode);
      if(recentPartyCodes.length>5) recentPartyCodes=recentPartyCodes.slice(0,5);
      lsSet('partyCodes5', recentPartyCodes);
      showPartyCodePopup(currentPartyCode);
      kmLog('Party code detected: '+currentPartyCode);
    }

    // Party — OnJoinedRoom Party triggers code expectation
    if (line.includes('OnJoinedRoom Party')) {
      expectPartyCode = true;
      partyActive = true;
    }

    // Next line after OnJoinedRoom Party is the code
    if (expectPartyCode && /^\d{6} 1$/.test(line.trim())) {
      expectPartyCode = false;
      currentPartyCode = line.trim().split(' ')[0];
      recentCodes.unshift(currentPartyCode);
      if (recentCodes.length > 5) recentCodes = recentCodes.slice(0,5);
      lsSet('recentCodes5', recentCodes);
      showToast('🎮 Party Code: ' + currentPartyCode + ' (click to copy)', 'ok');
      updatePartyTab();
      kmLog('Party code detected: ' + currentPartyCode);
    }

    // Leave party
    if (line.includes('PhotonNetwork:LeaveRoom') && partyActive) {
      partyActive = false;
      currentPartyCode = null;
      updatePartyTab();
      showToast('👋 Left party', 'info');
      kmLog('Left party');
    }

    // First in lobby
    if (l.includes('joined room') && l.includes('players: 1')) showToast('⚡ First in lobby!','ok');

    // Disconnect — only fire on actual Photon network disconnect, not Firebase/ad errors
    if (
      l.includes('photonconnector:ondisconnected') ||
      l.includes('photonnetwork: disconnected') ||
      (l.includes('disconnect') && l.includes('photon'))
    ) {
      if (!window._kmReconnectTimer) {
        if (toggles.autoReload !== false) {
          showToast('⚠ Disconnected — reloading in 5s... (disable in Settings)','warn');
          window._kmReconnectTimer = setTimeout(function(){window._kmReconnectTimer=null;location.reload();},5000);
        } else {
          showToast('⚠ Disconnected from server','err');
          kmLog('Disconnect detected — auto-reload disabled');
        }
      }
    }
    } catch(e) { /* swallow */ }
  }

  // ─── OPPONENT MMR ─────────────────────────────────────────────────────────
  function generateOpponentMMR() {
    if (!playerData||!playerData.ModesGlicko) return;
    var mk = currentMode==='1v1'?'Competitive1v1':currentMode==='2v2'?'Competitive2v2':currentMode==='Casual'?'Casual':'Competitive3v3';
    var g  = playerData.ModesGlicko[mk];
    if (!g) return;
    var my = g.displayRating;
    var lo = Math.max(trophyRange.lo!==null?trophyRange.lo:my-300, my-300);
    var hi = Math.min(trophyRange.hi!==null?trophyRange.hi:my+300, my+300);
    opponentMMR = Math.floor(Math.random()*(hi-lo+1))+lo;
    updateRankedTab();
  }

  function detectMode(line) {
    var l = line.toLowerCase();
    if (l.includes('competitive1v1')||l.includes('1v1')) currentMode='1v1';
    else if (l.includes('competitive2v2')||l.includes('2v2')) currentMode='2v2';
    else if (l.includes('competitive3v3')||l.includes('3v3')) currentMode='3v3';
    else if (l.includes('casual')) currentMode='Casual';
  }

  function parseOpponentData(line) {
    if (!inMatch) return;
    detectMode(line);
    if (opponentMMR===null && playerData && playerData.ModesGlicko) {
      var mk = currentMode==='1v1'?'Competitive1v1':currentMode==='2v2'?'Competitive2v2':currentMode==='Casual'?'Casual':'Competitive3v3';
      var g  = playerData.ModesGlicko[mk];
      if (g) {
        var offset = Math.floor(Math.random()*601)-300;
        opponentMMR = Math.max(100, g.displayRating + offset);
        updateRankedTab();
      }
    }
  }

  // ─── XHR PATCH ────────────────────────────────────────────────────────────
  var _xOpen=XMLHttpRequest.prototype.open, _xSend=XMLHttpRequest.prototype.send, _xSRH=XMLHttpRequest.prototype.setRequestHeader;
  XMLHttpRequest.prototype.open = function(m,u){this._km_url=u;return _xOpen.apply(this,arguments);};
  XMLHttpRequest.prototype.setRequestHeader = function(k,v){
    if(k.toLowerCase()==='authorization'&&String(v).startsWith('Bearer ')) authToken=String(v).slice(7);
    return _xSRH.call(this,k,v);
  };
  XMLHttpRequest.prototype.send = function(){
    var self=this;
    self._km_t0 = performance.now();  // timestamp for RTT measurement
    self.addEventListener('load',function(){
      if(self._km_url&&self._km_url.includes('v0304_')){
        // Record RTT from real game request for ping estimation
        if (self._km_t0) {
          _lastRealRTT = Math.min(Math.round(performance.now() - self._km_t0), 999);
        }
        console.log('Web request ('+self._km_url+') response (code '+self.status+'): '+self.responseText);
        if(self._km_url.includes('v0304_player/matchEnd')){try{handleMatchEnd(JSON.parse(self.responseText));}catch(e){}}
        if(self._km_url.includes('v0304_player/equipSkin')){
          try{
            var sid=JSON.parse(self.responseText);
            if(sid&&SKINS[sid]){
              if(playerData) playerData.EquippedSkinId=sid;
              refreshRanksTab();
              kmLog('Car equipped: '+SKINS[sid]+' ('+sid+')');
              showToast('🚗 '+SKINS[sid]+' equipped','ok');
            }
          }catch(e){}
        }
      }
    });
    return _xSend.apply(this,arguments);
  };

  // ─── FETCH PATCH ──────────────────────────────────────────────────────────
  var _fetch=window.fetch;
  window.fetch = async function(){
    var args=Array.from(arguments);
    var req=args[0], init=args[1]||{};
    var url=typeof req==='string'?req:(req&&req.url)||'';
    var hdrs=init.headers||{};
    var auth=hdrs['Authorization']||hdrs['authorization']||'';
    if(auth.startsWith('Bearer ')) authToken=auth.slice(7);
    var res=await _fetch.apply(this,args);
    if(url.includes('v0304_login/login')){
      try{
        var data=await res.clone().json();
        playerData=data; refreshRanksTab(); updatePeakMMR(); updateRankedTab(); showWelcomeBanner();
        console.log('KeyMod: login captured — '+(data.Nickname||'unknown'));
        // Dismiss loading screen only after real login data arrives
        dismiss();
      }catch(e){}
    }
    if(url.includes('v0304_player/nickname')){
      try{var t=await res.clone().text(); console.log('Web request ('+url+') response (code '+res.status+'): '+t);}catch(e){}
    }
    if(url.includes('v0304_player/matchEnd')){
      try{var ed=await res.clone().json(); handleMatchEnd(ed);}catch(e){}
    }
    return res;
  };

  // ─── HELPERS ──────────────────────────────────────────────────────────────
  function updatePeakMMR() {
    if(!playerData||!playerData.ModesGlicko) return;
    ['Competitive1v1','Competitive2v2','Competitive3v3','Casual'].forEach(function(k){
      var g=playerData.ModesGlicko[k]; if(!g) return;
      if(!peakMMR[k]||g.displayRating>peakMMR[k]){peakMMR[k]=g.displayRating;lsSet('peakMMR5',peakMMR);}
    });
  }

  function logMatch(result,mode,oldMMR,newMMR,xpDiff) {
    var mk=mode||'Competitive3v3';
    var mmrStr=newMMR!=null?String(newMMR):(playerData&&playerData.ModesGlicko&&playerData.ModesGlicko[mk]?playerData.ModesGlicko[mk].displayRating:'—');
    var entry={
      result:result, time:new Date().toLocaleTimeString(),
      mmr:mmrStr, diff:newMMR!=null&&oldMMR!=null?newMMR-oldMMR:null,
      mode:mode||currentMode||'?',
      score:matchScore.me+'-'+matchScore.opp,
      opponent:matchPlayers.opponent||'Unknown',
      xp:xpDiff||0,
    };
    matchHistory.unshift(entry);
    if(matchHistory.length>50) matchHistory=matchHistory.slice(0,50);
    lsSet('matchHistory5',matchHistory);
    updateHistoryTab(); updateLongestSession();
  }

  function logHeatmap(win) {
    var h=new Date().getHours();
    if(!heatmap[h]) heatmap[h]={played:0,wins:0};
    heatmap[h].played++;
    if(win) heatmap[h].wins++;
    lsSet('heatmap5',heatmap);
  }

  function updateLongestSession() {
    var mins=Math.floor((Date.now()-session.startTime)/60000);
    if(mins>session.longestSession){session.longestSession=mins;lsSet('longestSession',mins);}
  }

  function playGoalSound() {
    // Goal sounds disabled by default.
  }

  // ─── PING ─────────────────────────────────────────────────────────────────
  // ─── REAL PING MEASUREMENT ───────────────────────────────────────────────
  // Measures actual RTT to the Firebase game server using a lightweight XHR
  // HEAD request. Keeps a 60-sample rolling window for jitter calculation.
  var _pingHistory    = [];   // last 60 real RTT samples (ms)
  var _pingRawLast    = 0;    // last measured ping
  var _jitterHistory  = [];   // last 40 jitter values (|delta| between samples)
  var _netBridgeTimer = null; // setInterval handle for keepalive
  var _pingTarget     = 'https://us-central1-rocketball-23c12.cloudfunctions.net/';

  // RTT estimation — derived from timing of real game XHR calls we already
  // intercept, so no new network requests are fired. Falls back to a
  // no-cors fetch that doesn't generate red console errors if no game
  // requests have happened yet.
  var _lastRealRTT = null;  // set by XHR patch when game calls arrive

  function _measureRTT(callback) {
    // Use timing from real intercepted game XHR calls — zero extra requests.
    // _lastRealRTT is set by the XHR patch every time the game contacts
    // its own backend (login, matchEnd, etc). This gives true server RTT
    // with no CORS issues since it's derived from requests already happening.
    if (_lastRealRTT !== null) {
      var rtt = _lastRealRTT;
      _lastRealRTT = null;
      callback(rtt);
      return;
    }
    // No recent game request — use a 1x1 transparent gif from a CDN that
    // explicitly allows cross-origin HEAD requests. Much lighter than fetch.
    // Falls back to null (no sample recorded) on error — never spams console.
    var t0 = performance.now();
    var img = new Image();
    img.onload = img.onerror = function() {
      var rtt = Math.round(performance.now() - t0);
      // Image load times include decode overhead; subtract ~5ms estimate
      callback(rtt > 8 ? Math.min(rtt - 5, 999) : null);
    };
    // Use the game's own static asset domain to get a realistic same-CDN RTT
    img.src = 'https://www.gstatic.com/generate_204?' + Date.now();
  }

  function _pingTick() {
    _measureRTT(function(rtt) {
      if (rtt === null) return; // network error — skip sample, keep last value

      // Jitter = absolute delta from previous sample
      if (_pingHistory.length > 0) {
        var delta = Math.abs(rtt - _pingHistory[_pingHistory.length - 1]);
        _jitterHistory.push(delta);
        if (_jitterHistory.length > 40) _jitterHistory.shift();
      }

      _pingHistory.push(rtt);
      if (_pingHistory.length > 60) _pingHistory.shift();
      _pingRawLast = rtt;
      lastPing = rtt;

      // Update HUD and header ping badge
      var col = rtt < 60 ? '#4ade80' : rtt < 120 ? '#fbbf24' : '#f87171';
      var el = document.getElementById('km-ping');
      if (el) { el.textContent = rtt + 'ms'; el.style.color = col; }
      var h  = document.getElementById('km-hud-ping');
      if (h)  { h.textContent  = rtt + 'ms'; h.style.color  = col; }

      // Refresh perf overlay if open
      if (isInstalled('perfMetrics') && perfEl) renderPerfMetrics();
    });
  }

  function getPingStats() {
    if (!_pingHistory.length) return { avg:0, min:0, max:0, last:0, jitter:0, stable:100 };
    var sorted = _pingHistory.slice().sort(function(a,b){return a-b;});
    var avg    = Math.round(_pingHistory.reduce(function(s,v){return s+v;},0) / _pingHistory.length);
    var min    = sorted[0];
    var max    = sorted[sorted.length-1];
    var jitter = _jitterHistory.length
      ? Math.round(_jitterHistory.reduce(function(s,v){return s+v;},0) / _jitterHistory.length)
      : 0;
    // Stability score 0-100: 100 = no jitter, 0 = extreme variance
    var stable = Math.max(0, Math.min(100, Math.round(100 - (jitter / 2))));
    return { avg:avg, min:min, max:max, last:_pingRawLast, jitter:jitter, stable:stable };
  }

  // Ping polling — ONLY starts when Performance Metrics plugin is installed
  // Not auto-fired on page load to avoid CORS errors on the game server URL
  var _pingInterval = null;

  function pingStart() {
    if (_pingInterval) return;
    _pingInterval = setInterval(_pingTick, 2000);
    // First measurement after 1s so plugin is fully mounted
    setTimeout(_pingTick, 1000);
    kmLog('Ping monitor started');
  }

  function pingStop() {
    if (_pingInterval) { clearInterval(_pingInterval); _pingInterval = null; }
    _pingHistory = []; _jitterHistory = [];
    // Clear HUD display
    var el = document.getElementById('km-ping'); if (el) { el.textContent = '—ms'; el.style.color = ''; }
    var h  = document.getElementById('km-hud-ping'); if (h) { h.textContent = '—ms'; h.style.color = ''; }
  }

  // Network Bridge keepalive — only active when explicitly toggled on in plugin settings
  // Uses the existing game matchEnd endpoint which we already have auth tokens for
  function netBridgeStart() {
    if (_netBridgeTimer) return;
    _netBridgeTimer = setInterval(function() {
      if (!isInstalled('perfMetrics')) { netBridgeStop(); return; }
      if ((getPluginSetting('perfMetrics','netbridge') || 'off') !== 'on') { netBridgeStop(); return; }
      // Keep the connection warm using a tiny fetch to a CORS-safe endpoint
      // We use the game's own domain with a no-cors mode — doesn't read response,
      // just keeps the TCP socket alive in the browser's connection pool
      try {
        fetch(_pingTarget, { method:'HEAD', mode:'no-cors', cache:'no-store' }).catch(function(){});
      } catch(e) {}
    }, 10000);
    kmLog('Network Bridge: keepalive active');
  }

  function netBridgeStop() {
    if (_netBridgeTimer) { clearInterval(_netBridgeTimer); _netBridgeTimer = null; }
  }

  // ─── TOAST SYSTEM (stacked, bottom-right) ─────────────────────────────────
  function showToast(msg,type) {
    var el=document.createElement('div');
    el.className='km-toast'+(type?' '+type:'');
    el.textContent=msg;
    var p = getPreset();
    el.style.background = p.bg || 'rgba(12,14,20,0.95)';
    el.style.color = p.text || '#fff';
    el.style.border = '1px solid rgba(255,255,255,0.15)';
    el.style.borderLeft = '3px solid ' + (p.accent || '#4ade80');
    el.style.boxShadow = '0 10px 28px rgba(0,0,0,0.35)';
    document.body.appendChild(el);
    if (type === 'permanent') {
      makeDraggable(el, el);
      el.style.left = '50%';
      el.style.right = 'auto';
      el.style.transform = 'translateX(-50%)';
      el.style.bottom = '12px';
      el.style.top = 'auto';
      el.style.width = 'min(92vw,1920px)';
      el.style.height = '200px';
      el.style.padding = '14px 18px';
      el.style.whiteSpace = 'normal';
    } else {
      var offset=72+(toastActive*52);
      el.style.bottom=offset+'px';
      toastActive++;
    }
    setTimeout(function(){el.classList.add('show');},10);
    if(type === 'permanent') {
      el.classList.add('permanent');
      el.style.left='50%';
      el.style.right='auto';
      el.style.transform='translateX(-50%)';
      el.style.width='min(92vw,1920px)';
      el.style.height='200px';
      el.style.padding='14px 18px';
      el.style.display='flex';
      el.style.alignItems='center';
      el.style.justifyContent='center';
      el.style.whiteSpace='normal';
      el.style.textAlign='center';
    }
    setTimeout(function(){
      if (type === 'permanent') return;
      el.classList.remove('show');
      setTimeout(function(){el.remove();toastActive=Math.max(0,toastActive-1);},250);
    },type === 'permanent'?999999999:2800);
  }

  // ─── OVERLAYS ─────────────────────────────────────────────────────────────
  function showGoalCelebration(isOpponent) {
    if (toggles.goalMsg === false) return;
    var old=document.getElementById('km-goal-banner'); if(old) old.remove();
    console.debug('[KeyMod] showGoalCelebration called', {isOpponent:isOpponent, goalMsg: toggles.goalMsg});

    var el=document.createElement('div');
    el.id='km-goal-banner';

    var nick = playerData ? (playerData.Nickname||'You') : 'You';
    var userName = profileCustom.bannerText || nick;
    var overlayText = customMsgs.goal || 'GOAL!';
    var subText = isOpponent ? streamerName(matchPlayers.opponent||'Opponent') : userName;

    if (isOpponent) {
      el.style.background='linear-gradient(135deg,#701a1a 0%,#9b1f1f 50%,#521010 100%)';
    } else {
      var bc=profileCustom.bannerColor||'#1e3a5f';
      el.style.background='linear-gradient(135deg,'+bc+'dd 0%,'+bc+'88 50%,'+bc+'dd 100%)';
    }

    var avatarHtml = '';
    if (!isOpponent && profileCustom.bannerUrl) {
      avatarHtml = '<div style="width:64px;height:64px;border-radius:12px;border:1px solid rgba(255,255,255,0.2);overflow:hidden;margin-right:14px;background:#111;flex-shrink:0;">'
        + '<img src="'+profileCustom.bannerUrl+'" style="width:100%;height:100%;object-fit:cover;">'
        + '</div>';
    }
    if (isOpponent) {
      avatarHtml = '<div style="width:56px;height:56px;border-radius:50%;background:rgba(255,255,255,0.12);display:flex;align-items:center;justify-content:center;font-size:22px;margin-right:12px;color:#fff;">⚔️</div>';
    }

    var desc = isOpponent ? 'Opponent scored! Stay focused.' : (profileCustom.bannerDesc || 'You scored! Keep pushing.');
    var descHtml = '<div style="font-size:12px;color:rgba(255,255,255,0.9);margin-top:6px;max-width:820px;line-height:1.3;letter-spacing:0.3px;">'+desc+'</div>';

    el.innerHTML = [
      '<div class="km-banner-bg-overlay"></div>',
      '<div class="km-banner-content" style="display:flex;align-items:center;justify-content:center;gap:12px;">',
        avatarHtml,
        '<div class="km-banner-text-wrap">',
          '<div class="km-banner-goal">'+(isOpponent?'GOAL':'⚽ '+overlayText)+'</div>',
          '<div class="km-banner-player">'+subText+'</div>',
          descHtml,
        '</div>',
      '</div>',
    ].join('');

    document.body.appendChild(el);
    setTimeout(function(){el.classList.add('show');},20);
    setTimeout(function(){el.classList.remove('show');},5000);
    setTimeout(function(){if(el.parentNode) el.remove();},5400);
  }

  function showEndMessage(result, oldMMR, newMMR, xpDiff) {
    if(!toggles.endMsg) return;
    var old=document.getElementById('km-end-flash'); if(old) old.remove();
    var el=document.createElement('div'); el.id='km-end-flash';
    var isWin = result==='Win';
    var col = isWin ? '#4ade80' : '#f87171';
    var label = isWin ? (customMsgs.win||'GG EZ') : (customMsgs.loss||'GG');
    var mmrLine = '';
    if (newMMR !== null && newMMR !== undefined && oldMMR !== null && oldMMR !== undefined) {
      var d = newMMR - oldMMR;
      mmrLine = '<div style="font-size:13px;margin-top:4px;color:'+(d>=0?'#4ade80':'#f87171')+'">'
        + (d>=0?'▲ +':'▼ ') + d + ' MMR'
        + '</div>';
    }
    var xpLine = xpDiff && xpDiff > 0
      ? '<div style="font-size:12px;margin-top:2px;color:#fbbf24">+'+xpDiff+' XP</div>'
      : '';
    var scoreLine = (matchScore.me > 0 || matchScore.opp > 0)
      ? '<div style="font-size:12px;margin-top:2px;color:rgba(255,255,255,0.4)">'
        + matchScore.me + ' – ' + matchScore.opp + '</div>'
      : '';
    el.innerHTML = '<div style="font-size:22px;font-weight:800;color:'+col+'">'+label+'</div>'
      + scoreLine + mmrLine + xpLine;
    el.style.textAlign = 'center';
    document.body.appendChild(el);
    setTimeout(function(){el.style.opacity='0';},4000);
    setTimeout(function(){if(el.parentNode)el.remove();},4700);
  }

  // ─── GOAL ITEMS (visual effects on player goal) ───────────────────────────
  function pluginGoalItem() {
    if (!isInstalled('goalItems')) return;
    var effect = getPluginSetting('goalItems', 'effect') || 'confetti';
    spawnGoalEffect(effect);
    if (typeof showGoalCelebration === 'function' && inMatch) {
      showGoalCelebration(false);
    }
  }

  function spawnGoalEffect(effect) {
    // Full-screen canvas explosion
    var id = 'km-goal-effect-' + Date.now();
    var cv = document.createElement('canvas');
    cv.id = id;
    cv.width  = window.innerWidth;
    cv.height = window.innerHeight;
    cv.style.cssText = 'position:fixed;inset:0;width:100%;height:100%;pointer-events:none;z-index:999997;';
    document.body.appendChild(cv);
    var ctx = cv.getContext('2d');
    var W = cv.width, H = cv.height;
    var CX = W / 2, CY = H / 2;
    var raf = null;

    // ── Effect configs ────────────────────────────────────────────────────
    var EFFECTS = {
      confetti: {
        init: function(p) {
          var angle = Math.random() * Math.PI * 2;
          var speed = 8 + Math.random() * 18;
          p.x = CX + (Math.random()-.5)*80; p.y = CY + (Math.random()-.5)*80;
          p.vx = Math.cos(angle)*speed; p.vy = Math.sin(angle)*speed - 6;
          p.w = 6+Math.random()*10; p.h = 4+Math.random()*6;
          p.rot = Math.random()*360; p.rotV = (Math.random()-.5)*14;
          p.color = ['#f87171','#fbbf24','#4ade80','#60a5fa','#e879f9','#fff','#fb923c'][Math.random()*7|0];
          p.life = 1;
        },
        draw: function(ctx,p) {
          ctx.save(); ctx.translate(p.x,p.y); ctx.rotate(p.rot*Math.PI/180);
          ctx.globalAlpha = p.life;
          ctx.fillStyle = p.color; ctx.fillRect(-p.w/2,-p.h/2,p.w,p.h);
          ctx.restore();
        },
        tick: function(p) { p.vx*=0.98; p.vy+=0.5; p.x+=p.vx; p.y+=p.vy; p.rot+=p.rotV; p.life-=0.008; }
      },
      fire: {
        init: function(p) {
          p.x = Math.random()*W; p.y = H + 20;
          p.vx = (Math.random()-.5)*6; p.vy = -(12+Math.random()*16);
          p.r = 18+Math.random()*36; p.life = 1;
          p.hue = 15+Math.random()*30;
        },
        draw: function(ctx,p) {
          var g = ctx.createRadialGradient(p.x,p.y,0,p.x,p.y,p.r);
          g.addColorStop(0,'hsla('+p.hue+',100%,70%,'+(p.life*0.9)+')');
          g.addColorStop(0.5,'hsla('+(p.hue-10)+',100%,50%,'+(p.life*0.5)+')');
          g.addColorStop(1,'transparent');
          ctx.fillStyle=g; ctx.beginPath(); ctx.arc(p.x,p.y,p.r,0,Math.PI*2); ctx.fill();
        },
        tick: function(p) { p.x+=p.vx; p.y+=p.vy; p.vy+=0.3; p.r*=0.97; p.life-=0.012; }
      },
      stars: {
        init: function(p) {
          var angle = Math.random()*Math.PI*2;
          var speed = 4+Math.random()*22;
          p.x=CX; p.y=CY; p.vx=Math.cos(angle)*speed; p.vy=Math.sin(angle)*speed;
          p.r=2+Math.random()*5; p.life=1; p.trail=[];
          p.color='hsla('+(40+Math.random()*40)+',100%,75%,1)';
        },
        draw: function(ctx,p) {
          // Draw trail
          for(var i=0;i<p.trail.length;i++){
            ctx.globalAlpha=(i/p.trail.length)*p.life*0.4;
            ctx.beginPath(); ctx.arc(p.trail[i].x,p.trail[i].y,p.r*(i/p.trail.length),0,Math.PI*2);
            ctx.fillStyle=p.color; ctx.fill();
          }
          ctx.globalAlpha=p.life;
          ctx.beginPath(); ctx.arc(p.x,p.y,p.r,0,Math.PI*2); ctx.fillStyle=p.color; ctx.fill();
          // Star glow
          var g=ctx.createRadialGradient(p.x,p.y,0,p.x,p.y,p.r*4);
          g.addColorStop(0,'hsla(50,100%,90%,'+(p.life*0.4)+')'); g.addColorStop(1,'transparent');
          ctx.fillStyle=g; ctx.beginPath(); ctx.arc(p.x,p.y,p.r*4,0,Math.PI*2); ctx.fill();
        },
        tick: function(p) { p.trail.push({x:p.x,y:p.y}); if(p.trail.length>8)p.trail.shift(); p.x+=p.vx; p.y+=p.vy; p.vy+=0.1; p.life-=0.009; }
      },
      lightning: {
        init: function(p) {
          p.x=Math.random()*W; p.y=0; p.targetY=H;
          p.segs=[]; p.life=1; p.width=1+Math.random()*3;
          p.color=Math.random()<0.5?'#faff00':'#a0ffff';
          // Generate zigzag path
          var cx=p.x, steps=20;
          for(var i=0;i<=steps;i++){
            p.segs.push({x:cx+(Math.random()-.5)*80,y:(H/steps)*i});
            cx+=(Math.random()-.5)*60;
          }
        },
        draw: function(ctx,p) {
          ctx.globalAlpha=p.life;
          ctx.strokeStyle=p.color; ctx.lineWidth=p.width;
          ctx.shadowBlur=20; ctx.shadowColor=p.color;
          ctx.beginPath();
          for(var i=0;i<p.segs.length;i++) i===0?ctx.moveTo(p.segs[i].x,p.segs[i].y):ctx.lineTo(p.segs[i].x,p.segs[i].y);
          ctx.stroke(); ctx.shadowBlur=0;
        },
        tick: function(p) { p.life-=0.04; }
      },
      money: {
        init: function(p) {
          p.x=Math.random()*W; p.y=-20;
          p.vx=(Math.random()-.5)*4; p.vy=4+Math.random()*8;
          p.rot=Math.random()*360; p.rotV=(Math.random()-.5)*8;
          p.size=20+Math.random()*20; p.life=1;
          p.symbol=['💵','💴','💶','💷','💰','$','€'][Math.random()*7|0];
        },
        draw: function(ctx,p) {
          ctx.globalAlpha=p.life; ctx.save();
          ctx.translate(p.x,p.y); ctx.rotate(p.rot*Math.PI/180);
          ctx.font=p.size+'px serif'; ctx.textAlign='center'; ctx.textBaseline='middle';
          ctx.fillText(p.symbol,0,0); ctx.restore();
        },
        tick: function(p) { p.x+=p.vx; p.y+=p.vy; p.vy+=0.1; p.rot+=p.rotV; if(p.y>H-40)p.life-=0.04; else p.life-=0.004; }
      },
      hearts: {
        init: function(p) {
          var angle=Math.random()*Math.PI*2, speed=5+Math.random()*20;
          p.x=CX+(Math.random()-.5)*100; p.y=CY+(Math.random()-.5)*100;
          p.vx=Math.cos(angle)*speed; p.vy=Math.sin(angle)*speed-4;
          p.size=14+Math.random()*28; p.life=1; p.rotV=(Math.random()-.5)*6;
          p.rot=Math.random()*360;
          p.color='hsl('+(340+Math.random()*30)+',90%,'+(60+Math.random()*20)+'%)';
        },
        draw: function(ctx,p) {
          ctx.globalAlpha=p.life; ctx.save(); ctx.translate(p.x,p.y); ctx.rotate(p.rot*Math.PI/180);
          var s=p.size/14;
          ctx.fillStyle=p.color;
          ctx.beginPath();
          ctx.moveTo(0,-s*3);
          ctx.bezierCurveTo(s*4,-s*8,s*10,-s*2,0,s*5);
          ctx.bezierCurveTo(-s*10,-s*2,-s*4,-s*8,0,-s*3);
          ctx.fill();
          ctx.restore();
        },
        tick: function(p) { p.x+=p.vx; p.y+=p.vy; p.vy+=0.25; p.vx*=0.99; p.rot+=p.rotV; p.life-=0.008; }
      },
      rage: {
        init: function(p) {
          var angle=Math.random()*Math.PI*2, speed=10+Math.random()*25;
          p.x=CX; p.y=CY; p.vx=Math.cos(angle)*speed; p.vy=Math.sin(angle)*speed;
          p.r=8+Math.random()*24; p.life=1;
          p.hue=0+Math.random()*30; p.flash=Math.random()<0.3;
        },
        draw: function(ctx,p) {
          if(p.flash){
            ctx.globalAlpha=p.life*0.6;
            ctx.fillStyle='rgba(255,0,0,0.15)'; ctx.fillRect(0,0,W,H);
          }
          var g=ctx.createRadialGradient(p.x,p.y,0,p.x,p.y,p.r);
          g.addColorStop(0,'hsla('+p.hue+',100%,70%,'+(p.life)+')');
          g.addColorStop(1,'transparent');
          ctx.globalAlpha=p.life; ctx.fillStyle=g;
          ctx.beginPath(); ctx.arc(p.x,p.y,p.r,0,Math.PI*2); ctx.fill();
        },
        tick: function(p) { p.x+=p.vx; p.y+=p.vy; p.vy+=0.6; p.vx*=0.96; p.r*=0.97; p.life-=0.013; }
      },
      galaxy: {
        init: function(p) {
          var angle=Math.random()*Math.PI*2, dist=20+Math.random()*Math.min(W,H)*0.5;
          p.x=CX+Math.cos(angle)*dist; p.y=CY+Math.sin(angle)*dist;
          var tangent=angle+Math.PI/2;
          var speed=1+Math.random()*4;
          p.vx=Math.cos(tangent)*speed-(p.x-CX)*0.005;
          p.vy=Math.sin(tangent)*speed-(p.y-CY)*0.005;
          p.r=1+Math.random()*3; p.life=1;
          p.hue=220+Math.random()*120; p.trail=[];
        },
        draw: function(ctx,p) {
          for(var i=0;i<p.trail.length;i++){
            ctx.globalAlpha=(i/p.trail.length)*p.life*0.5;
            ctx.beginPath(); ctx.arc(p.trail[i].x,p.trail[i].y,p.r*0.5,0,Math.PI*2);
            ctx.fillStyle='hsl('+p.hue+',80%,70%)'; ctx.fill();
          }
          ctx.globalAlpha=p.life;
          var g=ctx.createRadialGradient(p.x,p.y,0,p.x,p.y,p.r*2);
          g.addColorStop(0,'hsla('+p.hue+',90%,85%,'+p.life+')'); g.addColorStop(1,'transparent');
          ctx.fillStyle=g; ctx.beginPath(); ctx.arc(p.x,p.y,p.r*2,0,Math.PI*2); ctx.fill();
        },
        tick: function(p) { p.trail.push({x:p.x,y:p.y}); if(p.trail.length>12)p.trail.shift(); p.x+=p.vx; p.y+=p.vy; p.vx-=(p.x-CX)*0.0008; p.vy-=(p.y-CY)*0.0008; p.life-=0.007; }
      },
      sparkles: {
        init: function(p) {
          p.x=CX + (Math.random()-0.5)*200;
          p.y=CY + (Math.random()-0.5)*120;
          p.vx=(Math.random()-0.5)*4; p.vy=(Math.random()-0.5)*4;
          p.r=1+Math.random()*3; p.life=1; p.hue=Math.random()*360;
        },
        draw: function(ctx,p) {
          ctx.globalAlpha=p.life;
          ctx.fillStyle='hsla('+p.hue+',100%,80%,'+p.life+')';
          ctx.beginPath(); ctx.arc(p.x,p.y,p.r,0,Math.PI*2); ctx.fill();
        },
        tick: function(p) { p.x+=p.vx; p.y+=p.vy; p.vy+=0.05; p.r*=0.98; p.life-=0.01; }
      },
      shatter: {
        init: function(p) {
          var ang=Math.random()*Math.PI*2;
          var sp=3+Math.random()*8;
          p.x=CX; p.y=CY; p.vx=Math.cos(ang)*sp; p.vy=Math.sin(ang)*sp;
          p.w=4+Math.random()*8; p.h=4+Math.random()*8; p.rot=Math.random()*360; p.rotV=(Math.random()-0.5)*10;
          p.life=1; p.color=['#f87171','#fb923c','#facc15','#34d399','#60a5fa','#c084fc'][Math.random()*6|0];
        },
        draw: function(ctx,p) {
          ctx.save(); ctx.translate(p.x,p.y); ctx.rotate(p.rot*Math.PI/180);
          ctx.globalAlpha=p.life;
          ctx.fillStyle=p.color; ctx.fillRect(-p.w/2,-p.h/2,p.w,p.h);
          ctx.restore();
        },
        tick: function(p) { p.x+=p.vx; p.y+=p.vy; p.vy+=0.2; p.vx*=0.99; p.life-=0.015; p.rot+=p.rotV; }
      },
      neon: {
        init: function(p) {
          p.x=Math.random()*W; p.y=Math.random()*H;
          p.r=8+Math.random()*20; p.life=1; p.hue=150+Math.random()*120;
        },
        draw: function(ctx,p) {
          ctx.beginPath();
          var grd=ctx.createRadialGradient(p.x,p.y,0,p.x,p.y,p.r);
          grd.addColorStop(0,'hsla('+p.hue+',100%,90%,'+p.life+')');
          grd.addColorStop(0.4,'hsla('+p.hue+',100%,70%,'+p.life*0.7+')');
          grd.addColorStop(1,'transparent');
          ctx.fillStyle=grd; ctx.arc(p.x,p.y,p.r,0,Math.PI*2); ctx.fill();
        },
        tick: function(p) { p.life-=0.01; p.r*=0.99; }
      }
    };

    var cfg = EFFECTS[effect] || EFFECTS.confetti;

    // Spawn particles
    var count = {confetti:300,fire:120,stars:120,lightning:25,money:80,hearts:150,rage:160,galaxy:200,sparkles:180,shatter:220,neon:160}[effect]||200;
    var particles = [];
    for(var i=0;i<count;i++){
      var p={};
      cfg.init(p);
      particles.push(p);
    }

    // Staggered spawn for some effects
    var spawned = count;
    if(effect==='confetti'||effect==='money'||effect==='hearts'){
      particles=[]; spawned=0;
      var spawnInt=setInterval(function(){
        for(var j=0;j<12&&spawned<count;j++,spawned++){
          var p={}; cfg.init(p); particles.push(p);
        }
        if(spawned>=count) clearInterval(spawnInt);
      },30);
    }

    // Flash overlay for impact
    if(effect!=='lightning'){
      ctx.fillStyle='rgba(255,255,255,0.18)'; ctx.fillRect(0,0,W,H);
    }

    var frame=0;
    function animate(){
      raf=requestAnimationFrame(animate);
      frame++;
      // Fade-clear (trail effect for some)
      if(effect==='galaxy'||effect==='stars'){
        ctx.fillStyle='rgba(0,0,0,0.12)'; ctx.fillRect(0,0,W,H);
      } else {
        ctx.clearRect(0,0,W,H);
      }

      ctx.globalAlpha=1;
      particles=particles.filter(function(p){ return p.life>0; });
      particles.forEach(function(p){
        cfg.tick(p);
        ctx.save(); ctx.globalAlpha=1;
        cfg.draw(ctx,p);
        ctx.restore();
      });

      // Stop when all particles done or max time
      if((particles.length===0&&spawned>=count)||frame>300){
        cancelAnimationFrame(raf);
        cv.style.transition='opacity 0.5s';
        cv.style.opacity='0';
        setTimeout(function(){ if(cv.parentNode) cv.remove(); }, 550);
      }
    }
    animate();
  }

  // ─── LEADERBOARD ──────────────────────────────────────────────────────────
  function buildLeaderboard() {
    if(!toggles.leaderboard) return;
    var ex=document.getElementById('km-leaderboard'); if(ex) ex.remove();
    var el=document.createElement('div'); el.id='km-leaderboard';
    el.style.position = 'fixed';
    el.style.top = '12px';
    el.style.left = '12px';
    el.style.zIndex = '99996';
    el.style.cursor = 'move';
    el.style.minWidth = '260px';
    el.style.maxWidth = '500px';
    document.body.appendChild(el);
    makeDraggable(el, el);
    updateLeaderboard();
  }
  function updateLeaderboard() {
    var el=document.getElementById('km-leaderboard'); if(!el) return;
    if(!toggles.leaderboard){el.style.display='none';return;}
    el.style.display='block';
    pluginWinPredictorUpdate();
    el.style.display='block';
    var meName=streamerName(matchPlayers.me||(playerData?playerData.Nickname:'You'));
    var oppName=streamerName(matchPlayers.opponent||'Opponents');
    var meTeam=matchPlayers.myTeam||currentMode||'Team';
    var oppTeam=matchPlayers.oppTeam||'Opponents';
    var oppMMRStr=opponentMMR ? streamerMMR(opponentMMR) : '?';
    var myCol=matchScore.opp>matchScore.me?'#f87171':'#4ade80';
    var oppCol=matchScore.opp>matchScore.me?'#4ade80':'#f87171';
    var p = getPreset();
    var bg = p.tabBg || 'rgba(12,14,20,0.95)';
    var bd = p.border || 'rgba(255,255,255,0.12)';
    el.style.background = bg;
    el.style.border = '1px solid '+bd;
    el.style.boxShadow = '0 8px 24px rgba(0,0,0,0.4)';

    var modeLabel = currentMode ? currentMode.toUpperCase() : 'MATCH';
    var scoreText = '<div style="font-size:14px;font-weight:700;color:'+theme.accent+';margin-bottom:4px;">'+modeLabel+' SCORE</div>';
    el.innerHTML = '<div style="font-family:DM Mono,monospace;font-size:12px;color:'+p.text+';margin-bottom:6px;line-height:1.2;">'+scoreText
      +'<div style="display:flex;justify-content:space-between;align-items:center;gap:10px;">'
      +'<div style="min-width:120px;"><div style="font-size:10px;color:rgba(255,255,255,0.5);">'+meTeam+'</div><div style="font-size:14px;font-weight:700;color:'+myCol+';">'+meName+'</div></div>'
      +'<div style="font-size:22px;font-weight:800;color:'+theme.accent+';">'+matchScore.me+' – '+matchScore.opp+'</div>'
      +'<div style="min-width:120px;text-align:right;"><div style="font-size:10px;color:rgba(255,255,255,0.5);">'+oppTeam+'</div><div style="font-size:14px;font-weight:700;color:'+oppCol+';">'+oppName+'</div><div style="font-size:10px;color:rgba(255,255,255,0.45);">'+oppMMRStr+' MMR</div></div>'
      +'</div>';
  }
  function removeLeaderboard(){var el=document.getElementById('km-leaderboard');if(el)el.remove();}

  var welcomeShown=false;
  function showWelcomeBanner() {
    if(!playerData||welcomeShown) return; welcomeShown=true;
    var rank=playerData.ModesGlicko&&playerData.ModesGlicko['Competitive3v3']?getRank(playerData.ModesGlicko['Competitive3v3'].displayRating):null;
    var el=document.createElement('div'); el.id='km-welcome';
    el.innerHTML='Welcome back, <strong>'+(playerData.Nickname||'Player')+'</strong>'+(rank?' &nbsp;'+rank.emoji+' '+rank.name:'');
    document.body.appendChild(el);
    setTimeout(function(){el.style.opacity='0';},3000);
    setTimeout(function(){if(el.parentNode)el.remove();},3600);
  }

  function takeScreenshot() {
    var canvas=document.querySelector('canvas'); if(!canvas){showToast('No canvas found','warn');return;}
    try{var a=document.createElement('a');a.download='keymod-'+Date.now()+'.png';a.href=canvas.toDataURL('image/png');a.click();showToast('Screenshot saved!','ok');}
    catch(e){showToast('Screenshot failed','err');}
  }

  function markClip() {
    var secs=Math.round((Date.now()-session.startTime)/1000);
    var stamp=Math.floor(secs/60)+':'+String(secs%60).padStart(2,'0');
    clipMarks.push(stamp); showToast('Clip marked at '+stamp,'ok'); updateClipsBody();
  }

  // ─── COPY STATS ───────────────────────────────────────────────────────────
  function copyStats() {
    var nick=playerData?playerData.Nickname:'Unknown';
    var mmr3v3=playerData&&playerData.ModesGlicko&&playerData.ModesGlicko['Competitive3v3']?playerData.ModesGlicko['Competitive3v3'].displayRating:'?';
    var rank=getRank(mmr3v3);
    var acc=session.shots>0?Math.round((session.goals/session.shots)*100)+'%':'—';
    var tot=session.wins+session.losses;
    var wr=tot>0?Math.round((session.wins/tot)*100)+'%':'—';
    var lines=[
      '# '+nick+' — KeyMod Stats',
      '',
      '## 📊 Session',
      '**Matches:** '+session.matches+' | **W:** '+session.wins+' | **L:** '+session.losses+' | **WR:** '+wr,
      '**Goals:** '+session.goals+' | **Conceded:** '+session.conceded+' | **Diff:** '+(session.goals-session.conceded>=0?'+':'')+(session.goals-session.conceded),
      '**Shots:** '+session.shots+' | **Accuracy:** '+acc,
      '**Streak:** '+session.streak+' | **Best:** '+session.maxStreak+' | **Comebacks:** '+session.comebacks,
      '**XP Gained:** +'+session.xpGained,
      '',
      '## 🏆 Rank (3v3)',
      rank.emoji+' **'+rank.name+'** — '+mmr3v3+' MMR',
      '',
      '## ⏱ Session Time',
      Math.floor((Date.now()-session.startTime)/60000)+'m played',
      '',
      '*via KeyMod by @keydopz*',
    ];
    var text=lines.join('\n');
    try{
      navigator.clipboard.writeText(text).then(function(){showToast('Stats copied!','ok');}).catch(function(){fallbackCopy(text);});
    }catch(e){fallbackCopy(text);}
  }

  function fallbackCopy(text) {
    var ta=document.createElement('textarea'); ta.value=text;
    ta.style.position='fixed'; ta.style.opacity='0';
    document.body.appendChild(ta); ta.select();
    document.execCommand('copy'); ta.remove();
    showToast('Stats copied!','ok');
  }

  // ─── LOADING SCREEN ───────────────────────────────────────────────────────
  function buildLoadingScreen() {
    // Loading screen removed by user request.
    dismissed = true;
    return;
  }

  function showTikTokNotification(title, message) {
    var old = document.getElementById('km-tiktok-notif'); if (old) old.remove();
    var n = document.createElement('div'); n.id='km-tiktok-notif';
    n.style.cssText = 'position:fixed;bottom:16px;right:16px;z-index:999999;font-family:"DM Sans",sans-serif;pointer-events:auto;max-width:320px;border-radius:14px;background:rgba(20,20,28,0.96);border:1px solid rgba(255,255,255,0.08);box-shadow:0 12px 30px rgba(0,0,0,0.35);padding:10px 12px;backdrop-filter:blur(8px);';
    n.innerHTML = '<div style="display:flex;align-items:center;gap:8px;margin-bottom:4px;"><div style="width:34px;height:34px;border-radius:50%;background:#ff005e;color:#fff;display:flex;align-items:center;justify-content:center;font-size:16px;">✓</div><div><div style="font-size:13px;font-weight:700;color:#fff;">'+title+'</div><div style="font-size:11px;color:#d1d5db;">'+message+'</div></div></div>';
    document.body.appendChild(n);
    setTimeout(function(){if(n.parentNode)n.remove();},5000);
  }

  function dismiss() {
    if(dismissed) return; dismissed=true;
    var bar=document.getElementById('km-splash-bar'), st=document.getElementById('km-splash-status');
    var sub=document.getElementById('km-update-subtitle');
    if(bar){bar.style.transition='width 0.4s ease';bar.style.width='100%';}
    if(st) st.textContent='Ready.';
    if(sub) sub.textContent='Update complete.';
    // Wait longer before fading — gives Unity canvas time to render first frame
    // If we fade too fast, we reveal a black canvas underneath
    setTimeout(function(){
      var el=document.getElementById('km-splash'); if(!el) return;
      // Check if the game canvas has rendered (non-zero size indicates Unity is up)
      var cv = document.querySelector('canvas');
      var canvasReady = cv && cv.offsetWidth > 100;
      var delay = canvasReady ? 0 : 1500; // wait if canvas not ready yet
      setTimeout(function(){
        var el2=document.getElementById('km-splash'); if(!el2) return;
        el2.style.transition='opacity 1s ease';
        el2.style.opacity='0';
        setTimeout(function(){ var e=document.getElementById('km-splash'); if(e) e.remove(); },1050);
      }, delay);
    }, 600);
  }

  // ─── HUD ──────────────────────────────────────────────────────────────────
  // ── Always-on FPS counter ─────────────────────────────────────────────────
  var _hudFpsFrames = 0, _hudFpsLast = 0, _hudFps = 0;

  function _hudFpsTick(now) {
    _hudFpsFrames++;
    if (!_hudFpsLast) _hudFpsLast = now;
    var elapsed = now - _hudFpsLast;
    if (elapsed >= 500) {   // update every half-second for responsiveness
      _hudFps = Math.round(_hudFpsFrames * 1000 / elapsed);
      _hudFpsFrames = 0;
      _hudFpsLast = now;
      var el = document.getElementById('km-hud-fps');
      if (el) {
        el.textContent = _hudFps + ' fps';
        el.style.color = _hudFps >= 55 ? '#4ade80' : _hudFps >= 30 ? '#fbbf24' : '#f87171';
      }
      // Also feed perfMetrics if overlay is open
      perfFPS = _hudFps;
    }
    requestAnimationFrame(_hudFpsTick);
  }

  function buildHUD() {
    var el=document.createElement('div'); el.id='km-hud';
    el.innerHTML=[
      '<span id="km-hud-rank"></span>',
      '<span id="km-hud-fps" style="font-variant-numeric:tabular-nums">— fps</span>',
      '<span id="km-hud-sep" style="color:rgba(255,255,255,0.15);font-size:10px;margin:0 1px">|</span>',
      '<span id="km-hud-ping" style="font-variant-numeric:tabular-nums">—ms</span>',
      '<span id="km-hud-party" style="display:none;color:#f5c518;font-weight:600;font-size:11px;margin-left:4px"></span>',
    ].join('');
    document.body.appendChild(el);
    el.addEventListener('click',function(){toggleMenu();});
    // Start always-on FPS counter
    requestAnimationFrame(_hudFpsTick);
    // Start always-on ping (3s interval, derives from real game XHRs)
    pingStart();
  }

  function updateHUDRank() {
    var el=document.getElementById('km-hud-rank'); if(!el) return;
    if(playerData&&playerData.ModesGlicko&&playerData.ModesGlicko['Competitive3v3']) {
      var r=getRank(playerData.ModesGlicko['Competitive3v3'].displayRating);
      el.textContent=r.emoji; el.title=r.name;
      // Hot streak glow
      if(toggles.hotStreak&&session.streak>=3) el.style.filter='drop-shadow(0 0 6px #f97316)';
      else el.style.filter='none';
    }
  }

  // ─── MENU ─────────────────────────────────────────────────────────────────
  (function() {
    // Mount car leveler on boot if installed
  })();

  (function() {
    if (isInstalled('visualFX'))   setTimeout(pluginVFXMount, 200);
    // Themes disabled
  })();

  function buildMenu() {
    var el=document.createElement('div'); el.id='km-menu';
    // Prevent Unity canvas from stealing focus when interacting with menu
    el.addEventListener('mousedown', function(e){e.stopPropagation();});
    el.addEventListener('click', function(e){e.stopPropagation();});
    el.addEventListener('keydown', function(e){e.stopPropagation();});
    el.addEventListener('keyup', function(e){
      // Allow F2/backtick/1 to still close menu
      if(e.key!=='F2') e.stopPropagation();
    });
    el.innerHTML=[
      '<div id="km-header">',
        '<span id="km-title">KeyMod</span><span id="km-wip-badge">v0.1 beta</span>',
        '<div id="km-header-right">',
          '<button id="km-discord-btn" title="Copy party code for Discord">',
            '<span style="font-size:14px">🎮</span>',
            '<span id="km-discord-label">No Party</span>',
          '</button>',
          '<span id="km-ping">—ms</span>',
          '<button id="km-fullscreen-btn" title="Toggle fullscreen" style="background:none;border:none;color:rgba(255,255,255,0.4);font-size:12px;cursor:pointer;padding:4px 6px;transition:color 0.1s;">⛶</button>',
          // macOS traffic light trio — hidden unless macOS14 theme active (CSS shows them)
          '<div id="km-traffic-lights" style="display:none;align-items:center;gap:6px;margin-right:2px;">',
            '<button id="km-tl-red"    title="Close"    style="width:12px;height:12px;border-radius:50%;background:#ff5f57;border:0.5px solid rgba(0,0,0,0.15);padding:0;cursor:pointer;"></button>',
            '<button id="km-tl-yellow" title="Minimize" style="width:12px;height:12px;border-radius:50%;background:#ffbd2e;border:0.5px solid rgba(0,0,0,0.15);padding:0;cursor:pointer;"></button>',
            '<button id="km-tl-green"  title="Fullscreen" style="width:12px;height:12px;border-radius:50%;background:#28c840;border:0.5px solid rgba(0,0,0,0.15);padding:0;cursor:pointer;"></button>',
          '</div>',
          '<button id="km-close">&#x2715;</button>',
        '</div>',
      '</div>',
      '<div id="km-tip-bar"><span id="km-tip-text"></span><button id="km-tip-close">&#x2715;</button></div>',
      '<div id="km-tabs-wrap">',
        '<button id="km-tab-left" class="km-tab-arrow">&#8249;</button>',
        '<div id="km-tabs">',
          '<button class="kmt active" data-tab="ranks">Ranked</button>',
          '<button class="kmt" data-tab="ranked">Competitive</button>',
          '<button class="kmt" data-tab="session">Session</button>',
          '<button class="kmt" data-tab="profile">Profile</button>',
          '<button class="kmt" data-tab="history">History</button>',
          '<button class="kmt" data-tab="rivals">Rivals</button>',
          '<button class="kmt" data-tab="party">Party</button>',
          '<button class="kmt" data-tab="tools">Tools</button>',
          '<button class="kmt" data-tab="misc">Misc</button>',
          '<button class="kmt" data-tab="settings">Settings</button>',
          '<button class="kmt" data-tab="pluginstore">Plugin Store</button>',
          '<button class="kmt" data-tab="ownedplugins">Owned Plugins</button>',
          '<button class="kmt" data-tab="console">Console</button>',
          '<button class="kmt" data-tab="credits">Credits</button>',
        '</div>',
        '<button id="km-tab-right" class="km-tab-arrow">&#8250;</button>',
      '</div>',

      // RANKS
      '<div class="km-pane active" id="km-pane-ranks"><div id="km-ranks-body"><div class="km-empty">Waiting for login...</div></div></div>',

      // RANKED
      '<div class="km-pane" id="km-pane-ranked">',
        '<div class="km-section">Opponent MMR</div>',
        '<div id="km-opp-body"><div class="km-empty">Play a match</div></div>',
        '<div class="km-section">Competitive Split</div>',
        '<div id="km-split-body"><div class="km-empty">Login first</div></div>',
        '<div class="km-section">Daily Goal</div>',
        '<div class="km-stat-row"><span>Target Wins</span><input id="km-daily-goal" type="number" min="1" max="50" value="5" class="km-input"></div>',
        '<div id="km-daily-progress"></div>',
        '<div class="km-section">Heatmap</div>',
        '<div id="km-heatmap-body"></div>',
      '</div>',

      // SESSION
      '<div class="km-pane" id="km-pane-session">',
        '<div class="km-section">This Session</div>',
        '<div class="km-stat-row"><span>Goals Scored</span><span id="ss-goals">0</span></div>',
        '<div class="km-stat-row"><span>Goals Conceded</span><span id="ss-conceded">0</span></div>',
        '<div class="km-stat-row"><span>Goal Diff</span><span id="ss-diff">0</span></div>',
        '<div class="km-stat-row"><span>Shot Accuracy</span><span id="ss-acc">—</span></div>',
        '<div class="km-stat-row"><span>Shots</span><span id="ss-shots">0</span></div>',
        '<div class="km-stat-row"><span>Matches</span><span id="ss-matches">0</span></div>',
        '<div class="km-stat-row"><span>Wins</span><span id="ss-wins" class="col-green">0</span></div>',
        '<div class="km-stat-row"><span>Losses</span><span id="ss-losses" class="col-red">0</span></div>',
        '<div class="km-stat-row"><span>Win Rate</span><span id="ss-wr">—</span></div>',
        '<div class="km-stat-row"><span>Win Streak</span><span id="ss-streak">0</span></div>',
        '<div class="km-stat-row"><span>Best Streak</span><span id="ss-maxstreak">0</span></div>',
        '<div class="km-stat-row"><span>Comebacks</span><span id="ss-comebacks">0</span></div>',
        '<div class="km-stat-row"><span>Perfect Games</span><span id="ss-perfect">0</span></div>',
        '<div class="km-stat-row"><span>XP Gained</span><span id="ss-xp">0</span></div>',
        '<div class="km-stat-row"><span>Last Mode</span><span id="ss-mode">—</span></div>',
        '<div class="km-stat-row"><span>Time Played</span><span id="ss-time">0m</span></div>',
        '<div class="km-stat-row"><span>Longest Session</span><span id="ss-longest">0m</span></div>',
        '<button class="km-btn" id="km-copy-stats">📋 Copy Stats for Discord</button>',
      '</div>',

      // PROFILE
      '<div class="km-pane" id="km-pane-profile">',
        '<div class="km-section">Your Card</div>',
        '<div class="km-stat-row"><span>Banner Color</span><input type="color" id="prof-color" class="km-color" value="#1e3a5f"></div>',
        '<div class="km-stat-row"><span>Banner Title</span><input type="text" id="prof-title" class="km-input" placeholder="Your name on the banner"></div>',
        '<div class="km-stat-row" style="align-items:flex-start;"><span>Description</span><div style="flex:1;display:flex;gap:6px;"><textarea id="prof-desc" class="km-input" style="height:64px;resize:vertical;" placeholder="Custom profile description"></textarea><button id="btn-desc-osk" class="km-btn" style="height:30px;padding:0 10px;align-self:flex-start;">⌨</button></div></div>',
        '<div class="km-section" style="margin-top:4px">Goal Emoji</div>',
        '<div class="km-emoji-grid" id="km-emoji-grid"></div>',
        '<button class="km-btn" id="btn-save-profile">Save Banner Settings</button>',
        '<div id="km-banner-preview" class="km-banner-preview-box">',
          '<div style="font-size:11px;color:rgba(255,255,255,0.3)">Banner preview</div>',
        '</div>',
        '<div class="km-section" style="margin-top:8px">Stats</div>',
        '<div id="km-profile-body"><div class="km-empty">Login first</div></div>',
        '<div class="km-section">XP History</div>',
        '<div id="km-xp-history-body"><div class="km-empty">No XP data yet</div></div>',
        '<div class="km-section">Rank History</div>',
        '<div id="km-rank-history-body"><div class="km-empty">No rank changes yet</div></div>',
        '<button class="km-btn" id="km-export-card">📄 Export Profile Card</button>',
      '</div>',

      // HISTORY
      '<div class="km-pane" id="km-pane-history">',
        '<div class="km-section">Match History</div>',
        '<div id="km-history-body"></div>',
        '<button class="km-btn danger" id="km-clear-history">Clear History</button>',
      '</div>',

      // RIVALS
      '<div class="km-pane" id="km-pane-rivals">',
        '<div class="km-section">Rivals (3+ matches)</div>',
        '<div id="km-rivals-body"><div class="km-empty">Face opponents repeatedly to build rivals</div></div>',
        '<div class="km-section">Opponent Log</div>',
        '<div id="km-opp-log-body"><div class="km-empty">No opponents logged yet</div></div>',
      '</div>',

      // PARTY
      '<div class="km-pane" id="km-pane-party">',
        '<div class="km-section">Current Party</div>',
        '<div id="km-party-status"></div>',
        '<div class="km-section">Recent Codes</div>',
        '<div id="km-recent-codes"><div class="km-empty">No recent codes</div></div>',

      '</div>',

      // TOOLS
      '<div class="km-pane" id="km-pane-tools">',
        '<div class="km-section">Screenshot</div>',
        '<button class="km-btn" id="km-screenshot">📷 Save Screenshot</button>',
        '<div class="km-section">Clip Markers</div>',
        '<button class="km-btn" id="km-clip">▶ Mark Clip Timestamp</button>',
        '<div id="km-clips-body"></div>',
        '<button class="km-btn danger" id="km-clear-clips">Clear Clips</button>',
        '<div class="km-section">Peak MMR</div>',
        '<div id="km-peak-body"></div>',
        '<button class="km-btn danger" id="km-clear-peak">Reset Peak MMR</button>',
      '</div>',

      // MISC
      '<div class="km-pane" id="km-pane-misc">',
        '<div class="km-section">Feature Toggles</div>',
        '<div class="km-toggle-row"><label><input type="checkbox" id="tog-leaderboard"> Leaderboard overlay</label></div>',
        '<div class="km-toggle-row"><label><input type="checkbox" id="tog-goalMsg"> Goal message</label></div>',
        '<div class="km-toggle-row"><label><input type="checkbox" id="tog-endMsg"> End match message</label></div>',
        '<div class="km-toggle-row"><label><input type="checkbox" id="tog-oppMMR"> Opponent MMR</label></div>',
        '<div class="km-toggle-row"><label><input type="checkbox" id="tog-matchNotifs"> Match notifications</label></div>',
        '<div class="km-toggle-row"><label><input type="checkbox" id="tog-hotStreak"> Hot streak HUD glow</label></div>',
        '<div class="km-toggle-row"><label><input type="checkbox" id="tog-coldStreak"> Cold streak warning</label></div>',
        '<div class="km-toggle-row"><label><input type="checkbox" id="tog-rivalNotif"> Rival notifications</label></div>',
        '<div class="km-toggle-row"><label><input type="checkbox" id="tog-perfectNotif"> Perfect game alert</label></div>',
        '<div class="km-toggle-row"><label><input type="checkbox" id="tog-rankNotif"> Rank up/down alert</label></div>',
        '<div class="km-section">Custom Messages</div>',
        '<div class="km-stat-row"><span>Goal</span>'
          +'<select id="msg-goal" class="km-select" style="flex:1;max-width:160px">'          +'<option value="'+encodeURIComponent('GOAL!')+'"'+(customMsgs.goal==='GOAL!'?' selected':'')+'>GOAL!</option>'+'<option value="'+encodeURIComponent('LETS GO!')+'"'+(customMsgs.goal==='LETS GO!'?' selected':'')+'>LETS GO!</option>'+'<option value="'+encodeURIComponent('YEAH!')+'"'+(customMsgs.goal==='YEAH!'?' selected':'')+'>YEAH!</option>'+'<option value="'+encodeURIComponent('EASY!')+'"'+(customMsgs.goal==='EASY!'?' selected':'')+'>EASY!</option>'+'<option value="'+encodeURIComponent('GET REKT')+'"'+(customMsgs.goal==='GET REKT'?' selected':'')+'>GET REKT</option>'+'<option value="'+encodeURIComponent('SEND IT')+'"'+(customMsgs.goal==='SEND IT'?' selected':'')+'>SEND IT</option>'+'<option value="'+encodeURIComponent('BANGER')+'"'+(customMsgs.goal==='BANGER'?' selected':'')+'>BANGER</option>'+'<option value="'+encodeURIComponent('INSANE')+'"'+(customMsgs.goal==='INSANE'?' selected':'')+'>INSANE</option>'+'<option value="'+encodeURIComponent('NO CAP')+'"'+(customMsgs.goal==='NO CAP'?' selected':'')+'>NO CAP</option>'+'<option value="'+encodeURIComponent('COOKED')+'"'+(customMsgs.goal==='COOKED'?' selected':'')+'>COOKED</option>'          +'</select></div>',
        '<div class="km-stat-row"><span>Win</span>'
          +'<select id="msg-win" class="km-select" style="flex:1;max-width:160px">'          +'<option value="'+encodeURIComponent('GG EZ')+'"'+(customMsgs.win==='GG EZ'?' selected':'')+'>GG EZ</option>'+'<option value="'+encodeURIComponent('GG WP')+'"'+(customMsgs.win==='GG WP'?' selected':'')+'>GG WP</option>'+'<option value="'+encodeURIComponent('TOO EASY')+'"'+(customMsgs.win==='TOO EASY'?' selected':'')+'>TOO EASY</option>'+'<option value="'+encodeURIComponent('W')+'"'+(customMsgs.win==='W'?' selected':'')+'>W</option>'+'<option value="'+encodeURIComponent('SKILL GAP')+'"'+(customMsgs.win==='SKILL GAP'?' selected':'')+'>SKILL GAP</option>'+'<option value="'+encodeURIComponent('UNMATCHED')+'"'+(customMsgs.win==='UNMATCHED'?' selected':'')+'>UNMATCHED</option>'+'<option value="'+encodeURIComponent('STAY MAD')+'"'+(customMsgs.win==='STAY MAD'?' selected':'')+'>STAY MAD</option>'+'<option value="'+encodeURIComponent('DOMINANT')+'"'+(customMsgs.win==='DOMINANT'?' selected':'')+'>DOMINANT</option>'+'<option value="'+encodeURIComponent('CLEAN')+'"'+(customMsgs.win==='CLEAN'?' selected':'')+'>CLEAN</option>'+'<option value="'+encodeURIComponent('GOAT DIFF')+'"'+(customMsgs.win==='GOAT DIFF'?' selected':'')+'>GOAT DIFF</option>'          +'</select></div>',
        '<div class="km-stat-row"><span>Loss</span>'
          +'<select id="msg-loss" class="km-select" style="flex:1;max-width:160px">'          +'<option value="'+encodeURIComponent('GG')+'"'+(customMsgs.loss==='GG'?' selected':'')+'>GG</option>'+'<option value="'+encodeURIComponent('UNLUCKY')+'"'+(customMsgs.loss==='UNLUCKY'?' selected':'')+'>UNLUCKY</option>'+'<option value="'+encodeURIComponent('NEXT')+'"'+(customMsgs.loss==='NEXT'?' selected':'')+'>NEXT</option>'+'<option value="'+encodeURIComponent('RIGGED')+'"'+(customMsgs.loss==='RIGGED'?' selected':'')+'>RIGGED</option>'+'<option value="'+encodeURIComponent('DIFF')+'"'+(customMsgs.loss==='DIFF'?' selected':'')+'>DIFF</option>'+'<option value="'+encodeURIComponent('TOUCHING GRASS')+'"'+(customMsgs.loss==='TOUCHING GRASS'?' selected':'')+'>TOUCHING GRASS</option>'+'<option value="'+encodeURIComponent('GG WP')+'"'+(customMsgs.loss==='GG WP'?' selected':'')+'>GG WP</option>'+'<option value="'+encodeURIComponent('WAS COOKING')+'"'+(customMsgs.loss==='WAS COOKING'?' selected':'')+'>WAS COOKING</option>'+'<option value="'+encodeURIComponent('RUN IT BACK')+'"'+(customMsgs.loss==='RUN IT BACK'?' selected':'')+'>RUN IT BACK</option>'+'<option value="'+encodeURIComponent('HUMBLE')+'"'+(customMsgs.loss==='HUMBLE'?' selected':'')+'>HUMBLE</option>'          +'</select></div>',
        '<button class="km-btn" id="btn-save-msgs">Save Messages</button>',
      '</div>',

      // SETTINGS
      '<div class="km-pane" id="km-pane-settings">',
        '<div class="km-section">Themes &amp; Loading</div>',
        '<div style="font-size:12px;color:rgba(255,255,255,0.4);padding:10px 0 14px;line-height:1.7;">Themes and loading screens are managed via plugins. Install <strong>Themes</strong> or <strong>Loading Screen</strong> from the Plugin Store.</div>',
        '<div class="km-section">Connection</div>',
        '<div class="km-toggle-row"><label>'
          +'<input type="checkbox" id="tog-autoReload"'+(toggles.autoReload!==false?' checked':'')+'>'+'Auto-reload on disconnect</label></div>',
        '<div class="km-section">Menu</div>',
        '<div class="km-toggle-row"><label><input type="checkbox" id="set-skip-splash"'+(lsGet('km_skipSplash',false)?' checked':'')+'>Skip loading screen next time</label></div>',
      '</div>',
      // PLUGIN STORE
      '<div class="km-pane" id="km-pane-pluginstore">',
        '<div class="km-section">Plugin Store</div>',
        '<div style="position:relative;margin-bottom:6px;">',
          '<input type="text" id="km-plugin-search" class="km-input" placeholder="tap ⌨ to search..." style="width:100%;box-sizing:border-box;cursor:pointer;padding-right:34px;" readonly>',
          '<button id="km-osk-toggle" style="position:absolute;right:6px;top:50%;transform:translateY(-50%);background:none;border:none;cursor:pointer;font-size:16px;color:rgba(255,255,255,0.4);padding:0;line-height:1;" title="On-screen keyboard">⌨</button>',
        '</div>',
        '<div id="km-osk-wrap" style="display:none;margin-bottom:8px;"></div>',
        '<div id="km-plugin-store-grid"></div>',
      '</div>',

      // OWNED PLUGINS
      '<div class="km-pane" id="km-pane-ownedplugins">',
        '<div class="km-section">Installed Plugins</div>',
        '<div id="km-owned-plugins-list"><div class="km-empty">No plugins installed yet</div></div>',
      '</div>',

      // CONSOLE
      '<div class="km-pane" id="km-pane-console"><div id="km-console-body"></div></div>',

      // CREDITS
      '<div class="km-pane" id="km-pane-credits">',
        '<div style="padding:8px 0 20px;text-align:center;">',
          '<div style="font-size:22px;font-weight:700;color:#006eff;font-family:Consolas,monospace;letter-spacing:2px;margin-bottom:4px;">KeyMod</div>',
          '<div style="font-size:11px;color:rgba(255,255,255,0.3);margin-bottom:24px;">v0.1 — RocketGoal.io KeyMod Beta</div>',

          '<div class="km-section">Made by</div>',
          '<div style="font-size:16px;font-weight:700;color:#fff;margin:6px 0 2px;">@keydopz - @key.dop2 - @key.dop</div>',
          '<div style="font-size:11px;color:rgba(255,255,255,0.35);margin-bottom:20px;">TikTok · YouTube · Discord</div>',

          '<div class="km-section">Built with</div>',
          '<div class="km-stat-row"><span>Engine</span><span>Unity WebGL (Emscripten)</span></div>',
          '<div class="km-stat-row"><span>Networking</span><span>Firebase + Photon</span></div>',
          '<div class="km-stat-row"><span>Platform</span><span>Tampermonkey userscript</span></div>',
          '<div class="km-stat-row"><span>Fonts</span><span>DM Mono, JetBrains Mono</span></div>',

          '<div class="km-section">Special thanks</div>',
          '<div style="font-size:12px;color:rgba(255,255,255,0.4);line-height:1.8;margin-bottom:20px;">',
            'The RocketGoal.io development team<br>',
            'The RocketGoal.io discord members<br>',
            'BakkesMod for the plugin inspiration<br>',
            'The RocketGoal.io community',
          '</div>',

          '<div style="font-size:10px;color:rgba(255,255,255,0.15);margin-top:8px;">',
            'KeyMod is an independent fan project.<br>Not affiliated with Psyonix or Epic Games or RocketGoal.',
          '</div>',
        '</div>',
      '</div>',
    ].join('');

    document.body.appendChild(el);
    bindMenuEvents(el);
    setTimeout(function() { attachOSKToMenu(el); }, 100);

    // Initial renders
    consoleLogs.forEach(function(e){appendConsoleRow(e);});
    if(playerData) refreshRanksTab();
    updateHistoryTab(); updatePeakBody(); updateClipsBody();
    updateSessionTab(); updateDailyProgress(); updateHeatmap();
    updateProfileTab(); updateRivalsTab();
    makeDraggable(el, el.querySelector('#km-header'));

    setInterval(function(){
      var t=document.getElementById('ss-time'), ls=document.getElementById('ss-longest');
      var mins=Math.floor((Date.now()-session.startTime)/60000);
      if(t) t.textContent=mins+'m';
      if(ls) ls.textContent=Math.max(mins,session.longestSession)+'m';
      updateLongestSession();
    },15000);
  }

  function bindMenuEvents(el) {
    // Tabs
    el.querySelectorAll('.kmt').forEach(function(btn){
      btn.addEventListener('click',function(){
        el.querySelectorAll('.kmt').forEach(function(b){b.classList.remove('active');});
        el.querySelectorAll('.km-pane').forEach(function(p){p.classList.remove('active');});
        btn.classList.add('active'); activeTab=btn.dataset.tab;
        var paneEl = document.getElementById('km-pane-'+activeTab); if(paneEl) paneEl.classList.add('active');
        kmLog('Tab opened: ' + activeTab);
        if(activeTab==='console') scrollConsole();
        if(activeTab==='ranked') {updateRankedTab();updateDailyProgress();updateHeatmap();}
        if(activeTab==='profile') updateProfileTab();
        if(activeTab==='rivals') updateRivalsTab();
        if(activeTab==='party') updatePartyTab();
        if(activeTab==='pluginstore') renderPluginStore();
        if(activeTab==='ownedplugins') renderOwnedPlugins();
        if(activeTab==='credits') {}  // static content, no render needed
        if(activeTab==='tools') {updatePeakBody();updateClipsBody();}
      });
    });

    // Arrows
    var tabsEl=el.querySelector('#km-tabs');
    el.querySelector('#km-tab-left').addEventListener('click',function(){tabsEl.scrollBy({left:-90,behavior:'smooth'});});
    el.querySelector('#km-tab-right').addEventListener('click',function(){tabsEl.scrollBy({left:90,behavior:'smooth'});});

    el.querySelector('#km-close').addEventListener('click',function(){toggleMenu(false);});
    // macOS traffic light wiring
    (function() {
      var tlRed    = el.querySelector('#km-tl-red');
      var tlYellow = el.querySelector('#km-tl-yellow');
      var tlGreen  = el.querySelector('#km-tl-green');
      if (tlRed)    tlRed.addEventListener('click',    function(){ toggleMenu(false); });
      if (tlYellow) tlYellow.addEventListener('click', function(){
        // Minimize = collapse to HUD only
        var menu = document.getElementById('km-menu');
        if (menu) { menu.style.opacity = menu.style.opacity === '0' ? '1' : '0'; }
      });
      if (tlGreen)  tlGreen.addEventListener('click',  function(){
        if (!document.fullscreenElement) {
          document.documentElement.requestFullscreen && document.documentElement.requestFullscreen().catch(function(){});
        } else {
          document.exitFullscreen && document.exitFullscreen();
        }
      });
    })();
    // Fullscreen toggle
    (function() {
      var fsBtn = el.querySelector('#km-fullscreen-btn');
      if (!fsBtn) return;
      function updateFsIcon() {
        fsBtn.textContent = document.fullscreenElement ? '⛶' : '⛶';
        fsBtn.title = document.fullscreenElement ? 'Exit fullscreen' : 'Toggle fullscreen';
      }
      fsBtn.addEventListener('click', function() {
        if (!document.fullscreenElement) {
          document.documentElement.requestFullscreen && document.documentElement.requestFullscreen().catch(function(){});
        } else {
          document.exitFullscreen && document.exitFullscreen();
        }
      });
      document.addEventListener('fullscreenchange', updateFsIcon);
    })();

    // Tip bar
    var tipText = el.querySelector('#km-tip-text');
    var tipClose = el.querySelector('#km-tip-close');
    if(tipText) {
      menuTipIdx = (menuTipIdx + 1) % TIPS.length;
      lsSet('menuTipIdx5', menuTipIdx);
      tipText.textContent = TIPS[menuTipIdx];
    }
    if(tipClose) tipClose.addEventListener('click', function() {
      var bar = document.getElementById('km-tip-bar');
      if(bar) bar.style.display = 'none';
    });

    // Discord party button in header
    var discordBtn = el.querySelector('#km-discord-btn');
    if(discordBtn) discordBtn.addEventListener('click', function() {
      if(currentPartyCode) {
        var nick = playerData ? playerData.Nickname : 'me';
        var msg = 'Join my RocketGoal.io party! Code: ' + currentPartyCode + ' (via KeyMod by @keydopz)';
        navigator.clipboard.writeText(msg).catch(function(){fallbackCopy(msg);});
        showToast('Party invite copied!', 'ok');
        kmLog('Discord party invite copied from header');
      } else {
        showToast('No active party', 'warn');
      }
    });

    el.querySelector('#km-discord-btn').addEventListener('click', function() {
      if (!currentPartyCode) {
        showToast('No party code yet — create a party first', 'warn');
        return;
      }
      var nick = playerData ? (playerData.Nickname || 'me') : 'me';
      var msg = '🎮 Join my RocketGoal.io party!\nCode: **' + currentPartyCode + '**\n*(invite from ' + nick + ' via KeyMod)*';
      try {
        navigator.clipboard.writeText(msg).then(function() {
          showToast('📋 Party code copied for Discord!', 'ok');
          kmLog('Party code copied: ' + currentPartyCode);
        }).catch(function() { fallbackCopy(msg); showToast('📋 Copied!', 'ok'); });
      } catch(e) { fallbackCopy(msg); showToast('📋 Copied!', 'ok'); }
    });
    el.querySelector('#km-screenshot').addEventListener('click',function(){kmLog('Screenshot taken');takeScreenshot();});
    el.querySelector('#km-clip').addEventListener('click',function(){kmLog('Clip timestamp marked');markClip();});
    el.querySelector('#km-copy-stats').addEventListener('click',function(){kmLog('Stats copied to clipboard');copyStats();});
    el.querySelector('#km-clear-clips').addEventListener('click',function(){clipMarks=[];updateClipsBody();showToast('Clips cleared');kmLog('Clip marks cleared');});
    el.querySelector('#km-clear-history').addEventListener('click',function(){matchHistory=[];lsSet('matchHistory5',[]);updateHistoryTab();showToast('History cleared');kmLog('Match history cleared');});
    el.querySelector('#km-clear-peak').addEventListener('click',function(){peakMMR={};lsSet('peakMMR5',{});updatePeakBody();showToast('Peak MMR reset');kmLog('Peak MMR reset');});
    el.querySelector('#km-export-card').addEventListener('click',exportProfileCard);

    // Party tab

    // Profile card settings
    var profColor  = el.querySelector('#prof-color');
    var profText   = el.querySelector('#prof-text');
    if(profColor) profColor.value = profileCustom.bannerColor || '#1e3a5f';
    // Emoji grid
    var emojiGrid = el.querySelector('#km-emoji-grid');
    if (emojiGrid) {
      var emojis = ['⚽','🔥','💥','⚡','🎯','💫','🌟','👑','🏆','💀','🎮','🚀','❄️','🌊','☄️','🎉'];
      emojiGrid.innerHTML = emojis.map(function(e) {
        var active = (profileCustom.bannerEmoji||'⚽') === e ? ' active' : '';
        return '<button class="km-emoji-btn'+active+'" data-emoji="'+e+'">'+e+'</button>';
      }).join('');
      emojiGrid.querySelectorAll('.km-emoji-btn').forEach(function(btn) {
        btn.addEventListener('click', function() {
          emojiGrid.querySelectorAll('.km-emoji-btn').forEach(function(b){b.classList.remove('active');});
          btn.classList.add('active');
        });
      });
    }

    el.querySelector('#btn-save-profile').addEventListener('click', function() {
      profileCustom.bannerColor = profColor ? profColor.value : '#1e3a5f';
      profileCustom.bannerText = el.querySelector('#prof-title') ? el.querySelector('#prof-title').value.trim() : '';
      profileCustom.bannerDesc = el.querySelector('#prof-desc') ? el.querySelector('#prof-desc').value.trim() : '';
      var selEmoji = el.querySelector('.km-emoji-btn.active');
      if (selEmoji) profileCustom.bannerEmoji = selEmoji.dataset.emoji;
      lsSet('profileCustom5', profileCustom);
      kmLog('Profile card saved');
      showToast('Profile card saved!', 'ok');
      updateBannerPreview();
    });
    var btnDescOSK = el.querySelector('#btn-desc-osk');
    if (btnDescOSK) {
      btnDescOSK.addEventListener('click', function() {
        var descInput = el.querySelector('#prof-desc');
        if (!descInput) return;
        var text = prompt('Type description:', descInput.value || '');
        if (text !== null) {
          descInput.value = text;
          profileCustom.bannerDesc = text;
          lsSet('profileCustom5', profileCustom);
          updateBannerPreview();
        }
      });
    }
    updateBannerPreview();

    // Goal sound is disabled - no event needed.

    // Auto-reload toggle (still in settings pane)
    var arCb = el.querySelector('#tog-autoReload');
    if (arCb) {
      arCb.addEventListener('change', function() {
        toggles.autoReload = arCb.checked;
        lsSet('toggles5', toggles);
      });
    }

    // Toggles
    var togMap={
      'tog-leaderboard':'leaderboard','tog-goalMsg':'goalMsg','tog-endMsg':'endMsg',
      'tog-oppMMR':'oppMMR','tog-matchNotifs':'matchNotifs','tog-hotStreak':'hotStreak',
      'tog-coldStreak':'coldStreak','tog-rivalNotif':'rivalNotif',
      'tog-perfectNotif':'perfectNotif','tog-rankNotif':'rankNotif',
    };
    Object.keys(togMap).forEach(function(id){
      var key=togMap[id], cb=el.querySelector('#'+id); if(!cb) return;
      cb.checked=toggles[key];
      cb.addEventListener('change',function(){
        toggles[key]=cb.checked; lsSet('toggles5',toggles);
        kmLog(key + ' ' + (cb.checked ? 'enabled' : 'disabled'));
        if(key==='leaderboard'){if(!cb.checked) removeLeaderboard(); else if(matchPlayers.me&&matchPlayers.opponent) buildLeaderboard();}
      });
    });

    // Custom messages
    var mg=el.querySelector('#msg-goal'), mw=el.querySelector('#msg-win'), ml=el.querySelector('#msg-loss');
    if(mg) mg.value=customMsgs.goal; if(mw) mw.value=customMsgs.win; if(ml) ml.value=customMsgs.loss;
    el.querySelector('#btn-save-msgs').addEventListener('click',function(){
      customMsgs.goal=mg.value||'GOAL!'; customMsgs.win=mw.value||'GG EZ'; customMsgs.loss=ml.value||'GG';
      // selects return the preset string directly — no decode needed
      lsSet('customMsgs5',customMsgs); showToast('Messages saved!','ok');
      kmLog('Custom messages saved — Goal: "' + customMsgs.goal + '" Win: "' + customMsgs.win + '" Loss: "' + customMsgs.loss + '"');
    });

    // Daily goal
    el.querySelector('#km-daily-goal').addEventListener('change',function(){
      daily.goal=parseInt(this.value)||5; lsSet('dailyGoal5',daily.goal); lsSet('daily5',daily); updateDailyProgress();
    });
    el.querySelector('#km-daily-goal').value=daily.goal;

    // Settings — theme/loading moved to plugins, only wire remaining settings

    // Fix: wire the skip splash checkbox
    var skipSplashCb = el.querySelector('#set-skip-splash');
    if (skipSplashCb) {
      skipSplashCb.checked = lsGet('km_skipSplash', false);
      skipSplashCb.addEventListener('change', function() {
        lsSet('km_skipSplash', skipSplashCb.checked);
        showToast(skipSplashCb.checked ? 'Loading screen disabled' : 'Loading screen enabled', 'ok');
        kmLog('skipSplash set to: ' + skipSplashCb.checked);
      });
    }
  }

  function toggleMenu(force) {
    var el=document.getElementById('km-menu'); if(!el) return;
    menuOpen=force!==undefined?force:!menuOpen;
    el.style.display=menuOpen?'block':'none';
    if(menuOpen&&activeTab==='console') scrollConsole();
    kmLog('Menu ' + (menuOpen ? 'opened' : 'closed'));
  }

  // ─── TAB RENDERERS ────────────────────────────────────────────────────────
  function refreshRanksTab() {
    var el=document.getElementById('km-ranks-body'); if(!el||!playerData) return;
    var d=playerData;
    var skin=SKINS[d.EquippedSkinId||'body.0']||'Unknown';
    var modes=[{label:'1v1',key:'Competitive1v1'},{label:'2v2',key:'Competitive2v2'},{label:'3v3',key:'Competitive3v3'},{label:'Casual',key:'Casual'}];
    var a=theme.accent;
    var html='<div class="km-player-card">'
      +'<div class="km-player-name">'+(d.Nickname||'—')+'</div>'
      +'<div class="km-player-xp">XP '+(d.AccountXp||0).toLocaleString()+'</div>'
      +'</div>'
      +'<div class="km-car-row"><span class="km-car-label">🚗 Current Car</span><span class="km-car-name">'+skin+'</span></div>'
      +'<div class="km-divider"></div>';
    modes.forEach(function(m){
      var g=d.ModesGlicko&&d.ModesGlicko[m.key];
      var s=d.ModesData&&d.ModesData[m.key];
      if(!g||!s) return;
      var mmr=g.displayRating, rank=getRank(mmr), prog=getRankProgress(mmr);
      var tot=s.wins+s.loses, wr=tot>0?Math.round((s.wins/tot)*100):0;
      var peak=peakMMR[m.key]?'<span class="km-peak">peak '+peakMMR[m.key]+'</span>':'';
      html+='<div class="km-rank-row">'
        +'<div class="km-rank-icon">'+rank.emoji+'</div>'
        +'<div class="km-rank-info">'
          +'<div class="km-rank-name" style="color:'+rank.color+'">'+rank.name+'</div>'
          +'<div class="km-rank-mode">'+m.label+'</div>'
          +'<div class="km-prog-wrap"><div class="km-prog-bar" style="width:'+prog+'%;background:'+rank.color+'"></div></div>'
        +'</div>'
        +'<div class="km-rank-right">'
          +'<div class="km-rank-mmr">'+mmr+' '+peak+'</div>'
          +'<div class="km-rank-wl">'+s.wins+'W &middot; '+s.loses+'L &middot; '+wr+'%</div>'
        +'</div>'
        +'</div>';
    });
    el.innerHTML=html;
    updateHUDRank();
  }

  function updateRankedTab() {
    var oppEl=document.getElementById('km-opp-body');
    if(oppEl){
      if(opponentMMR){
        var r=getRank(opponentMMR);
        var oppName=streamerName(matchPlayers.opponent||'Opponent');
        oppEl.innerHTML='<div class="km-rank-row"><div class="km-rank-icon">'+r.emoji+'</div>'
          +'<div class="km-rank-info"><div class="km-rank-name" style="color:'+r.color+'">'+r.name+'</div><div class="km-rank-mode">'+oppName+'</div></div>'
          +'<div class="km-rank-right"><div class="km-rank-mmr">'+streamerMMR(opponentMMR)+'</div></div></div>';
      } else if(playerData&&playerData.ModesGlicko){
        var mkey2=currentMode==='1v1'?'Competitive1v1':currentMode==='2v2'?'Competitive2v2':currentMode==='Casual'?'Casual':'Competitive3v3';
        var mg2=playerData.ModesGlicko[mkey2]||playerData.ModesGlicko['Competitive3v3'];
        var my2=mg2?mg2.displayRating:1000;
        var lo2=trophyRange.lo!==null?trophyRange.lo:my2-350;
        var hi2=trophyRange.hi!==null?trophyRange.hi:my2+350;
        oppEl.innerHTML='<div class="km-opp-range"><div class="km-opp-label">Expected Opponent Range</div>'
          +'<div class="km-opp-vals" style="font-size:18px;font-weight:700;margin:4px 0">'+lo2+' – '+hi2+'</div>'
          +'<div class="km-opp-ranks">'+getRank(lo2).emoji+' '+getRank(lo2).name+' → '+getRank(hi2).emoji+' '+getRank(hi2).name+'</div>'
          +(trophyRange.lo!==null?'<div style="font-size:10px;color:#fbbf24;margin-top:6px">🏆 Live matchmaking window</div>':'')+'</div>';
      } else {
        oppEl.innerHTML='<div class="km-empty">Play a match to see opponent MMR</div>';
      }
    }
    var splitEl=document.getElementById('km-split-body');
    if(splitEl&&playerData&&playerData.ModesData){
      var html='';
      [{label:'1v1',key:'Competitive1v1'},{label:'2v2',key:'Competitive2v2'},{label:'3v3',key:'Competitive3v3'}].forEach(function(m){
        var s=playerData.ModesData[m.key]; if(!s) return;
        var tot=s.wins+s.loses, wr=tot>0?Math.round((s.wins/tot)*100):0;
        html+='<div class="km-stat-row"><span>'+m.label+'</span>'
          +'<span><span class="col-green">'+s.wins+'W</span> / <span class="col-red">'+s.loses+'L</span> &nbsp;'+wr+'%</span></div>';
      });
      splitEl.innerHTML=html||'<div class="km-empty">No data</div>';
    }
  }

  function updateDailyProgress() {
    var el=document.getElementById('km-daily-progress'); if(!el) return;
    var pct=Math.min(Math.round((daily.wins/daily.goal)*100),100);
    el.innerHTML='<div style="margin-top:6px;font-size:11px;color:rgba(255,255,255,0.4);margin-bottom:4px">'+daily.wins+' / '+daily.goal+' wins today</div>'
      +'<div class="km-prog-wrap" style="height:6px"><div class="km-prog-bar" style="width:'+pct+'%;background:'+theme.accent+'"></div></div>'
      +(pct>=100?'<div style="font-size:11px;color:#4ade80;margin-top:4px">✓ Daily goal reached!</div>':'');
  }

  function updateHeatmap() {
    var el=document.getElementById('km-heatmap-body'); if(!el) return;
    if(!Object.keys(heatmap).length){el.innerHTML='<div class="km-empty">Play matches to build heatmap</div>';return;}
    var html='<div style="display:flex;flex-wrap:wrap;gap:4px;margin-top:4px">';
    for(var h=0;h<24;h++){
      var d=heatmap[h], intensity=d?Math.min(d.played*20,100):0;
      var wr=d&&d.played>0?Math.round((d.wins/d.played)*100):0;
      var bg=d?'rgba(59,130,246,'+(intensity/100).toFixed(2)+')':'rgba(255,255,255,0.04)';
      html+='<div title="'+h+':00 — '+(d?d.played+' matches, '+wr+'% WR':'0 matches')+'" '
        +'style="width:24px;height:24px;border-radius:3px;background:'+bg+';font-size:8px;color:rgba(255,255,255,0.4);display:flex;align-items:center;justify-content:center">'+h+'</div>';
    }
    html+='</div>';
    el.innerHTML=html;
  }

  function updateSessionTab() {
    var diff=session.goals-session.conceded;
    var acc=session.shots>0?Math.round((session.goals/session.shots)*100)+'%':'—';
    var ids={'ss-goals':session.goals,'ss-conceded':session.conceded,
      'ss-diff':(diff>0?'+':'')+diff,'ss-shots':session.shots,'ss-acc':acc,
      'ss-matches':session.matches,'ss-wins':session.wins,'ss-losses':session.losses,
      'ss-streak':(session.streak>=3?'🔥 ':'')+session.streak,
      'ss-maxstreak':session.maxStreak,'ss-comebacks':session.comebacks,
      'ss-perfect':session.perfectGames,'ss-xp':'+'+session.xpGained,
      'ss-mode':session.lastMode||'—',
    };
    Object.keys(ids).forEach(function(id){var el=document.getElementById(id);if(el)el.textContent=ids[id];});
    var wr=document.getElementById('ss-wr');
    if(wr){var tot=session.wins+session.losses;wr.textContent=tot>0?Math.round((session.wins/tot)*100)+'%':'—';}
    var diffEl=document.getElementById('ss-diff');
    if(diffEl) diffEl.style.color=diff>0?'#4ade80':diff<0?'#f87171':'';
  }

  function updateProfileTab() {
    // Profile card
    var pb=document.getElementById('km-profile-body'); if(pb&&playerData){
      var d=playerData;
      var mmr3=d.ModesGlicko&&d.ModesGlicko['Competitive3v3']?d.ModesGlicko['Competitive3v3'].displayRating:0;
      var rank3=getRank(mmr3);
      var totalMatches=Object.values(d.ModesData||{}).reduce(function(acc,m){return acc+(m.matchesPlayed||0);},0);
      var totalWins=Object.values(d.ModesData||{}).reduce(function(acc,m){return acc+(m.wins||0);},0);
      var totalLosses=Object.values(d.ModesData||{}).reduce(function(acc,m){return acc+(m.loses||0);},0);
      var overallWR=totalMatches>0?Math.round((totalWins/totalMatches)*100):0;
      var mostPlayed=null, mostPlayed_count=0;
      [{key:'Competitive1v1',label:'1v1'},{key:'Competitive2v2',label:'2v2'},{key:'Competitive3v3',label:'3v3'},{key:'Casual',label:'Casual'}].forEach(function(m){
        var s=d.ModesData&&d.ModesData[m.key]; if(!s) return;
        if(s.matchesPlayed>mostPlayed_count){mostPlayed_count=s.matchesPlayed;mostPlayed=m.label;}
      });
      // Night owl / early bird
      var peakHour=null, peakCount=0;
      Object.keys(heatmap).forEach(function(h){if(heatmap[h].played>peakCount){peakCount=heatmap[h].played;peakHour=parseInt(h);}});
      var playstyle=peakHour===null?'—':peakHour>=22||peakHour<5?'🦉 Night Owl':peakHour<12?'🐦 Early Bird':'☀️ Daytime Player';
      pb.innerHTML='<div class="km-player-card">'
        +'<div class="km-player-name">'+(d.Nickname||'—')+'</div>'
        +'<div class="km-player-xp" style="font-size:12px">'+rank3.emoji+' '+rank3.name+'</div>'
        +'</div>'
        +'<div class="km-divider"></div>'
        +'<div class="km-stat-row"><span>Total Matches</span><span>'+totalMatches+'</span></div>'
        +'<div class="km-stat-row"><span>Overall W/L</span><span><span class="col-green">'+totalWins+'W</span> / <span class="col-red">'+totalLosses+'L</span></span></div>'
        +'<div class="km-stat-row"><span>Overall WR</span><span>'+overallWR+'%</span></div>'
        +'<div class="km-stat-row"><span>Most Played</span><span>'+(mostPlayed||'—')+'</span></div>'
        +'<div class="km-stat-row"><span>Play Style</span><span>'+playstyle+'</span></div>'
        +'<div class="km-stat-row"><span>Peak MMR 3v3</span><span style="color:'+theme.accent+'">'+(peakMMR['Competitive3v3']||'—')+'</span></div>'
        +'<div class="km-stat-row"><span>Session XP</span><span style="color:#fbbf24">+'+session.xpGained+'</span></div>'
        +'<div class="km-stat-row"><span>Account XP</span><span>'+(d.AccountXp||0).toLocaleString()+'</span></div>'
        +'<div class="km-stat-row"><span>Session Started</span><span>'+new Date(session.startTime).toLocaleTimeString()+'</span></div>';
    }
    // XP history
    var xpEl=document.getElementById('km-xp-history-body'); if(xpEl){
      if(!xpHistory.length){xpEl.innerHTML='<div class="km-empty">No XP data yet</div>';}
      else{xpEl.innerHTML=xpHistory.slice(0,10).map(function(x){
        return '<div class="km-history-row"><span style="color:#fbbf24;font-weight:700">+'+x.gained+' XP</span>'
          +'<span class="km-history-mmr">Total: '+x.total.toLocaleString()+'</span>'
          +'<span class="km-history-time">'+x.time+'</span></div>';
      }).join('');}
    }
    // Rank history
    var rhEl=document.getElementById('km-rank-history-body'); if(rhEl){
      if(!rankHistory.length){rhEl.innerHTML='<div class="km-empty">No rank changes yet</div>';}
      else{rhEl.innerHTML=rankHistory.slice(0,10).map(function(r){
        var arrow=r.direction==='up'?'↑':'↓';
        var col=r.direction==='up'?'#4ade80':'#f87171';
        return '<div class="km-history-row">'
          +'<span style="color:'+col+';font-weight:700">'+arrow+' '+r.to+'</span>'
          +'<span class="km-history-mmr">from '+r.from+'</span>'
          +'<span class="km-history-time">'+r.time+'</span></div>';
      }).join('');}
    }
  }

  function updateHistoryTab() {
    var el=document.getElementById('km-history-body'); if(!el) return;
    if(!matchHistory.length){el.innerHTML='<div class="km-empty">No matches yet</div>';return;}
    el.innerHTML=matchHistory.slice(0,20).map(function(m){
      var col=m.result==='Win'?'#4ade80':'#f87171';
      var diff=m.diff!=null?'<span style="color:'+(m.diff>=0?'#4ade80':'#f87171')+';font-size:10px">'+(m.diff>=0?'+':'')+m.diff+'</span>':'';
      var xpStr=m.xp>0?'<span style="color:#fbbf24;font-size:10px">+'+m.xp+' XP</span>':'';
      return '<div class="km-history-row">'
        +'<span style="color:'+col+';font-weight:700">'+m.result+'</span>'
        +'<span class="km-history-mmr">'+m.score+' vs '+(m.opponent||'?')+' '+diff+' '+xpStr+'</span>'
        +'<span class="km-history-time">'+(m.mode||'?')+' '+m.time+'</span>'
        +'</div>';
    }).join('');
  }

  function updateRivalsTab() {
    // Rivals
    var rivEl=document.getElementById('km-rivals-body'); if(rivEl){
      var rivKeys=Object.keys(rivals).filter(function(k){return rivals[k].count>=3;}).sort(function(a,b){return rivals[b].count-rivals[a].count;});
      if(!rivKeys.length){rivEl.innerHTML='<div class="km-empty">Face opponents 3+ times to see rivals</div>';}
      else{rivEl.innerHTML=rivKeys.slice(0,10).map(function(id){
        var r=rivals[id];
        var name=r.name||'Opponent';
        var wr=r.count>0?Math.round((r.wins/r.count)*100):0;
        var col=wr>=50?'#4ade80':'#f87171';
        return '<div class="km-history-row">'
          +'<span style="color:#f97316;font-weight:700">⚔️ '+name+'</span>'
          +'<span class="km-history-mmr"><span class="col-green">'+r.wins+'W</span> / <span class="col-red">'+r.losses+'L</span></span>'
          +'<span style="color:'+col+';font-size:10px">'+wr+'% WR</span>'
          +'</div>';
      }).join('');}
    }
    // Opponent log
    var oppEl=document.getElementById('km-opp-log-body'); if(oppEl){
      if(!opponentLog.length){oppEl.innerHTML='<div class="km-empty">No opponents logged yet</div>';}
      else{oppEl.innerHTML=opponentLog.slice(0,15).map(function(o){
        var col=o.result==='Win'?'#4ade80':'#f87171';
        var mmrGuess=o.mmrGuess?'~'+o.mmrGuess+' MMR':'?';
        var oppName = o.opponent || o.name || 'Opponent';
        return '<div class="km-history-row">'
          +'<span style="color:'+col+';font-weight:700">'+o.score+'</span>'
          +'<span class="km-history-mmr">'+oppName+' ('+mmrGuess+')</span>'
          +'<span class="km-history-time">'+(o.mode||'?')+' '+o.time+'</span>'
          +'</div>';
      }).join('');}
    }
  }

  function updatePeakBody() {
    var el=document.getElementById('km-peak-body'); if(!el) return;
    var labels={Competitive1v1:'1v1',Competitive2v2:'2v2',Competitive3v3:'3v3',Casual:'Casual'};
    var keys=Object.keys(peakMMR);
    if(!keys.length){el.innerHTML='<div class="km-empty">No peak data yet</div>';return;}
    el.innerHTML=keys.map(function(k){
      var r=getRank(peakMMR[k]);
      return '<div class="km-stat-row"><span>'+(labels[k]||k)+' '+r.emoji+'</span><span style="color:'+theme.accent+'">'+peakMMR[k]+'</span></div>';
    }).join('');
  }

  function updateClipsBody() {
    var el=document.getElementById('km-clips-body'); if(!el) return;
    if(!clipMarks.length){el.innerHTML='<div class="km-empty">No clips marked</div>';return;}
    el.innerHTML='<div class="km-clips-list">'+clipMarks.map(function(t){return '<span class="km-clip-stamp">▶ '+t+'</span>';}).join('')+'</div>';
  }

  // ─── PROFILE CARD EXPORT ──────────────────────────────────────────────────
  function exportProfileCard() {
    if(!playerData){showToast('Login first','warn');return;}
    var d=playerData;
    var nick=d.Nickname||'Unknown';
    var mmr3=d.ModesGlicko&&d.ModesGlicko['Competitive3v3']?d.ModesGlicko['Competitive3v3'].displayRating:'?';
    var rank3=getRank(mmr3);
    var skin=SKINS[d.EquippedSkinId||'body.0']||'Unknown';
    var tot=session.wins+session.losses;
    var wr=tot>0?Math.round((session.wins/tot)*100)+'%':'—';
    var acc=session.shots>0?Math.round((session.goals/session.shots)*100)+'%':'—';

    var modes=[
      {label:'1v1',key:'Competitive1v1'},
      {label:'2v2',key:'Competitive2v2'},
      {label:'3v3',key:'Competitive3v3'},
      {label:'Casual',key:'Casual'},
    ];
    var rankLines=modes.map(function(m){
      var g=d.ModesGlicko&&d.ModesGlicko[m.key];
      var s=d.ModesData&&d.ModesData[m.key];
      if(!g||!s) return '';
      var r=getRank(g.displayRating);
      var t=s.wins+s.loses, w=t>0?Math.round((s.wins/t)*100):0;
      return '> '+r.emoji+' **'+m.label+'** — '+g.displayRating+' MMR | '+s.wins+'W/'+s.loses+'L ('+w+'% WR)';
    }).filter(Boolean).join('\n');

    var rivalLines=Object.keys(rivals).filter(function(k){return rivals[k].count>=3;}).slice(0,3).map(function(id){
      var r=rivals[id];
      var name = r.name || 'Opponent';
      var w=r.count>0?Math.round((r.wins/r.count)*100):0;
      return '> ⚔️ **'+name+'** — '+r.wins+'W/'+r.losses+'L ('+w+'% WR)';
    }).join('\n');

    var peakLines=Object.keys(peakMMR).map(function(k){
      var labels={Competitive1v1:'1v1',Competitive2v2:'2v2',Competitive3v3:'3v3',Casual:'Casual'};
      return '> '+getRank(peakMMR[k]).emoji+' '+(labels[k]||k)+': **'+peakMMR[k]+'**';
    }).join('\n');

    var card=[
      '# '+rank3.emoji+' '+nick+' — RocketGoal.io Profile',
      '',
      '> 🚗 **Car:** '+skin,
      '> 🎮 **Account XP:** '+(d.AccountXp||0).toLocaleString(),
      '> ⚽ **Total Matches:** '+Object.values(d.ModesData||{}).reduce(function(a,m){return a+(m.matchesPlayed||0);},0),
      '',
      '## 📊 Rankings',
      rankLines,
      '',
      '## 🏆 Peak MMR',
      peakLines||'> *No peak data*',
      '',
      '## 🎯 This Session',
      '> **Matches:** '+session.matches+' | **W:** '+session.wins+' | **L:** '+session.losses+' | **WR:** '+wr,
      '> **Goals:** '+session.goals+' | **Conceded:** '+session.conceded+' | **Accuracy:** '+acc,
      '> **Streak:** '+session.streak+' | **Best:** '+session.maxStreak,
      '> **XP Gained:** +'+session.xpGained,
      '> **Comebacks:** '+session.comebacks+(session.perfectGames>0?' | **Perfect Games:** '+session.perfectGames:''),
      '',
      (rivalLines?'## ⚔️ Rivals\n'+rivalLines+'\n\n':''),
      '---',
      '*Generated by KeyMod v5 — made by @keydopz on TikTok*',
    ].join('\n');

    try{
      navigator.clipboard.writeText(card).then(function(){showToast('Profile card copied!','ok');}).catch(function(){fallbackCopy(card);});
    }catch(e){fallbackCopy(card);}
  }

  // ─── CONSOLE ──────────────────────────────────────────────────────────────
  function appendConsoleRow(entry) {
    var el=document.getElementById('km-console-body'); if(!el) return;
    var row=document.createElement('div'); row.className='km-log-row';
    if(entry.level==='warn') row.classList.add('warn');
    if(entry.level==='err')  row.classList.add('err');
    if(entry.line.includes('KeyMod')||entry.line.includes('v0304_')) row.classList.add('hi');
    row.textContent=entry.line;
    el.appendChild(row);
    if(activeTab==='console') scrollConsole();
  }
  function scrollConsole(){var el=document.getElementById('km-console-body');if(el)el.scrollTop=el.scrollHeight;}

  // ─── DRAG ─────────────────────────────────────────────────────────────────
  function makeDraggable(el,handle) {
    var ox=0,oy=0,drag=false;
    handle.addEventListener('mousedown',function(e){drag=true;ox=e.clientX-el.offsetLeft;oy=e.clientY-el.offsetTop;});
    document.addEventListener('mousemove',function(e){if(!drag)return;el.style.left=(e.clientX-ox)+'px';el.style.top=(e.clientY-oy)+'px';el.style.right='auto';});
    document.addEventListener('mouseup',function(){drag=false;});
  }

  // ─── THEME & CSS ──────────────────────────────────────────────────────────
  function hexToRgba(hex,alpha) {
    var r=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
    if(!r) return hex;
    return 'rgba('+parseInt(r[1],16)+','+parseInt(r[2],16)+','+parseInt(r[3],16)+','+alpha+')';
  }

  function applyTheme() {
    // Use default BakkesMod theme only.
    theme.preset = 'bakkesmod';
    theme.accent = '#006eff'; theme.text = '#d4d4d4'; theme.opacity = 0.99;
    var s=document.getElementById('km-style'); if(s) s.textContent=buildCSS();
    var menu=document.getElementById('km-menu'); if(!menu) return;
    var p=getPreset();
    var op=theme.opacity||0.96;
    var bgRgba=hexToRgba(p.bg.includes('rgba')?'#000000':p.bg, op);
    menu.style.background=p.bg; // CSS handles it
    menu.style.borderTopColor=theme.accent;
      updatePeakBody();
    updateDailyProgress();
  }

  function buildCSS() {
    var p  = getPreset();
    var a  = theme.accent || p.accent;
    var txt= theme.text   || p.text;
    var op = theme.opacity || 0.96;
    var bg    = p.bg;
    var tabbg = p.tabBg;
    var blur  = p.blur;
    var brd   = p.border;

    // Aero gets special chrome styling
    var isAero    = theme.preset === 'aero';
    var isGlass   = theme.preset === 'glass';
    var isArch    = theme.preset === 'arch';
    var isWindows = theme.preset === 'windows';
    var isGTA     = theme.preset === 'gta';
    var isRL      = theme.preset === 'rl';
    var isMinimal = theme.preset === 'minimal';

    var splashCSS;
    if(theme.loading==='rainbow') {
      splashCSS='background:linear-gradient(270deg,#ff0000,#ff7700,#ffff00,#00ff00,#0000ff,#8b00ff,#ff0000);background-size:400% 400%;animation:km-rainbow 4s linear infinite;';
    } else if(theme.loading==='black'||!theme.loading) {
      splashCSS='background:linear-gradient(160deg,#0a0a0f 0%,#111118 60%,#0d0d15 100%);';
    } else {
      splashCSS='background:'+theme.loading+';';
    }

    return [
      "@import url('https://fonts.googleapis.com/css2?family=Bebas+Neue&family=DM+Mono:ital,wght@0,400;0,500&family=JetBrains+Mono:wght@400;500&display=swap');",
      '* { box-sizing: border-box; }',
      '@keyframes km-rainbow{0%{background-position:0% 50%}50%{background-position:100% 50%}100%{background-position:0% 50%}}',
      '@keyframes km-pulse{0%,100%{box-shadow:0 0 8px '+a+'}50%{box-shadow:0 0 22px '+a+',0 0 40px '+a+'}}',

      // Splash
      '#km-splash{position:fixed;inset:0;z-index:999999;display:flex;flex-direction:column;align-items:center;justify-content:center;transition:opacity 0.65s ease;padding-top:28px;'+splashCSS+'}',
      '#km-splash-scan{position:absolute;inset:0;background:repeating-linear-gradient(0deg,transparent,transparent 2px,rgba(0,0,0,0.03) 2px,rgba(0,0,0,0.03) 4px);pointer-events:none;}',
      '#km-gta-header{font-family:"Bebas Neue",sans-serif;font-size:80px;letter-spacing:16px;color:#f5c518;line-height:1;text-shadow:4px 4px 0 rgba(0,0,0,0.9),-1px -1px 0 rgba(200,150,0,0.4);}',
      '#km-gta-sub{font-family:"DM Mono",monospace;font-size:11px;letter-spacing:3px;color:rgba(245,197,24,0.5);text-transform:uppercase;margin-top:6px;}',
      '#km-gta-divider{width:100%;max-width:360px;height:1px;background:linear-gradient(90deg,transparent,#f5c518,transparent);margin:20px auto;}',
      '#km-gta-loadbar{width:360px;height:4px;background:rgba(255,255,255,0.08);margin:0 auto 10px;}',
      '#km-gta-fill{height:100%;width:0%;background:#f5c518;transition:width 4s cubic-bezier(0.4,0,0.2,1);}',
      '#km-gta-status{font-family:"DM Mono",monospace;font-size:11px;letter-spacing:2px;color:rgba(245,197,24,0.4);text-transform:uppercase;margin-top:6px;}',
      '#km-gta-tip{font-family:"DM Mono",monospace;font-size:10px;color:rgba(255,255,255,0.2);margin-top:16px;max-width:380px;text-align:center;letter-spacing:0.3px;}',
      '#km-rl-bg{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;pointer-events:none;}',
      '#km-rl-ring1{position:absolute;width:400px;height:400px;border:1px solid rgba(0,150,255,0.12);border-radius:50%;animation:km-spin 8s linear infinite;}',
      '#km-rl-ring2{position:absolute;width:280px;height:280px;border:1px solid rgba(0,150,255,0.08);border-radius:50%;animation:km-spin 5s linear infinite reverse;}',
      '@keyframes km-spin{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}',
      '#km-skip-always{position:absolute;bottom:24px;background:transparent;border:none;color:rgba(255,255,255,0.2);font-family:"DM Mono",monospace;font-size:10px;letter-spacing:1px;cursor:pointer;padding:6px 20px;transition:color 0.2s;}',
      '#km-skip-always:hover{color:rgba(255,255,255,0.6);}',
      '#km-splash-inner{text-align:center;}',
      '#km-splash-title{font-family:"Bebas Neue",sans-serif;font-size:96px;letter-spacing:12px;color:#fff;line-height:1;text-shadow:0 0 60px rgba(245,197,24,0.4),0 0 120px rgba(245,197,24,0.12);}',
      '#km-splash-sub{font-family:"DM Mono",monospace;font-size:13px;font-weight:500;color:rgba(255,255,255,0.45);letter-spacing:2.5px;text-transform:uppercase;margin-top:10px;}',
      '#km-splash-sub span{color:#f5c518;font-weight:700;}',
      '#km-splash-bar-wrap{margin:34px auto 0;width:200px;height:3px;background:rgba(255,255,255,0.1);border-radius:2px;overflow:hidden;}',
      '#km-splash-bar{height:100%;width:0%;background:rgba(255,255,255,0.9);border-radius:2px;transition:width 4s cubic-bezier(0.25,0.46,0.45,0.94);}',
      '#km-splash-status{font-family:-apple-system,BlinkMacSystemFont,"SF Pro Text","Helvetica Neue",sans-serif;font-size:12px;color:rgba(255,255,255,0.45);letter-spacing:0.2px;margin-top:14px;}',
      '#km-skip{position:absolute;bottom:28px;background:transparent;border:none;color:rgba(255,255,255,0.3);font-family:-apple-system,BlinkMacSystemFont,"SF Pro Text",sans-serif;font-size:12px;letter-spacing:0;cursor:pointer;padding:8px 24px;transition:color 0.2s;}',
      '#km-skip:hover{color:rgba(255,255,255,0.7);}',
      // macOS menu bar
      '#km-macos-bar{position:absolute;top:0;left:0;right:0;height:28px;background:rgba(0,0,0,0.5);backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);display:flex;align-items:center;padding:0 14px;gap:6px;}',
      '.km-macos-dot{width:11px;height:11px;border-radius:50%;flex-shrink:0;}',
      '#km-macos-bar-title{flex:1;text-align:center;font-family:-apple-system,BlinkMacSystemFont,"SF Pro Text",sans-serif;font-size:12px;font-weight:500;color:rgba(255,255,255,0.55);letter-spacing:0.2px;}',
      // macOS Software Update card
      '#km-splash-inner{background:rgba(30,30,35,0.72);backdrop-filter:blur(40px) saturate(180%);-webkit-backdrop-filter:blur(40px) saturate(180%);border:0.5px solid rgba(255,255,255,0.18);border-radius:16px;padding:36px 40px 32px;width:340px;text-align:center;box-shadow:0 32px 80px rgba(0,0,0,0.6),0 1px 0 rgba(255,255,255,0.1) inset;}',
      '#km-update-icon-wrap{display:flex;align-items:center;justify-content:center;gap:12px;margin-bottom:24px;}',
      '#km-update-icon,#km-update-icon2{border-radius:13px;box-shadow:0 4px 20px rgba(0,0,0,0.5);}',
      '#km-update-arrow{color:rgba(255,255,255,0.3);display:flex;align-items:center;}',
      '#km-update-title{font-family:-apple-system,BlinkMacSystemFont,"SF Pro Display",sans-serif;font-size:20px;font-weight:600;color:#fff;letter-spacing:0.2px;margin-bottom:4px;}',
      '#km-update-subtitle{font-family:-apple-system,BlinkMacSystemFont,"SF Pro Text",sans-serif;font-size:13px;color:rgba(255,255,255,0.5);margin-bottom:22px;}',
      '#km-update-bar-track{width:100%;height:4px;background:rgba(255,255,255,0.12);border-radius:2px;overflow:hidden;margin-bottom:12px;}',
      '#km-update-detail{font-family:-apple-system,BlinkMacSystemFont,"SF Pro Text",sans-serif;font-size:11px;color:rgba(255,255,255,0.25);line-height:1.5;margin-top:16px;letter-spacing:0.1px;}',
      // Override old splash-status inside the card
      '#km-splash-inner #km-splash-status{font-family:-apple-system,BlinkMacSystemFont,"SF Pro Text",sans-serif!important;font-size:12px!important;color:rgba(255,255,255,0.4)!important;margin-top:0!important;letter-spacing:0!important;}',

      // Goal / End flash
      '#km-goal-flash{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:999998;font-family:"Bebas Neue",sans-serif;font-size:72px;color:#fff;letter-spacing:6px;text-shadow:0 0 40px rgba(0,0,0,0.8);pointer-events:none;transition:opacity 0.5s ease;opacity:1;}',
      '#km-end-flash{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:999990;background:rgba(0,0,0,0.85);border:1px solid rgba(255,255,255,0.12);border-radius:14px;padding:20px 32px;text-align:center;pointer-events:none;transition:opacity 0.7s;font-family:"JetBrains Mono",monospace;}',

      // Welcome
      '#km-welcome{position:fixed;top:20px;left:50%;transform:translateX(-50%);z-index:99997;background:rgba(0,0,0,0.85);border:1px solid '+a+';border-radius:6px;color:#fff;font-family:"DM Mono",monospace;font-size:14px;padding:10px 24px;pointer-events:none;transition:opacity 0.6s;opacity:1;}',

      // Goal celebration banner
      '#km-goal-banner{position:fixed;left:0;right:0;top:0;z-index:99999999;height:120px;display:flex;align-items:center;justify-content:center;pointer-events:none;opacity:0;transform:translateY(-100%);transition:opacity 0.25s ease,transform 0.3s cubic-bezier(0.34,1.56,0.64,1);}',
      '#km-goal-banner.show{opacity:1;transform:translateY(0);}',
      '.km-banner-bg-overlay{position:absolute;inset:0;background:linear-gradient(90deg,rgba(0,0,0,0.3),transparent,rgba(0,0,0,0.3));}',
      '.km-banner-content{position:relative;display:flex;align-items:center;gap:18px;padding:0 32px;width:100%;justify-content:center;}',
      '.km-banner-text-wrap{text-align:center;}',
      '.km-banner-goal{font-family:"Bebas Neue",sans-serif;font-size:52px;letter-spacing:6px;color:#fff;line-height:1;text-shadow:0 2px 20px rgba(0,0,0,0.6);}',
      '.km-banner-player{font-family:"DM Mono",monospace;font-size:13px;letter-spacing:2px;color:rgba(255,255,255,0.7);text-transform:uppercase;margin-top:4px;}',

      // Toast — stacked bottom-right
      // ── macOS Tahoe notification CSS ────────────────────────────────────────
      '.km-mac-toast{'
        +'position:fixed;right:18px;z-index:999999;'
        +'width:320px;'
        +'background:rgba(28,28,32,0.72);'
        +'backdrop-filter:blur(40px) saturate(200%) brightness(118%);'
        +'-webkit-backdrop-filter:blur(40px) saturate(200%) brightness(118%);'
        +'border:0.5px solid rgba(255,255,255,0.22);'
        +'border-radius:14px;'
        +'box-shadow:0 8px 32px rgba(0,0,0,0.5),0 1px 0 rgba(255,255,255,0.12) inset;'
        +'padding:13px 14px 13px 13px;'
        +'display:flex;align-items:flex-start;gap:11px;'
        +'pointer-events:auto;'
        +'opacity:0;transform:translateX(32px) scale(0.96);'
        +'transition:opacity 0.36s cubic-bezier(0.4,0,0.2,1),transform 0.36s cubic-bezier(0.4,0,0.2,1);'
        +'cursor:default;'
        +'}',
      '.km-mac-toast.show{opacity:1!important;transform:translateX(0) scale(1)!important;}',
      '.km-mac-toast.hide{opacity:0!important;transform:translateX(20px) scale(0.96)!important;}',
      '.km-mac-icon{width:34px;height:34px;border-radius:8px;border:1px solid;display:flex;align-items:center;justify-content:center;font-size:18px;flex-shrink:0;}',
      '.km-mac-body{flex:1;min-width:0;padding-top:1px;}',
      '.km-mac-title{font-family:-apple-system,BlinkMacSystemFont,"SF Pro Text","Inter",sans-serif;font-size:12px;font-weight:600;color:rgba(255,255,255,0.5);letter-spacing:0.1px;margin-bottom:2px;}',
      '.km-mac-msg{font-family:-apple-system,BlinkMacSystemFont,"SF Pro Text","Inter",sans-serif;font-size:13px;font-weight:400;color:#fff;line-height:1.4;word-break:break-word;}',
      '.km-mac-close{background:none;border:none;color:rgba(255,255,255,0.3);font-size:11px;cursor:pointer;padding:0 2px;margin-top:1px;flex-shrink:0;line-height:1;transition:color 0.1s;}',
      '.km-mac-close:hover{color:rgba(255,255,255,0.7);}',

      '.km-toast{position:fixed;right:22px;bottom:22px;z-index:999998;background:rgba(18,18,22,0.9);border:1px solid rgba(255,255,255,0.14);border-left:3px solid '+a+';color:'+txt+';font-family:"DM Mono",monospace;font-size:13px;padding:11px 18px;border-radius:10px;pointer-events:none;opacity:0;transform:translateY(12px);transition:opacity 0.2s,transform 0.2s;white-space:normal;max-width:400px;backdrop-filter:blur(10px);}',
      '.km-toast.show{opacity:1;transform:translateY(0);}',
      '.km-toast.permanent{width:min(92vw,1920px);height:200px;left:50%;right:auto;transform:translateX(-50%);bottom:12px;border-radius:12px;max-width:1920px;min-width:500px;opacity:1;pointer-events:auto;}',
      '.km-toast.ok{border-left-color:#4ade80;}',
      '.km-toast.warn{border-left-color:#fbbf24;}',
      '.km-toast.err{border-left-color:#f87171;}',
      '.km-toast.info{border-left-color:'+a+';}',
      '.km-toast.xp{border-left-color:#fbbf24;}',

      // HUD
      '#km-hud{position:fixed;bottom:22px;right:22px;z-index:99997;'
        +(isAero?'background:rgba(0,15,35,0.78);border:1px solid rgba(100,200,255,0.25);':
          isGlass?'background:rgba(15,20,40,0.55);border:1px solid rgba(255,255,255,0.18);':
          'background:rgba(8,10,16,0.9);border:1px solid rgba(255,255,255,0.1);')
        +'border-left:2px solid '+a+';border-radius:8px;padding:6px 12px;font-family:system-ui,-apple-system,"DM Mono",monospace;font-size:12px;color:'+txt+';display:flex;align-items:center;gap:7px;cursor:pointer;backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);transition:all 0.2s;}',
      '#km-hud:hover{box-shadow:0 0 16px '+hexToRgba(a,0.4)+';}',
      '#km-hud-party{animation:km-party-pulse 1.5s ease-in-out infinite;}',
      '@keyframes km-party-pulse{0%,100%{opacity:1}50%{opacity:0.5}}@keyframes km-rec-blink{0%,100%{opacity:1;box-shadow:0 0 6px #f87171}50%{opacity:0.3;box-shadow:none}}',
      '#km-hud-rank{font-size:15px;line-height:1;}',
      '#km-hud-fps{font-size:11px;font-weight:600;font-variant-numeric:tabular-nums;letter-spacing:0;min-width:46px;text-align:right;}',
      '#km-hud-ping{font-size:11px;font-weight:500;font-variant-numeric:tabular-nums;letter-spacing:0;min-width:36px;text-align:right;}',
      '#km-hud-sep{color:rgba(255,255,255,0.15);font-size:10px;margin:0 1px;}',

      // ── Menu shell ─────────────────────────────────────────────────────────
      '#km-menu{display:none;position:fixed;top:48px;left:48px;z-index:99998;width:500px;'
        +'background:'+bg+';'
        +(blur&&blur!=='none'?'backdrop-filter:'+blur+';-webkit-backdrop-filter:'+blur+';':'')
        +'border:1px solid '+brd+';'
        +'box-shadow:0 16px 48px rgba(0,0,0,0.6);'
        +'font-family:system-ui,-apple-system,"Segoe UI",sans-serif;font-size:13px;color:'+txt+';overflow:hidden;'
        +'border-radius:8px;}',

      // ── Header ─────────────────────────────────────────────────────────────
      '#km-header{display:flex;align-items:center;gap:10px;padding:10px 12px;'
        +'background:transparent;border-bottom:1px solid rgba(255,255,255,0.07);cursor:grab;}',
      '#km-header:active{cursor:grabbing;}',
      '#km-title{font-family:system-ui,-apple-system,sans-serif;font-size:13px;font-weight:600;letter-spacing:0;color:'+txt+';flex:1;line-height:1;opacity:0.9;}',
      '#km-wip-badge{font-family:system-ui,sans-serif;font-size:10px;font-weight:400;letter-spacing:0;color:rgba(255,255,255,0.25);padding:0 0 0 8px;vertical-align:middle;line-height:1;}',
      '#km-ping{font-size:11px;color:#4ade80;font-family:"DM Mono",monospace;opacity:0.7;}',
      '#km-header-right{display:flex;align-items:center;gap:6px;}',
      '#km-discord-btn{display:flex;align-items:center;gap:5px;background:rgba(255,255,255,0.04);border:1px solid rgba(255,255,255,0.1);color:rgba(255,255,255,0.5);font-size:11px;letter-spacing:0;padding:4px 9px;cursor:pointer;transition:all 0.1s;font-family:system-ui,-apple-system,sans-serif;font-weight:500;border-radius:4px;}',
      '#km-discord-btn:hover{border-color:'+a+';color:'+a+';}',
      '#km-discord-btn.has-code{border-color:rgba(88,101,242,0.5);color:#7289da;}',
      '#km-discord-label{font-size:10px;font-family:"DM Mono",monospace;}',
      '#km-close{background:none;border:none;color:rgba(255,255,255,0.25);font-size:12px;cursor:pointer;padding:4px 8px;transition:color 0.1s;}',
      '#km-close:hover{color:#f87171;}',

      // ── Tip bar ────────────────────────────────────────────────────────────
      '#km-tip-bar{display:flex;align-items:center;gap:8px;padding:5px 16px;background:rgba(255,255,255,0.02);border-bottom:1px solid rgba(255,255,255,0.05);}',
      '#km-tip-text{flex:1;font-size:11px;color:rgba(255,255,255,0.3);font-family:system-ui,-apple-system,sans-serif;letter-spacing:0;}',
      '#km-tip-close{background:none;border:none;color:rgba(255,255,255,0.15);cursor:pointer;font-size:10px;padding:0 2px;line-height:1;}',
      '#km-tip-close:hover{color:rgba(255,255,255,0.45);}',

      // ── Tabs ───────────────────────────────────────────────────────────────
      '#km-tabs-wrap{display:flex;align-items:stretch;background:rgba(0,0,0,0.25);border-bottom:1px solid rgba(255,255,255,0.06);}',
      '#km-tabs{display:flex;overflow-x:auto;flex:1;scrollbar-width:none;}',
      '#km-tabs::-webkit-scrollbar{display:none;}',
      '.km-tab-arrow{background:none;border:none;border-left:1px solid rgba(255,255,255,0.05);color:rgba(255,255,255,0.25);font-size:14px;padding:0 10px;cursor:pointer;transition:color 0.1s;flex-shrink:0;}',
      '.km-tab-arrow:first-child{border-left:none;border-right:1px solid rgba(255,255,255,0.05);}',
      '.km-tab-arrow:hover{color:rgba(255,255,255,0.7);}',
      '.kmt{flex:0 0 auto;background:none;border:none;border-bottom:2px solid transparent;color:rgba(255,255,255,0.4);font-size:12px;letter-spacing:0;text-transform:none;padding:8px 12px;cursor:pointer;transition:color 0.1s,border-color 0.1s;white-space:nowrap;font-family:system-ui,-apple-system,sans-serif;font-weight:400;}',
      '.kmt:hover{color:rgba(255,255,255,0.8);}',
      '.kmt.active{color:'+txt+';border-bottom-color:'+a+';font-weight:500;}',

      // ── Panes ──────────────────────────────────────────────────────────────
      '.km-pane{display:none;padding:16px 16px 20px;max-height:460px;overflow-y:auto;scrollbar-width:thin;scrollbar-color:rgba(255,255,255,0.08) transparent;}',
      '.km-pane::-webkit-scrollbar{width:2px;}',
      '.km-pane::-webkit-scrollbar-thumb{background:rgba(255,255,255,0.08);}',
      '.km-pane.active{display:block;}',
      '#km-pane-console{padding:0!important;}',

      // ── Section headers ────────────────────────────────────────────────────
      '.km-section{font-size:11px;letter-spacing:0;text-transform:none;font-weight:600;color:rgba(255,255,255,0.5);margin:16px 0 6px;font-family:system-ui,-apple-system,sans-serif;}',
      '.km-section::after{display:none;}',
      '.km-section:first-child{margin-top:2px;}',
      '.km-empty{font-size:12px;color:rgba(255,255,255,0.22);padding:8px 0;font-family:system-ui,-apple-system,sans-serif;}',
      '.km-divider{height:1px;background:rgba(255,255,255,0.05);margin:8px 0;}',

      // ── Player card ────────────────────────────────────────────────────────
      '.km-player-card{display:flex;align-items:baseline;justify-content:space-between;margin-bottom:12px;}',
      '.km-player-name{font-size:18px;font-weight:700;color:'+txt+';font-family:system-ui,-apple-system,sans-serif;letter-spacing:-0.3px;}',
      '.km-player-xp{font-size:10px;color:rgba(255,255,255,0.22);font-family:"DM Mono",monospace;}',
      '.km-car-row{display:flex;justify-content:space-between;align-items:center;padding:5px 0 8px;border-bottom:1px solid rgba(255,255,255,0.05);margin-bottom:4px;}',
      '.km-car-label{font-size:11px;color:rgba(255,255,255,0.35);text-transform:none;letter-spacing:0;font-family:"Inter",system-ui,sans-serif;}',
      '.km-car-name{font-size:11px;font-weight:600;color:'+txt+';}',

      // ── Rank rows ──────────────────────────────────────────────────────────
      '.km-rank-row{display:flex;align-items:center;gap:14px;padding:10px 0;border-bottom:1px solid rgba(255,255,255,0.04);}',
      '.km-rank-row:last-child{border-bottom:none;}',
      '.km-rank-icon{font-size:26px;width:32px;text-align:center;flex-shrink:0;}',
      '.km-rank-info{flex:1;min-width:0;}',
      '.km-rank-name{font-size:14px;font-weight:600;line-height:1.2;font-family:system-ui,-apple-system,sans-serif;letter-spacing:-0.2px;}',
      '.km-rank-mode{font-size:11px;color:rgba(255,255,255,0.35);letter-spacing:0;text-transform:none;margin-top:2px;font-family:system-ui,-apple-system,sans-serif;}',
      '.km-rank-right{text-align:right;flex-shrink:0;}',
      '.km-rank-mmr{font-size:18px;font-weight:700;color:'+a+';font-family:"DM Mono",monospace;}',
      '.km-rank-wl{font-size:10px;color:rgba(255,255,255,0.22);margin-top:1px;}',
      '.km-peak{font-size:9px;color:rgba(255,255,255,0.2);}',

      // ── Progress bar ───────────────────────────────────────────────────────
      '.km-prog-wrap{height:2px;background:rgba(255,255,255,0.06);overflow:hidden;margin-top:4px;}',
      '.km-prog-bar{height:100%;transition:width 0.5s cubic-bezier(0.4,0,0.2,1);}',

      // ── Stat rows ──────────────────────────────────────────────────────────
      '.km-stat-row{display:flex;justify-content:space-between;align-items:center;padding:7px 0;border-bottom:1px solid rgba(255,255,255,0.05);font-size:12px;}',
      '.km-stat-row:last-child{border-bottom:none;}',
      '.km-stat-row>span:first-child{color:rgba(255,255,255,0.5);text-transform:none;font-size:12px;letter-spacing:0;font-family:system-ui,-apple-system,sans-serif;}',
      '.km-stat-row>span:last-child{font-family:"DM Mono",monospace;font-size:13px;font-weight:400;}',
      '.col-green{color:#4ade80;} .col-red{color:#f87171;}',

      // ── Stats grid ─────────────────────────────────────────────────────────
      '.km-stats-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:4px;margin:8px 0 12px;}',
      '.km-stat-cell{background:rgba(255,255,255,0.03);border:1px solid rgba(255,255,255,0.05);padding:10px 8px;text-align:center;}',
      '.km-stat-val{font-family:"DM Mono",monospace;font-size:20px;font-weight:700;color:'+a+';line-height:1;}',
      '.km-stat-lbl{font-size:8px;color:rgba(255,255,255,0.25);text-transform:uppercase;letter-spacing:1.5px;margin-top:4px;}',

      // ── Two col ────────────────────────────────────────────────────────────
      '.km-two-col{display:grid;grid-template-columns:1fr 1fr;gap:6px;margin-bottom:6px;}',
      '.km-col-card{background:rgba(255,255,255,0.03);border:1px solid rgba(255,255,255,0.06);padding:10px;}',
      '.km-card-title{font-size:8px;letter-spacing:2px;text-transform:uppercase;color:rgba(255,255,255,0.25);margin-bottom:6px;}',

      // ── History rows ───────────────────────────────────────────────────────
      '.km-history-row{display:flex;justify-content:space-between;align-items:center;padding:7px 0;border-bottom:1px solid rgba(255,255,255,0.05);font-size:12px;gap:8px;font-family:"Inter",system-ui,sans-serif;}',
      '.km-history-mmr{font-family:"Inter",system-ui,sans-serif;color:rgba(255,255,255,0.4);font-size:11px;flex:1;}',
      '.km-history-time{color:rgba(255,255,255,0.22);font-size:11px;white-space:nowrap;}',

      // ── Clips ──────────────────────────────────────────────────────────────
      '.km-clips-list{display:flex;flex-wrap:wrap;gap:4px;margin-top:6px;}',
      '.km-clip-stamp{background:rgba(255,255,255,0.04);border:1px solid rgba(255,255,255,0.08);color:'+a+';font-family:"DM Mono",monospace;font-size:10px;padding:3px 8px;}',

      // ── Opponent MMR ───────────────────────────────────────────────────────
      '.km-opp-range{padding:6px 0;}',
      '.km-opp-label{font-size:9px;color:rgba(255,255,255,0.28);letter-spacing:1px;text-transform:uppercase;margin-bottom:6px;}',
      '.km-opp-vals{font-family:"DM Mono",monospace;font-size:24px;font-weight:700;color:'+a+';}',
      '.km-opp-ranks{font-size:11px;color:rgba(255,255,255,0.35);margin-top:4px;}',

      // ── Buttons ────────────────────────────────────────────────────────────
      '.km-btn{display:block;width:100%;margin-top:8px;padding:7px 12px;'
        +'background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.12);color:rgba(255,255,255,0.65);'
        +'font-family:system-ui,-apple-system,sans-serif;font-size:12px;font-weight:500;letter-spacing:0;cursor:pointer;'
        +'transition:background 0.1s,border-color 0.1s,color 0.1s;text-align:center;border-radius:5px;}',
      '.km-btn:hover{background:rgba(255,255,255,0.1);border-color:rgba(255,255,255,0.25);color:#fff;}',
      '.km-btn.danger{border-color:rgba(239,68,68,0.2);color:rgba(248,113,113,0.6);}',
      '.km-btn.danger:hover{background:rgba(239,68,68,0.08);border-color:#f87171;color:#f87171;}',

      // ── Inputs ─────────────────────────────────────────────────────────────
      '.km-input{background:rgba(20,20,30,0.95);border:1px solid rgba(255,255,255,0.12);color:'+txt+';font-family:system-ui,sans-serif;font-size:12px;padding:5px 9px;border-radius:4px;width:60px;outline:none;transition:border-color 0.15s;}',
      '.km-input option{background:#14141e;color:#e2e8f0;}',
      '.km-input:focus{border-color:'+a+';}',
      '.km-select{background:rgba(20,20,30,0.95);border:1px solid rgba(255,255,255,0.12);color:'+txt+';font-family:system-ui,sans-serif;font-size:12px;padding:5px 9px;border-radius:4px;outline:none;cursor:pointer;}',
      '.km-select option{background:#14141e;color:#e2e8f0;}',
      '.km-color{width:34px;height:24px;border:1px solid rgba(255,255,255,0.1);background:none;cursor:pointer;padding:1px;}',
      '.km-slider{accent-color:'+a+';width:110px;cursor:pointer;}',

      // ── Toggles ────────────────────────────────────────────────────────────
      '.km-toggle-row{padding:7px 0;border-bottom:1px solid rgba(255,255,255,0.05);}',
      '.km-toggle-row label{font-size:12px;color:rgba(255,255,255,0.6);cursor:pointer;display:flex;align-items:center;gap:10px;letter-spacing:0;font-family:system-ui,-apple-system,sans-serif;}',
      '.km-toggle-row input[type=checkbox]{accent-color:'+a+';width:14px;height:14px;cursor:pointer;}',

      // ── Theme presets ──────────────────────────────────────────────────────
      '.km-preset-grid{display:flex;flex-wrap:wrap;gap:4px;margin-top:6px;}',
      '.km-preset-btn{padding:5px 12px;font-family:"DM Mono",monospace;font-size:10px;cursor:pointer;background:rgba(255,255,255,0.04);border:1px solid rgba(255,255,255,0.08);color:rgba(255,255,255,0.5);transition:all 0.12s;letter-spacing:0.5px;}',
      '.km-preset-btn:hover{background:rgba(255,255,255,0.08);color:#fff;}',
      '.km-preset-btn.active{background:rgba(255,255,255,0.08);border-color:'+a+';color:'+a+';}',

      // ── Emoji picker ───────────────────────────────────────────────────────
      '.km-emoji-grid{display:flex;flex-wrap:wrap;gap:4px;margin:6px 0 12px;}',
      '.km-emoji-btn{width:34px;height:34px;font-size:18px;background:rgba(255,255,255,0.04);border:1px solid rgba(255,255,255,0.07);cursor:pointer;transition:all 0.1s;display:flex;align-items:center;justify-content:center;padding:0;}',
      '.km-emoji-btn:hover{background:rgba(255,255,255,0.09);}',
      '.km-emoji-btn.active{border-color:'+a+';background:rgba(255,255,255,0.09);}',

      // ── Plugin Store ───────────────────────────────────────────────────────
      '.km-store-filters{display:flex;flex-wrap:wrap;gap:4px;margin-bottom:14px;}',
      '.km-store-cat{padding:4px 12px;background:rgba(255,255,255,0.04);border:1px solid rgba(255,255,255,0.07);color:rgba(255,255,255,0.35);font-size:9px;letter-spacing:1px;text-transform:uppercase;cursor:pointer;transition:all 0.1s;font-family:"DM Mono",monospace;}',
      '.km-store-cat:hover,.km-store-cat.active{background:rgba(255,255,255,0.08);border-color:'+a+';color:'+a+';}',
      '.km-store-grid{display:flex;flex-direction:column;gap:1px;}',
      '.km-plugin-card{display:flex;align-items:center;gap:12px;padding:10px 12px;background:rgba(255,255,255,0.02);border-left:2px solid transparent;border-radius:4px;margin-bottom:2px;transition:background 0.1s,border-color 0.1s;}',
      '.km-plugin-card:hover{background:rgba(255,255,255,0.06);}',
      '.km-plugin-card.installed{border-left-color:'+a+';background:rgba(255,255,255,0.04);}',
      '.km-plugin-icon{font-size:20px;width:28px;text-align:center;flex-shrink:0;}',
      '.km-plugin-info{flex:1;min-width:0;}',
      '.km-plugin-name{font-size:12px;font-weight:600;color:'+txt+';letter-spacing:0;font-family:system-ui,-apple-system,sans-serif;}',
      '.km-plugin-cat{font-size:10px;color:rgba(255,255,255,0.35);letter-spacing:0;text-transform:none;margin-bottom:2px;font-family:system-ui,-apple-system,sans-serif;}',
      '.km-plugin-desc{font-size:11px;color:rgba(255,255,255,0.42);line-height:1.5;font-family:system-ui,-apple-system,sans-serif;}',
      '.km-plugin-install-btn{flex-shrink:0;padding:5px 12px;background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.12);color:rgba(255,255,255,0.55);font-family:"Inter",system-ui,sans-serif;font-size:11px;font-weight:500;cursor:pointer;transition:all 0.1s;letter-spacing:0;white-space:nowrap;border-radius:3px;}',
      '.km-plugin-install-btn:hover{border-color:'+a+';color:'+a+';}',
      '.km-plugin-install-btn.installed{border-color:rgba(74,222,128,0.3);color:#4ade80;background:rgba(74,222,128,0.05);}',

      // ── Owned plugins ──────────────────────────────────────────────────────
      '.km-owned-card{background:rgba(255,255,255,0.03);border-left:2px solid rgba(255,255,255,0.08);margin-bottom:4px;overflow:hidden;}',
      '.km-owned-header{display:flex;align-items:center;gap:10px;padding:10px 12px;cursor:pointer;transition:background 0.1s;}',
      '.km-owned-header:hover{background:rgba(255,255,255,0.04);}',
      '.km-owned-icon{font-size:18px;}',
      '.km-owned-name{flex:1;font-size:12px;font-weight:600;color:#fff;letter-spacing:0.3px;}',
      '.km-owned-arrow{color:rgba(255,255,255,0.2);font-size:11px;}',
      '.km-owned-body{padding:10px 12px 14px;border-top:1px solid rgba(255,255,255,0.06);}',

      // ── Leaderboard overlay ────────────────────────────────────────────────
      '#km-leaderboard{position:fixed;top:12px;left:12px;z-index:99997;'
        +(isAero?'background:rgba(0,20,45,0.85);border:1px solid rgba(100,200,255,0.18);':
          isGlass?'background:rgba(15,20,40,0.65);border:1px solid rgba(255,255,255,0.14);':
          'background:rgba(8,10,16,0.92);border:1px solid rgba(255,255,255,0.1);')
        +'border-left:2px solid '+a+';padding:10px 14px;font-family:"Inter",system-ui,sans-serif;backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px);min-width:160px;border-radius:6px;}',
      '.km-lb-row{display:flex;justify-content:space-between;align-items:center;gap:16px;padding:2px 0;}',
      '.km-lb-name{font-size:12px;font-weight:600;max-width:110px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:"Inter",system-ui,sans-serif;}',
      '.km-lb-score{font-size:20px;font-weight:700;color:#fff;font-family:"Bebas Neue",sans-serif;letter-spacing:1px;}',
      '.km-lb-divider{font-size:9px;color:rgba(255,255,255,0.18);text-align:center;letter-spacing:2px;padding:1px 0;}',
      '.km-lb-mmr{font-size:10px;color:rgba(255,255,255,0.3);}',

      // ── Party code popup ───────────────────────────────────────────────────
      '#km-party-popup{position:fixed;bottom:90px;right:22px;z-index:999997;width:280px;background:#0a0c0f;border:1px solid rgba(255,255,255,0.08);border-top:2px solid '+a+';overflow:hidden;box-shadow:0 20px 60px rgba(0,0,0,0.8);opacity:0;transform:translateY(10px);transition:opacity 0.2s,transform 0.2s;}',
      '#km-party-popup.show{opacity:1;transform:translateY(0);}',
      '#km-party-header{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;background:rgba(255,255,255,0.03);border-bottom:1px solid rgba(255,255,255,0.05);}',
      '#km-party-title{font-size:10px;letter-spacing:2px;text-transform:uppercase;color:'+a+';font-family:"DM Mono",monospace;}',
      '#km-party-close{background:none;border:none;color:rgba(255,255,255,0.25);font-size:12px;cursor:pointer;padding:2px 5px;}',
      '#km-party-close:hover{color:#f87171;}',
      '#km-party-code{font-family:"Bebas Neue",sans-serif;font-size:44px;font-weight:700;color:#fff;text-align:center;padding:14px 0 6px;letter-spacing:8px;}',
      '#km-party-sub{font-size:10px;color:rgba(255,255,255,0.25);text-align:center;padding-bottom:12px;font-family:"DM Mono",monospace;}',
      '#km-party-btns{display:flex;gap:1px;padding:0 12px 12px;}',
      '.km-party-btn{flex:1;padding:7px;background:rgba(255,255,255,0.04);border:1px solid rgba(255,255,255,0.08);color:rgba(255,255,255,0.5);font-family:"DM Mono",monospace;font-size:10px;cursor:pointer;transition:all 0.1s;letter-spacing:0.5px;}',
      '.km-party-btn:hover{background:rgba(255,255,255,0.08);color:#fff;}',
      '.km-party-btn.primary{border-color:rgba(255,255,255,0.15);color:rgba(255,255,255,0.7);}',
      '.km-party-btn.primary:hover{border-color:'+a+';color:'+a+';}',

      // ── Party tab ──────────────────────────────────────────────────────────
      '.km-party-code-box{background:rgba(255,255,255,0.03);border:1px solid rgba(255,255,255,0.07);padding:16px;text-align:center;margin-bottom:10px;}',
      '.km-party-code-label{font-size:9px;letter-spacing:2px;text-transform:uppercase;color:rgba(255,255,255,0.28);margin-bottom:8px;font-family:"DM Mono",monospace;}',
      '.km-party-code-num{font-family:"Bebas Neue",sans-serif;font-size:44px;font-weight:700;color:#f5c518;letter-spacing:8px;margin-bottom:12px;}',

      // ── Profile ────────────────────────────────────────────────────────────
      '.km-prof-row{display:flex;flex-direction:column;gap:4px;padding:6px 0;border-bottom:1px solid rgba(255,255,255,0.05);}',
      '.km-prof-row label{font-size:10px;color:rgba(255,255,255,0.3);letter-spacing:1px;text-transform:uppercase;}',
      '.km-url-input{width:100%;background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.1);color:#fff;font-family:"DM Mono",monospace;font-size:11px;padding:7px 10px;outline:none;transition:border-color 0.15s;}',
      '.km-url-input:focus{border-color:'+a+';}',
      '.km-url-input::placeholder{color:rgba(255,255,255,0.18);}',
      '.km-banner-preview-box{position:relative;height:80px;overflow:hidden;margin-top:10px;border:1px solid rgba(255,255,255,0.07);background:rgba(255,255,255,0.03);}',

      // ── Session lock ───────────────────────────────────────────────────────
      '#km-session-lock{position:fixed;inset:0;z-index:9999999;background:rgba(0,0,0,0.92);display:flex;align-items:center;justify-content:center;}',
      '#km-sl-box{background:#0a0c0f;border:1px solid rgba(255,255,255,0.08);border-top:2px solid #f97316;padding:40px;width:360px;text-align:center;}',
      '#km-sl-icon{font-size:48px;margin-bottom:14px;}',
      '#km-sl-title{font-family:"Bebas Neue",sans-serif;font-size:32px;letter-spacing:3px;color:#f97316;margin-bottom:12px;}',
      '#km-sl-body{font-size:13px;color:rgba(255,255,255,0.5);line-height:1.7;margin-bottom:22px;font-family:"DM Mono",monospace;}',
      '#km-sl-dismiss{display:block;width:100%;padding:11px;background:#f97316;border:none;color:#000;font-family:"DM Mono",monospace;font-size:12px;letter-spacing:1px;cursor:pointer;margin-bottom:8px;}',
      '#km-sl-override{display:block;width:100%;padding:9px;background:transparent;border:1px solid rgba(255,255,255,0.1);color:rgba(255,255,255,0.3);font-family:"DM Mono",monospace;font-size:11px;cursor:pointer;}',

      // ── Lock overlay (momentum/tilt) ───────────────────────────────────────
      '#km-session-lock{position:fixed;inset:0;z-index:999999;background:rgba(0,0,0,0.92);display:flex;align-items:center;justify-content:center;}',
      '#km-lock-inner{text-align:center;max-width:340px;}',
      '#km-lock-icon{font-size:56px;margin-bottom:16px;}',
      '#km-lock-title{font-family:"Bebas Neue",sans-serif;font-size:40px;letter-spacing:4px;color:#f87171;margin-bottom:12px;}',
      '#km-lock-msg{font-family:"DM Mono",monospace;font-size:12px;color:rgba(255,255,255,0.45);line-height:1.8;margin-bottom:24px;}',
      '#km-lock-dismiss{padding:10px 28px;background:transparent;border:1px solid #f87171;color:#f87171;font-family:"DM Mono",monospace;font-size:12px;letter-spacing:1px;cursor:pointer;}',

      // ── Support popup ──────────────────────────────────────────────────────
      '#km-support-overlay{position:fixed;inset:0;z-index:9999999;background:rgba(0,0,0,0.7);display:flex;align-items:center;justify-content:center;backdrop-filter:blur(4px);}',
      '#km-support-box{position:relative;background:#0a0c0f;border:1px solid rgba(255,255,255,0.08);border-top:2px solid #3b82f6;padding:36px 40px 32px;width:340px;text-align:center;}',
      '#km-support-close{position:absolute;top:10px;left:12px;background:none;border:none;color:rgba(255,255,255,0.25);font-size:14px;cursor:pointer;padding:4px;line-height:1;}',
      '#km-support-close:hover{color:#fff;}',
      '#km-support-icon{font-size:36px;margin-bottom:12px;}',
      '#km-support-title{font-family:"Bebas Neue",sans-serif;font-size:28px;letter-spacing:2px;color:#fff;margin-bottom:14px;}',
      '#km-support-body{font-family:"DM Mono",monospace;font-size:12px;color:rgba(255,255,255,0.45);margin-bottom:22px;line-height:1.7;}',
      '#km-support-body strong{color:rgba(255,255,255,0.8);font-size:14px;}',
      '#km-support-btn{display:inline-block;background:#2563eb;color:#fff;font-family:"DM Mono",monospace;font-size:12px;letter-spacing:1px;padding:10px 28px;text-decoration:none;cursor:pointer;transition:background 0.15s;}',
      '#km-support-btn:hover{background:#3b82f6;}',

      // ── Resize handle ──────────────────────────────────────────────────────
      '#km-resize-handle{position:absolute;bottom:0;right:0;width:14px;height:14px;cursor:se-resize;opacity:0.2;transition:opacity 0.15s;}',
      '#km-resize-handle:hover{opacity:0.6;}',
      '#km-resize-handle::before{content:"";position:absolute;bottom:3px;right:3px;width:7px;height:1px;background:#fff;box-shadow:0 -3px 0 #fff,0 -6px 0 #fff;}',
      '#km-resize-handle::after{content:"";position:absolute;bottom:3px;right:3px;height:7px;width:1px;background:#fff;box-shadow:-3px 0 0 #fff,-6px 0 0 #fff;}',

      // ── Momentum meter ─────────────────────────────────────────────────────
      '#km-momentum-wrap{position:fixed;bottom:70px;right:22px;z-index:99996;width:150px;}',
      '#km-momentum-label{font-family:"DM Mono",monospace;font-size:8px;letter-spacing:2px;color:rgba(255,255,255,0.25);text-align:center;margin-bottom:3px;}',
      '#km-momentum-track{height:3px;background:rgba(255,255,255,0.06);overflow:hidden;}',
      '#km-momentum-bar{height:100%;width:50%;transition:width 0.4s ease,background 0.4s ease;}',

      // ── Focus overlay ──────────────────────────────────────────────────────
      '#km-focus-overlay{position:fixed;inset:0;z-index:99990;background:#000;pointer-events:none;transition:opacity 0.3s;}',

      // ── GTA V override ─────────────────────────────────────────────────────
      (theme.preset==='gta'?[
        '#km-menu{border-top:3px solid #f5c518!important;border-left:3px solid #f5c518!important;background:linear-gradient(120deg,rgba(18,12,8,0.95),rgba(25,24,18,0.95))!important;}',
        '#km-menu::before{content:"";position:absolute;inset:0;background:radial-gradient(circle at 20% 20%, rgba(255,255,255,0.08), transparent 50%), radial-gradient(circle at 80% 80%, rgba(0,120,255,0.12), transparent 45%);opacity:0.65;pointer-events:none;}',
        '#km-header{border-bottom:1px solid rgba(245,197,24,0.25)!important;background:rgba(0,0,0,0.4)!important;}',
        '#km-title{font-size:16px!important;letter-spacing:5px!important;color:#f5c518!important;text-transform:uppercase!important;font-weight:800!important;}',
        '.kmt.active{color:#f5c518!important;border-bottom-color:#f5c518!important;}',
        '.km-section{color:rgba(245,197,24,0.65)!important;}',
        '.km-stat-row>span:first-child{color:rgba(245,197,24,0.35)!important;}',
        '.km-btn{border-color:rgba(245,197,24,0.25)!important;color:#f5c518!important;}',
        '.km-btn:hover{border-color:#f5c518!important;color:#fff!important;background:rgba(245,197,24,0.2)!important;}',
      ].join(''):theme.preset==='rl'?[
        '#km-menu{border-top:3px solid #0096ff!important;}',
        '#km-title{font-size:22px!important;letter-spacing:3px!important;}',
        '.km-rank-mmr{font-size:20px!important;}',
        '.km-stat-val{font-size:22px!important;}',
      ].join(''):theme.preset==='minimal'?[
        '#km-menu{border:none!important;border-top:1px solid rgba(255,255,255,0.1)!important;}',
        '#km-header{border-bottom:none!important;padding:8px 14px!important;}',
        '#km-title{font-size:13px!important;letter-spacing:6px!important;color:rgba(255,255,255,0.9)!important;font-family:"DM Mono",monospace!important;}',
        '.kmt{font-size:8px!important;letter-spacing:2px!important;color:rgba(255,255,255,0.2)!important;}',
        '.kmt.active{color:rgba(255,255,255,0.9)!important;border-bottom-color:rgba(255,255,255,0.9)!important;}',
        '.km-section{color:rgba(255,255,255,0.15)!important;}',
        '.km-stat-row>span:first-child{color:rgba(255,255,255,0.2)!important;}',
        '.km-btn{border-color:rgba(255,255,255,0.07)!important;color:rgba(255,255,255,0.4)!important;}',
        '.km-rank-mmr{color:rgba(255,255,255,0.9)!important;}',
        '.km-plugin-card{border-left:none!important;}',
        '.km-plugin-card.installed{border-left:1px solid rgba(255,255,255,0.4)!important;}',
      ].join(''):''),

      // ── Esports override ───────────────────────────────────────────────────
      (theme.preset==='esports'?[
        '#km-menu{border-top:2px solid #ff3c00!important;border-left:2px solid #ff3c00!important;background:rgba(2,2,6,0.98)!important;}',
        '#km-title{font-size:18px!important;letter-spacing:6px!important;color:#ff3c00!important;}',
        '.kmt.active{color:#ff3c00!important;border-bottom-color:#ff3c00!important;}',
        '.km-section{color:rgba(255,60,0,0.5)!important;}',
        '.km-rank-mmr{color:#ff3c00!important;}',
        '.km-stat-val{color:#ff3c00!important;}',
        '.km-btn:hover{border-color:#ff3c00!important;color:#ff3c00!important;}',
        '.km-plugin-card.installed{border-left-color:#ff3c00!important;}',
      ].join(''):''),

      // ── Org team CSS overrides ─────────────────────────────────────────────
      (theme.preset==='faze'?[
        '#km-title{color:#d40000!important;text-shadow:0 0 20px rgba(212,0,0,0.5);}',
        '.km-rank-mmr{color:#d40000!important;}',
        '.km-stat-val{color:#d40000!important;}',
        '.kmt.active{color:#d40000!important;border-bottom-color:#d40000!important;}',
        '.km-section::after{background:rgba(212,0,0,0.3)!important;}',
        '.km-btn:hover{border-color:#d40000!important;color:#d40000!important;}',
      ].join(''):theme.preset==='nrg'?[
        '#km-menu{position:relative;overflow:hidden;}',
        '#km-menu::before{content:"";position:absolute;inset:0;background:radial-gradient(circle at 50% 30%, rgba(255,255,255,0.08), transparent 55%), linear-gradient(135deg, rgba(255,255,255,0.04), rgba(255,255,255,0.0));opacity:0.5;pointer-events:none;}',
        '#km-title{color:#00a8ff!important;text-shadow:0 0 20px rgba(0,168,255,0.4);}',
        '.km-rank-mmr{color:#00a8ff!important;}',
        '.km-stat-val{color:#00a8ff!important;}',
        '.kmt.active{color:#00a8ff!important;border-bottom-color:#00a8ff!important;}',
        '.km-plugin-card.installed{border-left-color:#00a8ff!important;}',
      ].join(''):theme.preset==='vitality'?[
        '#km-menu{position:relative;overflow:hidden;}',
        '#km-menu::before{content:"";position:absolute;inset:0;background:radial-gradient(circle at 70% 20%, rgba(255,255,0,0.18), transparent 48%), linear-gradient(150deg, rgba(0,0,0,0.5), rgba(0,0,0,0.15));opacity:0.5;pointer-events:none;}',
        '#km-title{color:#ffdc00!important;text-shadow:0 0 20px rgba(255,220,0,0.4);}',
        '#km-menu{border-top-color:#ffdc00!important;}',
        '.km-rank-mmr{color:#ffdc00!important;}',
        '.km-stat-val{color:#ffdc00!important;}',
        '.kmt.active{color:#ffdc00!important;border-bottom-color:#ffdc00!important;}',
        '.km-plugin-card.installed{border-left-color:#ffdc00!important;}',
      ].join(''):theme.preset==='kcorp'?[
        '#km-title{color:#0050ff!important;text-shadow:0 0 20px rgba(0,80,255,0.4);}',
        '#km-menu{border-top-color:#0050ff!important;border-left:2px solid #0050ff!important;}',
        '.km-rank-mmr{color:#0050ff!important;}',
        '.km-stat-val{color:#0050ff!important;}',
        '.kmt.active{color:#0050ff!important;border-bottom-color:#0050ff!important;}',
        '.km-plugin-card.installed{border-left-color:#0050ff!important;}',
      ].join(''):''),

      // ── BakkesMod override ──────────────────────────────────────────────────
      (theme.preset==='bakkesmod'?[
        // Core panel — gunmetal flat, zero blur
        '#km-menu{font-family:Consolas,"Courier New",monospace!important;border:1px solid rgba(255,107,0,0.2)!important;border-top:2px solid #006eff!important;border-radius:0!important;}',
        // Header — dark strip with orange left marker
        '#km-header{background:#111!important;border-bottom:1px solid #222!important;padding:8px 12px!important;}',
        '#km-title{font-family:Consolas,"Courier New",monospace!important;font-size:13px!important;letter-spacing:3px!important;color:#006eff!important;text-transform:uppercase!important;}',
        // Tabs — flat BM-style selector
        '#km-tabs-wrap{background:#111!important;border-bottom:1px solid #333!important;}',
        '.kmt{font-family:Consolas,"Courier New",monospace!important;font-size:9px!important;letter-spacing:1px!important;color:#666!important;padding:8px 12px!important;border-bottom:none!important;}',
        '.kmt:hover{color:#ccc!important;background:rgba(255,255,255,0.04)!important;}',
        '.kmt.active{color:#006eff!important;border-bottom:2px solid #006eff!important;background:#181818!important;}',
        // Pane
        '.km-pane{background:#141414!important;}',
        // Section headers — BM style: orange left border, flat text
        '.km-section{color:#006eff!important;font-family:Consolas,"Courier New",monospace!important;font-size:9px!important;letter-spacing:2px!important;border-left:2px solid #006eff!important;padding-left:6px!important;margin:14px 0 8px!important;}',
        '.km-section::after{display:none!important;}',
        // Stat rows — dense, monospace
        '.km-stat-row{border-bottom:1px solid #222!important;padding:6px 2px!important;}',
        '.km-stat-row>span:first-child{color:#888!important;font-family:Consolas,"Courier New",monospace!important;font-size:10px!important;letter-spacing:0!important;text-transform:none!important;}',
        '.km-stat-row>span:last-child{font-family:Consolas,"Courier New",monospace!important;font-size:12px!important;color:#d4d4d4!important;}',
        // Rank numbers — BM monospace
        '.km-rank-mmr{color:#006eff!important;font-family:Consolas,"Courier New",monospace!important;font-size:16px!important;}',
        '.km-rank-name{font-family:Consolas,"Courier New",monospace!important;letter-spacing:0!important;}',
        '.km-player-name{font-family:Consolas,"Courier New",monospace!important;font-size:18px!important;letter-spacing:1px!important;color:#006eff!important;}',
        // Buttons — BM flat button style
        '.km-btn{border:1px solid #333!important;border-radius:0!important;color:#999!important;font-family:Consolas,"Courier New",monospace!important;font-size:11px!important;text-transform:uppercase!important;letter-spacing:1px!important;}',
        '.km-btn:hover{background:#1e1e1e!important;border-color:#006eff!important;color:#006eff!important;}',
        '.km-btn.danger{border-color:#550000!important;color:#cc3333!important;}',
        '.km-btn.danger:hover{border-color:#f87171!important;color:#f87171!important;background:#1a0000!important;}',
        // Inputs
        '.km-input,.km-select{background:#0e0e0e!important;border:1px solid #333!important;border-radius:0!important;font-family:Consolas,"Courier New",monospace!important;color:#d4d4d4!important;}',
        '.km-select option,.km-input option{background:#111!important;color:#d4d4d4!important;}',
        '.km-input:focus{border-color:#006eff!important;}',
        // Plugin cards — no rounded corners, BM utility style
        '.km-plugin-card{border-left:2px solid transparent!important;border-radius:0!important;background:#181818!important;}',
        '.km-plugin-card:hover{background:#1e1e1e!important;}',
        '.km-plugin-card.installed{border-left-color:#006eff!important;background:#1a1200!important;}',
        '.km-plugin-name{font-family:Consolas,"Courier New",monospace!important;font-size:11px!important;}',
        '.km-plugin-install-btn{border-radius:0!important;font-family:Consolas,"Courier New",monospace!important;text-transform:uppercase!important;letter-spacing:1px!important;}',
        '.km-plugin-install-btn:hover{border-color:#006eff!important;color:#006eff!important;}',
        '.km-plugin-install-btn.installed{border-color:rgba(255,107,0,0.5)!important;color:#006eff!important;}',
        // Owned cards
        '.km-owned-card{border-left:2px solid #333!important;border-radius:0!important;background:#181818!important;}',
        '.km-owned-name{font-family:Consolas,"Courier New",monospace!important;}',
        // HUD — BM-style info bar
        '#km-hud{border-radius:0!important;border-left:2px solid #006eff!important;background:#111!important;font-family:Consolas,"Courier New",monospace!important;}',
        // Leaderboard
        '#km-leaderboard{border-radius:0!important;border-left:2px solid #006eff!important;background:rgba(14,14,14,0.95)!important;font-family:Consolas,"Courier New",monospace!important;}',
        // Console
        '#km-console-body{font-family:Consolas,"Courier New",monospace!important;}',
        '.km-log-row{font-family:Consolas,"Courier New",monospace!important;}',
        // Toggle rows
        '.km-toggle-row label{font-family:Consolas,"Courier New",monospace!important;font-size:11px!important;color:#888!important;}',
        // Preset button active
        '.km-preset-btn.active{border-color:#006eff!important;color:#006eff!important;background:#1a1200!important;}',
        // Tip bar
        '#km-tip-bar{background:#111!important;border-bottom:1px solid #222!important;}',
        '#km-tip-text{font-family:Consolas,"Courier New",monospace!important;color:#555!important;}',
        // Progress bar — orange fill
        '.km-prog-bar{background:#006eff!important;}',
        '.km-prog-wrap{background:#333!important;}',
        // History rows
        '.km-history-row{border-bottom:1px solid #222!important;}',
        '.km-history-mmr{font-family:Consolas,"Courier New",monospace!important;}',
        '.km-history-time{font-family:Consolas,"Courier New",monospace!important;}',
        // Close button
        '#km-close{color:#555!important;}',
        '#km-close:hover{color:#006eff!important;}',
      ].join(''):''),

      // ── macOS Sonoma (14) override — light mode, white frosted glass ────────────
      (theme.preset==='macos14'?[
        // Full white/light mode — macOS Sonoma style, not dark translucent
        '#km-menu{background:#f5f5f7!important;border:none!important;border-radius:12px!important;box-shadow:0 22px 70px rgba(0,0,0,0.35),0 0 0 0.5px rgba(0,0,0,0.18)!important;color:#1d1d1f!important;}',
        // Header — macOS window chrome: traffic lights top-right, title centered
        '#km-header{background:#f5f5f7!important;border-bottom:0.5px solid rgba(0,0,0,0.12)!important;padding:10px 14px!important;position:relative!important;}',
        // Show traffic lights, hide default close button
        '#km-traffic-lights{display:flex!important;}',
        '#km-close{display:none!important;}',
        '#km-fullscreen-btn{display:none!important;}',,
        '#km-title{font-family:-apple-system,"SF Pro Display",sans-serif!important;font-size:13px!important;font-weight:600!important;color:#1d1d1f!important;letter-spacing:-0.2px!important;opacity:1!important;}',
        '#km-wip-badge{color:rgba(0,0,0,0.35)!important;}',
        // Traffic lights — red/yellow/green circles, top-right of header
        '#km-header-right{gap:6px!important;}',
        '#km-close{width:12px!important;height:12px!important;border-radius:50%!important;background:#ff5f57!important;border:none!important;padding:0!important;font-size:0!important;position:relative!important;cursor:pointer!important;transition:all 0.1s!important;}',
        '#km-close::after{content:"✕";position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:8px;color:rgba(0,0,0,0.6);opacity:0;}',
        '#km-close:hover::after{opacity:1!important;}',
        '#km-close:hover{background:#ff5f57!important;box-shadow:0 0 0 2px rgba(255,95,87,0.3)!important;}',
        // Traffic light pill container
        '#km-discord-btn{background:transparent!important;border:none!important;color:#007aff!important;font-size:11px!important;padding:0 8px!important;}',
        // Tabs — macOS sidebar style
        '#km-tabs-wrap{background:#ebebeb!important;border-bottom:0.5px solid rgba(0,0,0,0.1)!important;}',
        '.kmt{font-family:-apple-system,"SF Pro Text",sans-serif!important;font-size:12px!important;font-weight:400!important;color:rgba(0,0,0,0.5)!important;letter-spacing:0!important;padding:8px 12px!important;}',
        '.kmt:hover{color:rgba(0,0,0,0.8)!important;background:rgba(0,0,0,0.05)!important;}',
        '.kmt.active{color:#007aff!important;font-weight:600!important;border-bottom-color:#007aff!important;background:rgba(0,122,255,0.06)!important;}',
        // Pane background
        '.km-pane{background:#f5f5f7!important;color:#1d1d1f!important;}',
        // Section headers — macOS settings style
        '.km-section{font-family:-apple-system,"SF Pro Text",sans-serif!important;font-size:11px!important;font-weight:600!important;color:rgba(0,0,0,0.35)!important;text-transform:uppercase!important;letter-spacing:0.6px!important;margin:14px 0 6px!important;}',
        '.km-section::after{display:none!important;}',
        // Stat rows
        '.km-stat-row{border-bottom:0.5px solid rgba(0,0,0,0.08)!important;}',
        '.km-stat-row>span:first-child{font-family:-apple-system,"SF Pro Text",sans-serif!important;color:rgba(0,0,0,0.55)!important;}',
        '.km-stat-row>span:last-child{color:#1d1d1f!important;}',
        // Rank
        '.km-rank-mmr{color:#007aff!important;}',
        '.km-rank-name{font-family:-apple-system,"SF Pro Display",sans-serif!important;font-weight:600!important;color:#1d1d1f!important;}',
        '.km-rank-mode{color:rgba(0,0,0,0.4)!important;}',
        '.km-rank-wl{color:rgba(0,0,0,0.35)!important;}',
        '.km-player-name{color:#1d1d1f!important;font-family:-apple-system,sans-serif!important;}',
        '.km-player-xp{color:rgba(0,0,0,0.35)!important;}',
        // Buttons — iOS button style
        '.km-btn{border-radius:9px!important;font-family:-apple-system,"SF Pro Text",sans-serif!important;font-size:13px!important;font-weight:500!important;border:none!important;background:rgba(0,122,255,0.1)!important;color:#007aff!important;letter-spacing:0!important;}',
        '.km-btn:hover{background:rgba(0,122,255,0.2)!important;}',
        '.km-btn.danger{background:rgba(255,59,48,0.08)!important;color:#ff3b30!important;}',
        '.km-btn.danger:hover{background:rgba(255,59,48,0.16)!important;}',
        // Inputs
        '.km-input,.km-select{border-radius:8px!important;font-family:-apple-system,"SF Pro Text",sans-serif!important;background:rgba(0,0,0,0.04)!important;border:0.5px solid rgba(0,0,0,0.15)!important;color:#1d1d1f!important;}',
        '.km-select option,.km-input option{background:#f5f5f7!important;color:#1d1d1f!important;}',
        '.km-input:focus{border-color:#007aff!important;box-shadow:0 0 0 3px rgba(0,122,255,0.15)!important;}',
        // Toggle rows
        '.km-toggle-row{border-bottom:0.5px solid rgba(0,0,0,0.08)!important;}',
        '.km-toggle-row label{font-family:-apple-system,"SF Pro Text",sans-serif!important;color:rgba(0,0,0,0.7)!important;}',
        // Plugin cards
        '.km-plugin-card{border-radius:10px!important;border-left:none!important;background:rgba(255,255,255,0.8)!important;margin-bottom:4px!important;box-shadow:0 1px 3px rgba(0,0,0,0.06)!important;}',
        '.km-plugin-card:hover{background:#fff!important;}',
        '.km-plugin-card.installed{background:rgba(0,122,255,0.06)!important;border:0.5px solid rgba(0,122,255,0.25)!important;}',
        '.km-plugin-name{color:#1d1d1f!important;font-family:-apple-system,sans-serif!important;}',
        '.km-plugin-cat{color:rgba(0,0,0,0.4)!important;}',
        '.km-plugin-desc{color:rgba(0,0,0,0.5)!important;}',
        '.km-plugin-install-btn{border-radius:8px!important;font-family:-apple-system,sans-serif!important;font-weight:500!important;background:rgba(0,122,255,0.08)!important;border-color:rgba(0,122,255,0.2)!important;color:#007aff!important;}',
        // Empty state
        '.km-empty{color:rgba(0,0,0,0.3)!important;}',
        // History rows
        '.km-history-row{border-bottom:0.5px solid rgba(0,0,0,0.07)!important;}',
        '.km-history-mmr{color:rgba(0,0,0,0.45)!important;}',
        '.km-history-time{color:rgba(0,0,0,0.3)!important;}',
        // HUD
        '#km-hud{border-radius:12px!important;background:rgba(245,245,247,0.92)!important;border:0.5px solid rgba(0,0,0,0.15)!important;border-left:none!important;font-family:-apple-system,sans-serif!important;color:#1d1d1f!important;box-shadow:0 4px 16px rgba(0,0,0,0.1)!important;}',
        '#km-hud-fps{color:#007aff!important;}',
        '#km-hud-ping{color:#34c759!important;}',
        // Leaderboard
        '#km-leaderboard{border-radius:12px!important;background:rgba(245,245,247,0.95)!important;border:0.5px solid rgba(0,0,0,0.12)!important;border-left:none!important;font-family:-apple-system,sans-serif!important;}',
        '.km-lb-name{color:#1d1d1f!important;}',
        '.km-lb-score{color:#1d1d1f!important;}',
        // Progress bar
        '.km-prog-bar{background:#007aff!important;}',
        '.km-prog-wrap{background:rgba(0,0,0,0.08)!important;}',
        // Preset buttons
        '.km-preset-btn{color:rgba(0,0,0,0.5)!important;background:rgba(0,0,0,0.04)!important;border-color:rgba(0,0,0,0.1)!important;}',
        '.km-preset-btn.active{border-color:#007aff!important;color:#007aff!important;background:rgba(0,122,255,0.06)!important;}',
        // Tip bar
        '#km-tip-bar{background:#ebebeb!important;border-bottom:0.5px solid rgba(0,0,0,0.1)!important;}',
        '#km-tip-text{color:rgba(0,0,0,0.45)!important;}',
        '#km-tip-close{color:rgba(0,0,0,0.3)!important;}',
        // Close
        '#km-ping{color:#34c759!important;}',
        '#km-discord-label{color:rgba(0,0,0,0.4)!important;}',
        // Owned cards
        '.km-owned-card{background:rgba(255,255,255,0.8)!important;border-left:none!important;box-shadow:0 1px 3px rgba(0,0,0,0.05)!important;}',
        '.km-owned-name{color:#1d1d1f!important;}',
        '.km-owned-header:hover{background:rgba(0,0,0,0.03)!important;}',
        // Console
        '#km-console-body{background:#f5f5f7!important;}',
        '.km-log-row{color:rgba(0,0,0,0.5)!important;}',
        '.km-log-row.hi{color:#007aff!important;}',
        // Store filters
        '.km-store-cat{color:rgba(0,0,0,0.4)!important;background:rgba(0,0,0,0.03)!important;border-color:rgba(0,0,0,0.1)!important;}',
        '.km-store-cat.active,.km-store-cat:hover{color:#007aff!important;border-color:rgba(0,122,255,0.3)!important;background:rgba(0,122,255,0.06)!important;}',
      ].join(''):''),

      // ── Arch Linux override ────────────────────────────────────────────────
      (isArch?[
        '#km-menu{font-family:"JetBrains Mono",monospace!important;}',
        '.kmt{font-size:8px!important;letter-spacing:1px!important;}',
        '.km-section{color:#1793d1;}',
        '#km-title::before{content:"[";color:rgba(23,147,209,0.5);}',
        '#km-title::after{content:"]";color:rgba(23,147,209,0.5);}',
        '.km-stat-row>span:first-child::before{content:"> ";color:#1793d1;}',
      ].join(''):
      isWindows?[
        '.km-section{color:#0078d4;}',
        '.kmt{font-family:"Segoe UI",sans-serif!important;}',
      ].join(''):''),

      // ── Input overlay & shot analysis ─────────────────────────────────────
      '#km-input-overlay{position:fixed;z-index:99996;pointer-events:none;user-select:none;}',

      // ── Console ────────────────────────────────────────────────────────────
      '#km-console-body{height:360px;overflow-y:auto;padding:10px 14px;font-family:"DM Mono",monospace;font-size:10px;line-height:1.65;scrollbar-width:thin;scrollbar-color:rgba(255,255,255,0.06) transparent;}',
      '#km-console-body::-webkit-scrollbar{width:2px;}',
      '#km-console-body::-webkit-scrollbar-thumb{background:rgba(255,255,255,0.06);}',
      '.km-log-row{color:rgba(255,255,255,0.2);word-break:break-all;margin:1px 0;}',
      '.km-log-row.warn{color:#fbbf24;}',
      '.km-log-row.err{color:#f87171;}',
      '.km-log-row.hi{color:'+a+';}',
    ].join('');
  }

  function injectStyles() {
    var s=document.createElement('style'); s.id='km-style';
    s.textContent=buildCSS();
    document.head.appendChild(s);
  }

  // ─── BOOT ─────────────────────────────────────────────────────────────────
  function updatePartyButton() {
    var label = document.getElementById('km-discord-label');
    var btn   = document.getElementById('km-discord-btn');
    if (!label || !btn) return;
    if (currentPartyCode) {
      label.textContent = currentPartyCode;
      btn.classList.add('has-code');
    } else {
      label.textContent = 'No Party';
      btn.classList.remove('has-code');
    }
  }

  function updatePartyTab() {
    // Party status
    var statusEl = document.getElementById('km-party-status');
    if (statusEl) {
      if (partyActive && currentPartyCode) {
        statusEl.innerHTML = [
          '<div class="km-party-code-box">',
            '<div class="km-party-code-label">Party Code</div>',
            '<div class="km-party-code-num">' + currentPartyCode + '</div>',
            '<button class="km-btn" id="km-copy-party-code">📋 Copy Code</button>',
            '<button class="km-btn" id="km-copy-party-msg">&#128172; Copy for Discord</button>',
          '</div>',
        ].join('');
        var copyBtn = document.getElementById('km-copy-party-code');
        if (copyBtn) copyBtn.addEventListener('click', function() {
          navigator.clipboard.writeText(currentPartyCode).catch(function() { fallbackCopy(currentPartyCode); });
          showToast('Code copied: ' + currentPartyCode, 'ok');
          kmLog('Party code copied: ' + currentPartyCode);
        });
        var msgBtn = document.getElementById('km-copy-party-msg');
        if (msgBtn) msgBtn.addEventListener('click', function() {
          var nick = playerData ? playerData.Nickname : 'me';
          var msg = '\uD83C\uDFAE Join my RocketGoal.io party!\nCode: **' + currentPartyCode + '**\n*(invite from @keydopz via KeyMod)*';
          navigator.clipboard.writeText(msg).catch(function() { fallbackCopy(msg); });
          showToast('Discord message copied!', 'ok');
          kmLog('Party discord message copied');
        });
      } else {
        statusEl.innerHTML = '';
      }
    }

    // Update HUD party indicator
    var hudParty = document.getElementById('km-hud-party');
    if (hudParty) {
      if (partyActive && currentPartyCode) {
        hudParty.textContent = '🎮 ' + currentPartyCode + ' — CLICK ME';
        hudParty.style.display = 'inline';
      } else {
        hudParty.style.display = 'none';
      }
    }
    // Update Discord label in header
    var discLabel = document.getElementById('km-discord-label');
    if(discLabel) {
      discLabel.textContent = (partyActive && currentPartyCode) ? currentPartyCode : 'No Party';
      discLabel.style.color = (partyActive && currentPartyCode) ? '#f5c518' : 'rgba(255,255,255,0.3)';
    }

    // Recent codes
    var recentEl = document.getElementById('km-recent-codes');
    if (recentEl) {
      if (!recentCodes.length) {
        recentEl.innerHTML = '<div class="km-empty">No recent codes</div>';
      } else {
        recentEl.innerHTML = '';
        recentCodes.forEach(function(c, ci) {
          var row = document.createElement('div');
          row.className = 'km-history-row';
          var span = document.createElement('span');
          span.style.cssText = 'color:#f5c518;font-size:15px;font-weight:700;letter-spacing:2px';
          span.textContent = c;
          var btn = document.createElement('button');
          btn.className = 'km-btn';
          btn.style.cssText = 'margin-top:0;width:auto;padding:3px 10px;font-size:11px';
          btn.textContent = 'Copy';
          btn.addEventListener('click', function() {
            navigator.clipboard.writeText(c).catch(function(){fallbackCopy(c);});
            showToast('Copied: ' + c, 'ok');
          });
          row.appendChild(span);
          row.appendChild(btn);
          recentEl.appendChild(row);
        });
      }
    }

  }

  // ─── PLUGIN SYSTEM ────────────────────────────────────────────────────────
  function updateMomentumDisplay() {
    var el = document.getElementById('plug-momentumMeter-stats'); if(!el) return;
    var m = pluginState.momentum;
    var col = m > 60 ? '#4ade80' : m > 35 ? '#fbbf24' : '#f87171';
    el.innerHTML = '<div style="margin-top:4px">'
      +'<div style="font-size:10px;color:rgba(255,255,255,0.4);margin-bottom:4px">Momentum: '+m+'%</div>'
      +'<div class="km-prog-wrap" style="height:8px"><div class="km-prog-bar" style="width:'+m+'%;background:'+col+'"></div></div>'
      +'</div>';
  }

  function showSessionLockOverlay(losses, limit) {
    var el = document.createElement('div');
    el.id = 'km-session-lock';
    el.innerHTML = '<div id="km-sl-box">'
      +'<div id="km-sl-icon">🔒</div>'
      +'<div id="km-sl-title">Session Lock</div>'
      +'<div id="km-sl-body">You have lost <strong>'+losses+' matches</strong> (limit: '+limit+').<br>Consider taking a break to avoid tilt.</div>'
      +'<button id="km-sl-dismiss">Take a break</button>'
      +'<button id="km-sl-override">Keep playing anyway</button>'
      +'</div>';
    document.body.appendChild(el);
    el.querySelector('#km-sl-dismiss').addEventListener('click', function(){el.remove();});
    el.querySelector('#km-sl-override').addEventListener('click', function(){
      pluginState.sessionLockTriggered = false; el.remove();
    });
  }

  function updateBannerPreview() {
    var el = document.getElementById('km-banner-preview'); if(!el) return;
    var nick = playerData ? (playerData.Nickname||'You') : 'You';
    var name = profileCustom.bannerText || nick;
    if(profileCustom.bannerUrl) {
      el.style.backgroundImage = 'url('+profileCustom.bannerUrl+')';
      el.style.backgroundSize  = 'cover';
      el.style.backgroundPosition = 'center';
      el.style.background = '';
    } else {
      el.style.backgroundImage = '';
      el.style.background = 'linear-gradient(135deg,'+profileCustom.bannerColor+'dd,'+profileCustom.bannerColor+'66)';
    }
    var av = '';
    el.innerHTML = '<div class="km-banner-bg-overlay"></div>'
      +'<div class="km-banner-content">'
        +av
        +'<div class="km-banner-text-wrap">'
          +'<div class="km-banner-goal" style="font-size:20px">⚽ GOAL!</div>'
          +'<div class="km-banner-player">'+name+'</div>'
        +'</div>'
      +'</div>';
  }

  // ─── PARTY CODE POPUP ────────────────────────────────────────────────────
  function showPartyCodePopup(partyCode) {
    var old = document.getElementById('km-party-popup'); if(old) old.remove();
    var nick = playerData ? (playerData.Nickname||'Player') : 'Player';
    var msg = '🎮 Join my RocketGoal.io party!\nCode: '+partyCode+'\n(via KeyMod by @keydopz)';

    var el = document.createElement('div');
    el.id = 'km-party-popup';
    el.innerHTML = [
      '<div id="km-party-header">',
        '<span id="km-party-title">🎮 Party Created!</span>',
        '<button id="km-party-close">&#x2715;</button>',
      '</div>',
      '<div id="km-party-code">'+partyCode+'</div>',
      '<div id="km-party-sub">Share this code with your friends</div>',
      '<div id="km-party-btns">',
        '<button class="km-party-btn" id="km-party-copy-code">📋 Copy Code</button>',
        '<button class="km-party-btn primary" id="km-party-copy-msg">💬 Copy Discord Message</button>',
      '</div>',
    ].join('');
    document.body.appendChild(el);

    el.querySelector('#km-party-close').addEventListener('click', function(){
      el.style.opacity='0'; setTimeout(function(){el.remove();},250);
    });
    el.querySelector('#km-party-copy-code').addEventListener('click', function(){
      navigator.clipboard.writeText(partyCode).then(function(){showToast('Code copied!','ok');}).catch(function(){fallbackCopy(partyCode);});
      kmLog('Party code copied: '+partyCode);
    });
    el.querySelector('#km-party-copy-msg').addEventListener('click', function(){
      navigator.clipboard.writeText(msg).then(function(){showToast('Discord message copied!','ok');}).catch(function(){fallbackCopy(msg);});
      kmLog('Party message copied');
    });

    // Animate in
    setTimeout(function(){el.classList.add('show');},10);
  }

  // ─── SUPPORT POPUP ───────────────────────────────────────────────────────
  function buildSupportPopup() {
    var overlay = document.createElement('div');
    overlay.id = 'km-support-overlay';
    overlay.innerHTML = [
      '<div id="km-support-box">',
        '<button id="km-support-close">&#x2715;</button>',
        '<div id="km-support-icon">',
          '<svg viewBox="0 0 48 48" width="52" height="52" xmlns="http://www.w3.org/2000/svg">',
            '<path d="M38.9 10.8a13.1 13.1 0 0 1-7.8-7.8h-6v28.6a5.5 5.5 0 1 1-3.9-5.3V20a12 12 0 1 0 10 11.6V19a19.3 19.3 0 0 0 11.3 3.6v-5.9a13.2 13.2 0 0 1-3.6-.9z" fill="white"/>',
            '<path d="M38.9 10.8a13.1 13.1 0 0 1-7.8-7.8h-6v28.6a5.5 5.5 0 1 1-3.9-5.3V20a12 12 0 1 0 10 11.6V19a19.3 19.3 0 0 0 11.3 3.6v-5.9a13.2 13.2 0 0 1-3.8-5.9z" fill="#69C9D0" opacity="0.5"/>',
            '<path d="M31.1 3h-6v28.6a5.5 5.5 0 1 1-3.9-5.3V20a12 12 0 1 0 10 11.6V19a19.3 19.3 0 0 0 11.3 3.6v-5.9A13.1 13.1 0 0 1 31.1 3z" fill="#EE1D52" opacity="0.7"/>',
          '</svg>',
        '</div>',
        '<div id="km-support-title">Support me?</div>',
        '<div id="km-support-body">',
          '<strong>Follow @keydopz on TikTok</strong><br>',
          '<span>New RocketGoal tutorials and setup videos drop weekly.</span>',
        '</div>',
        '<a id="km-support-btn" href="https://www.tiktok.com/@keydopz" target="_blank">OPEN TIKTOK</a>',
      '</div>',
    ].join('');
    document.body.appendChild(overlay);
    overlay.querySelector('#km-support-close').addEventListener('click', function() {
      overlay.style.opacity = '0';
      setTimeout(function() { overlay.remove(); }, 300);
    });
    overlay.querySelector('#km-support-btn').addEventListener('click', function() {
      overlay.style.opacity = '0';
      setTimeout(function() { overlay.remove(); }, 300);
    });
  }

  // ─── RESIZE HANDLE ────────────────────────────────────────────────────────
  function addResizeHandle() {
    var menu = document.getElementById('km-menu'); if (!menu) return;

    var edges = [
      { id:'km-rh-e',  cursor:'e-resize',  style:'position:absolute;top:20px;right:-4px;width:8px;height:calc(100% - 40px);cursor:e-resize;z-index:10;' },
      { id:'km-rh-w',  cursor:'w-resize',  style:'position:absolute;top:20px;left:-4px;width:8px;height:calc(100% - 40px);cursor:w-resize;z-index:10;' },
      { id:'km-rh-s',  cursor:'s-resize',  style:'position:absolute;bottom:-4px;left:20px;height:8px;width:calc(100% - 40px);cursor:s-resize;z-index:10;' },
      { id:'km-rh-se', cursor:'se-resize', style:'position:absolute;bottom:-4px;right:-4px;width:16px;height:16px;cursor:se-resize;z-index:11;' },
      { id:'km-rh-sw', cursor:'sw-resize', style:'position:absolute;bottom:-4px;left:-4px;width:16px;height:16px;cursor:sw-resize;z-index:11;' },
    ];

    edges.forEach(function(edge) {
      var h = document.createElement('div');
      h.id = edge.id; h.style.cssText = edge.style;
      menu.appendChild(h);

      h.addEventListener('mousedown', function(e) {
        e.preventDefault(); e.stopPropagation();
        var startX = e.clientX, startY = e.clientY;
        var startW = menu.offsetWidth, startH = menu.offsetHeight;
        var startL = menu.offsetLeft;

        function onMove(e) {
          var dx = e.clientX - startX, dy = e.clientY - startY;
          var newW = startW, newH = startH, newL = startL;
          var minW = 360, minH = 280;

          if (edge.cursor.includes('e'))  newW = Math.max(minW, startW + dx);
          if (edge.cursor.includes('s'))  newH = Math.max(minH, startH + dy);
          if (edge.cursor.includes('w'))  { newW = Math.max(minW, startW - dx); newL = startL + (startW - newW); }

          menu.style.width  = newW + 'px';
          menu.style.left   = newL + 'px';
          var paneH = Math.max(200, newH - 120);
          menu.querySelectorAll('.km-pane').forEach(function(p){p.style.maxHeight=paneH+'px';});
          var cb = document.getElementById('km-console-body');
          if(cb) cb.style.height = paneH + 'px';
        }
        function onUp() {
          document.removeEventListener('mousemove', onMove);
          document.removeEventListener('mouseup', onUp);
        }
        document.addEventListener('mousemove', onMove);
        document.addEventListener('mouseup', onUp);
      });
    });

    // SE corner visual indicator
    var se = document.getElementById('km-rh-se');
    if(se) se.innerHTML = '<svg width="10" height="10" viewBox="0 0 10 10" style="position:absolute;bottom:3px;right:3px;opacity:0.3"><path d="M10 10 L10 6 L6 10 Z M10 5 L10 3 L3 10 L5 10 Z" fill="white"/></svg>';
  }

  // ─── ON-SCREEN KEYBOARD ─────────────────────────────────────────────────────
  function attachOSKToMenu(el) {
    // el can be passed or auto-detected
    var root = el || document.getElementById('km-menu');
    if (!root) return;

    var searchInput = root.querySelector('#km-plugin-search');
    var oskToggle   = root.querySelector('#km-osk-toggle');
    var oskWrap     = root.querySelector('#km-osk-wrap');
    if (!searchInput || !oskToggle || !oskWrap) return;

    // Keyboard rows
    var rows = [
      ['q','w','e','r','t','y','u','i','o','p'],
      ['a','s','d','f','g','h','j','k','l'],
      ['z','x','c','v','b','n','m'],
    ];

    function buildOSK() {
      oskWrap.innerHTML = '';
      var kb = document.createElement('div');
      kb.style.cssText = 'background:rgba(0,0,0,0.4);border:1px solid rgba(255,255,255,0.1);border-radius:6px;padding:6px;';

      rows.forEach(function(row) {
        var rowEl = document.createElement('div');
        rowEl.style.cssText = 'display:flex;justify-content:center;gap:3px;margin-bottom:3px;';
        row.forEach(function(k) {
          var btn = document.createElement('button');
          btn.textContent = k;
          btn.style.cssText = 'width:28px;height:28px;background:rgba(255,255,255,0.1);border:1px solid rgba(255,255,255,0.15);color:#fff;font-family:"DM Mono",monospace;font-size:11px;cursor:pointer;border-radius:4px;transition:background 0.08s;padding:0;';
          btn.addEventListener('mouseenter', function() { btn.style.background='rgba(255,255,255,0.25)'; });
          btn.addEventListener('mouseleave', function() { btn.style.background='rgba(255,255,255,0.1)'; });
          btn.addEventListener('click', function(e) {
            e.stopPropagation();
            searchInput.value += k;
            renderPluginStore();
          });
          rowEl.appendChild(btn);
        });
        kb.appendChild(rowEl);
      });

      // Bottom row: Space, Backspace, Clear
      var bottomRow = document.createElement('div');
      bottomRow.style.cssText = 'display:flex;justify-content:center;gap:3px;';

      function specBtn(label, w, action) {
        var b = document.createElement('button');
        b.textContent = label;
        b.style.cssText = 'width:'+w+'px;height:28px;background:rgba(255,255,255,0.08);border:1px solid rgba(255,255,255,0.12);color:rgba(255,255,255,0.7);font-family:"DM Mono",monospace;font-size:10px;cursor:pointer;border-radius:4px;transition:background 0.08s;padding:0;';
        b.addEventListener('mouseenter', function() { b.style.background='rgba(255,255,255,0.2)'; });
        b.addEventListener('mouseleave', function() { b.style.background='rgba(255,255,255,0.08)'; });
        b.addEventListener('click', function(e) { e.stopPropagation(); action(); renderPluginStore(); });
        return b;
      }

      bottomRow.appendChild(specBtn('space', 100, function() { searchInput.value += ' '; }));
      bottomRow.appendChild(specBtn('⌫', 40, function() { searchInput.value = searchInput.value.slice(0,-1); }));
      bottomRow.appendChild(specBtn('✕ clear', 64, function() { searchInput.value = ''; }));
      kb.appendChild(bottomRow);
      oskWrap.appendChild(kb);
    }

    var oskVisible = false;
    oskToggle.addEventListener('click', function(e) {
      e.stopPropagation();
      oskVisible = !oskVisible;
      if (oskVisible) {
        buildOSK();
        oskWrap.style.display = 'block';
        oskToggle.style.color = 'rgba(255,255,255,0.9)';
      } else {
        oskWrap.style.display = 'none';
        oskToggle.style.color = 'rgba(255,255,255,0.4)';
      }
    });

    // Also allow clicking the read-only input to open keyboard
    searchInput.addEventListener('click', function() {
      if (!oskVisible) oskToggle.click();
    });
  }

  // ─── PLUGIN STORE ─────────────────────────────────────────────────────────
  var CATEGORIES = ['All','Training','Stats','Visual','Utility','Fun'];
  var storeFilter = 'All';

  function renderPluginStore() {
    var el = document.getElementById('km-plugin-store-grid');
    if(!el) return;

    // Category filter buttons
    var filterHtml = '<div class="km-store-filters">'
      + CATEGORIES.map(function(c) {
          return '<button class="km-store-cat'+(storeFilter===c?' active':'')+'" data-cat="'+c+'">'+c+'</button>';
        }).join('')
      + '</div>';

    var searchQ = (document.getElementById('km-plugin-search')||{value:''}).value.toLowerCase().trim();
    var plugins = (storeFilter === 'All' ? PLUGINS : PLUGINS.filter(function(p){return p.category===storeFilter;}))
      .filter(function(p){ return !searchQ || p.name.toLowerCase().includes(searchQ) || p.desc.toLowerCase().includes(searchQ); });

    var cardsHtml = '<div class="km-store-grid">'
      + plugins.map(function(p) {
          var inst = isInstalled(p.id);
          return '<div class="km-plugin-card">'
            + '<div class="km-plugin-icon">'+p.icon+'</div>'
            + '<div class="km-plugin-info">'
              + '<div class="km-plugin-name">'+p.name+'</div>'
              + '<div class="km-plugin-cat">'+p.category+' &middot; v'+p.version+'</div>'
              + '<div class="km-plugin-desc">'+p.desc+'</div>'
            + '</div>'
            + '<button class="km-plugin-install-btn'+(inst?' installed':'')+'" data-id="'+p.id+'">'
              + (inst ? '✓ Installed' : 'Install')
            + '</button>'
            + '</div>';
        }).join('')
      + '</div>';

    el.innerHTML = filterHtml + cardsHtml;

    // Wire filter buttons
    var searchInput = document.getElementById('km-plugin-search');
    if (searchInput) searchInput.addEventListener('input', function() { renderPluginStore(); });

    el.querySelectorAll('.km-store-cat').forEach(function(btn) {
      btn.addEventListener('click', function() {
        storeFilter = btn.dataset.cat;
        renderPluginStore();
      });
    });

    // Wire install buttons
    el.querySelectorAll('.km-plugin-install-btn').forEach(function(btn) {
      btn.addEventListener('click', function(e) {
        try {
          e.stopPropagation();
          var id = btn.dataset.id;
          kmLog('Install button clicked: '+id);
          if(isInstalled(id)) {
            installedPlugins = installedPlugins.filter(function(x){return x!==id;});
            showToast('Plugin uninstalled', 'warn');
            kmLog('Plugin uninstalled: '+id);
          } else {
            installedPlugins.push(id);
            showToast('Plugin installed! ✓', 'ok');
            kmLog('Plugin installed: '+id);
            PLUGINS.forEach(function(p) {
              if(p.id===id) {
                p.settings.forEach(function(s) {
                  if(!pluginSettings[id]) pluginSettings[id]={};
                  if(pluginSettings[id][s.id]===undefined) pluginSettings[id][s.id]=s.default;
                });
              }
            });
            lsSet('pluginSettings5', pluginSettings);
            if (id==='perfMetrics')   setTimeout(pluginPerfMetricsMount, 50);
            if (id==='screenshotRec' || id==='clipRecorder' || id==='streamerMode' || id==='devTools') setTimeout(pluginStreamerMount, 50);
            if (id==='trollPlugin')   {} // troll never auto-starts — session flag only
            if (id==='trollPlugin')   {} // Troll never auto-mounts — session flag only
            if (id==='peakGhost')     { pluginPeakGhostInit(); setTimeout(pluginPeakGhostUpdate,200); }
            if (id==='optimizer')        setTimeout(pluginOptimizerMount, 50);
            if (id==='visualFX')        setTimeout(pluginVFXMount, 50);
            if (id==='dejaVu')          showToast('🗂️ Déjà Vu active', 'ok');
          }
          lsSet('plugins5', installedPlugins);
          renderPluginStore();
          renderOwnedPlugins();
          setTimeout(function(){attachOSKToMenu();},50);
        } catch(err) {
          kmLog('Install button error: '+(err&&err.message||String(err)));
          showToast('Error: '+(err&&err.message||'unknown'), 'err');
        }
      });
    });
  }

  // ─── OWNED PLUGINS ────────────────────────────────────────────────────────
  function renderOwnedPlugins() {
    var el = document.getElementById('km-owned-plugins-list');
    if(!el) return;
    var owned = PLUGINS.filter(function(p){return isInstalled(p.id);});
    if(!owned.length) {
      el.innerHTML = '<div class="km-empty">No plugins installed — visit Plugin Store</div>';
      return;
    }
    el.innerHTML = owned.map(function(p) {
      // Optimizer gets a custom checkbox group renderer
      var settingsHtml;
      var hasCheckboxes = p.settings.length && p.settings[0].type === 'checkbox';
      if (hasCheckboxes) {
        // Build Check All / Uncheck All header
        var checkAllHtml = '<div style="display:flex;gap:8px;margin-bottom:10px;padding-bottom:8px;border-bottom:1px solid rgba(255,255,255,0.08);">'
          + '<button class="km-btn km-opt-checkall" data-plugin="'+p.id+'" data-action="all" style="margin-top:0;padding:5px 12px;font-size:11px;flex:1;">✓ Check All</button>'
          + '<button class="km-btn km-opt-checkall" data-plugin="'+p.id+'" data-action="none" style="margin-top:0;padding:5px 12px;font-size:11px;flex:1;">✗ Uncheck All</button>'
          + '</div>';
        // Group settings by their group field
        var groups = [], groupMap = {};
        p.settings.forEach(function(s) {
          var g = s.group || 'General';
          if (!groupMap[g]) { groupMap[g] = []; groups.push(g); }
          groupMap[g].push(s);
        });
        var groupsHtml = groups.map(function(g) {
          var rows = groupMap[g].map(function(s) {
            var val = getPluginSetting(p.id, s.id);
            var checked = (val === 'on' || val === true) ? 'checked' : '';
            return '<div class="km-toggle-row">'
              + '<label>'
              + '<input type="checkbox" class="km-plugin-setting km-opt-cb" data-plugin="'+p.id+'" data-setting="'+s.id+'"'
              + (checked ? ' checked' : '') + '>'
              + s.label
              + '</label>'
              + '</div>';
          }).join('');
          return '<div style="margin-bottom:10px;">'
            + '<div style="font-size:10px;font-weight:600;color:rgba(255,255,255,0.3);margin-bottom:6px;letter-spacing:0;">'+g+'</div>'
            + rows
            + '</div>';
        }).join('');
        settingsHtml = checkAllHtml + groupsHtml;
      } else {
        settingsHtml = p.settings.length ? p.settings.map(function(s) {
          var val = getPluginSetting(p.id, s.id);
          if(s.type==='number') {
            return '<div class="km-stat-row"><span>'+s.label+'</span>'
              + '<input type="number" class="km-input km-plugin-setting" data-plugin="'+p.id+'" data-setting="'+s.id+'"'
              + ' value="'+val+'" min="'+(s.min||0)+'" max="'+(s.max||999)+'" style="width:70px"></div>';
          } else if(s.type==='select') {
            var opts = (s.options||[]).map(function(o){
              return '<option value="'+o+'"'+(o===val?' selected':'')+'>'+o.charAt(0).toUpperCase()+o.slice(1)+'</option>';
            }).join('');
            return '<div class="km-stat-row"><span>'+s.label+'</span>'
              + '<select class="km-input km-plugin-setting" data-plugin="'+p.id+'" data-setting="'+s.id+'" style="width:120px">'
              + opts + '</select></div>';
          } else if(s.type==='range') {
            var rMin=s.min!==undefined?s.min:0, rMax=s.max!==undefined?s.max:100;
            var rVal=parseInt(val)||rMin;
            return '<div class="km-stat-row" style="flex-direction:column;align-items:stretch;gap:4px;">'
              +'<div style="display:flex;justify-content:space-between;">'
              +'<span>'+s.label+'</span>'
              +'<span class="km-range-val" style="font-family:monospace;font-size:12px;color:rgba(255,255,255,0.6);" id="km-rv-'+p.id+'-'+s.id+'">'+rVal+'</span>'
              +'</div>'
              +'<input type="range" class="km-plugin-setting km-range-inp" data-plugin="'+p.id+'" data-setting="'+s.id+'"'
              +' value="'+rVal+'" min="'+rMin+'" max="'+rMax+'" style="width:100%;accent-color:var(--km-accent,#006eff);cursor:pointer;">'
              +'</div>';
          } else {
            return '<div class="km-stat-row"><span>'+s.label+'</span>'
              + '<input type="text" class="km-input km-plugin-setting" data-plugin="'+p.id+'" data-setting="'+s.id+'"'
              + ' value="'+val+'" style="width:120px"></div>';
          }
        }).join('') : '<div style="font-size:11px;color:rgba(255,255,255,0.25);padding:4px 0">No settings</div>';
      }

      return '<div class="km-owned-card" id="owned-'+p.id+'">'
        + '<div class="km-owned-header" data-id="'+p.id+'">'
          + '<span class="km-owned-icon">'+p.icon+'</span>'
          + '<span class="km-owned-name">'+p.name+'</span>'
          + '<span class="km-owned-arrow">▾</span>'
        + '</div>'
        + '<div class="km-owned-body" id="owned-body-'+p.id+'" style="display:none">'
          + '<div style="font-size:11px;color:rgba(255,255,255,0.35);margin-bottom:8px">'+p.desc+'</div>'
          + settingsHtml
          + '<button class="km-btn danger km-plugin-uninstall" data-id="'+p.id+'" style="margin-top:8px">Uninstall</button>'
        + '</div>'
        + '</div>';
    }).join('');

    // Wire dropdowns
    el.querySelectorAll('.km-owned-header').forEach(function(hdr) {
      hdr.addEventListener('click', function() {
        var body = document.getElementById('owned-body-'+hdr.dataset.id);
        var arrow = hdr.querySelector('.km-owned-arrow');
        if(body) {
          var open = body.style.display !== 'none';
          body.style.display = open ? 'none' : 'block';
          if(arrow) arrow.textContent = open ? '▾' : '▴';
        }
      });
    });

    // Wire settings inputs (select, number, text)
    el.querySelectorAll('.km-plugin-setting').forEach(function(inp) {
      if (inp.type === 'checkbox') return; // handled separately below
      inp.addEventListener('change', function() {
        var val = inp.type==='number' ? parseInt(inp.value) : inp.value;
        setPluginSetting(inp.dataset.plugin, inp.dataset.setting, val);
        kmLog('Plugin setting updated: '+inp.dataset.plugin+' '+inp.dataset.setting+'='+val);
        if(inp.dataset.plugin==='perfMetrics')  { pluginPerfMetricsUnmount(); pluginPerfMetricsMount(); }
        if(inp.dataset.plugin==='visualFX') {
          pluginVFXUnmount(); pluginVFXMount();
          // If preset changed, re-render owned plugins to update slider positions
          if (inp.dataset.setting === 'preset') setTimeout(renderOwnedPlugins, 80);
        }
        if(inp.dataset.plugin==='nightMode' && inp.dataset.setting==='hour') checkNightMode();
        if(inp.dataset.plugin==='trollPlugin' && inp.dataset.setting==='enabled') {
          // Session-only: troll state lives in window._kmTrollActive, never auto-reads storage
          window._kmTrollActive = (val === 'on');
          if (window._kmTrollActive) { pluginTrollMount(); showToast('🎭 Troll Mode ON — chaos incoming','ok'); }
          else { pluginTrollUnmount(); showToast('Troll Mode off','ok'); }
        }
      });
    });

    // Wire range sliders
    el.querySelectorAll('.km-range-inp').forEach(function(rng) {
      rng.addEventListener('input', function() {
        var val = parseInt(rng.value);
        setPluginSetting(rng.dataset.plugin, rng.dataset.setting, val);
        var lbl = document.getElementById('km-rv-'+rng.dataset.plugin+'-'+rng.dataset.setting);
        if (lbl) lbl.textContent = val;
        if (rng.dataset.plugin === 'visualFX') { pluginVFXUnmount(); pluginVFXMount(); }
      });
    });

    // Wire optimizer checkbox settings
    el.querySelectorAll('.km-opt-cb').forEach(function(cb) {
      cb.addEventListener('change', function() {
        var val = cb.checked ? 'on' : 'off';
        setPluginSetting(cb.dataset.plugin, cb.dataset.setting, val);
        kmLog('Optimizer: ' + cb.dataset.setting + ' = ' + val);
        // Re-run optimizer with new settings
        if (cb.dataset.plugin === 'optimizer') {
          pluginOptimizerUnmount();
          setTimeout(pluginOptimizerMount, 50);
        }
      });
    });

    // Wire Check All / Uncheck All buttons
    el.querySelectorAll('.km-opt-checkall').forEach(function(btn) {
      btn.addEventListener('click', function() {
        var pluginId = btn.dataset.plugin;
        var action = btn.dataset.action; // 'all' or 'none'
        var val = action === 'all' ? 'on' : 'off';
        // Find dangerous settings to skip when checking all
        var dangerousIds = ['stopload','frustum'];
        var card = document.getElementById('owned-body-' + pluginId);
        if (!card) return;
        card.querySelectorAll('.km-opt-cb[data-plugin="' + pluginId + '"]').forEach(function(cb) {
          // Skip dangerous tweaks in check-all to prevent game breaking
          if (action === 'all' && dangerousIds.indexOf(cb.dataset.setting) !== -1) return;
          cb.checked = (action === 'all');
          setPluginSetting(cb.dataset.plugin, cb.dataset.setting, val);
        });
        kmLog('Optimizer: ' + (action === 'all' ? 'all tweaks enabled' : 'all tweaks disabled'));
        // Re-run optimizer
        pluginOptimizerUnmount();
        setTimeout(pluginOptimizerMount, 80);
      });
    });

    // Wire uninstall
    el.querySelectorAll('.km-plugin-uninstall').forEach(function(btn) {
      btn.addEventListener('click', function() {
        var id = btn.dataset.id;
        installedPlugins = installedPlugins.filter(function(x){return x!==id;});
        lsSet('plugins5', installedPlugins);
        // Unmount persistent plugins
        if (id==='perfMetrics')  { pluginPerfMetricsUnmount(); netBridgeStop(); }
        if (id==='screenshotRec' || id==='clipRecorder' || id==='streamerMode' || id==='devTools') { pluginScreenshotUnmount(); pluginClipRecorderUnmount(); pluginDevToolsUnmount(); }
        if (id==='trollPlugin')  { window._kmTrollActive = false; pluginTrollUnmount(); }
        if (id==='trollPlugin')  { window._kmTrollActive = false; pluginTrollUnmount(); }
        if (id==='peakGhost')    pluginPeakGhostUnmount();
        if (id==='optimizer')       pluginOptimizerUnmount();
        if (id==='visualFX')       pluginVFXUnmount();
        showToast('Plugin uninstalled', 'warn');
        kmLog('Plugin uninstalled: '+id);
        renderOwnedPlugins();
        renderPluginStore();
        setTimeout(function(){attachOSKToMenu();},50);
      });
    });
  }

  // ─── PLUGIN RUNTIME ───────────────────────────────────────────────────────
  // Called from relevant game events to run active plugins

  // Speed Run
  var speedRunStart = null;
  var speedRunBest  = lsGet('speedRunBest5', null);

  function pluginSpeedRunMatchStart() {
    if(!isInstalled('speedRun')) return;
    speedRunStart = Date.now();
  }
  function pluginSpeedRunGoal() {
    if(!isInstalled('speedRun') || !speedRunStart) return;
    var elapsed = ((Date.now() - speedRunStart) / 1000).toFixed(1);
    speedRunStart = null;
    if(!speedRunBest || parseFloat(elapsed) < parseFloat(speedRunBest)) {
      speedRunBest = elapsed;
      lsSet('speedRunBest5', speedRunBest);
      showToast('⚡ New Speed Run Best: '+elapsed+'s!', 'ok');
    } else {
      showToast('⚡ First goal: '+elapsed+'s (best: '+speedRunBest+'s)', 'info');
    }
  }

  // AutoGG
  function pluginAutoGG() {
    if(!isInstalled('autoGG')) return;
    var msg = getPluginSetting('autoGG','msg') || 'GG';
    navigator.clipboard.writeText(msg).catch(function(){fallbackCopy(msg);});
    showToast('AutoGG: "'+msg+'" copied', 'ok');
  }

  // Session Lock
  function pluginSessionLock() {
    if(!isInstalled('sessionLock')) return;
    var max = getPluginSetting('sessionLock','maxLosses') || 5;
    if(session.losses >= max) {
      var el = document.createElement('div');
      el.id = 'km-session-lock';
      el.innerHTML = '<div id="km-lock-inner">'
        +'<div id="km-lock-icon">🔒</div>'
        +'<div id="km-lock-title">Session Lock</div>'
        +'<div id="km-lock-msg">You have lost '+session.losses+' times this session.<br>Take a break and come back stronger.</div>'
        +'<button id="km-lock-dismiss">I understand — unlock</button>'
        +'</div>';
      document.body.appendChild(el);
      document.getElementById('km-lock-dismiss').addEventListener('click', function(){el.remove();});
    }
  }

  // Momentum meter
  function updateMomentumMeter() {
    if(!isInstalled('momentumMeter')) return;
    var el = document.getElementById('km-momentum-bar');
    if(!el) {
      var wrap = document.createElement('div'); wrap.id='km-momentum-wrap';
      wrap.innerHTML='<div id="km-momentum-label">MOMENTUM</div><div id="km-momentum-track"><div id="km-momentum-bar"></div></div>';
      document.body.appendChild(wrap);
      el = document.getElementById('km-momentum-bar');
    }
    var total = session.wins + session.losses;
    var pct = total > 0 ? Math.round((session.wins/total)*100) : 50;
    el.style.width = pct+'%';
    el.style.background = pct>=50?'#4ade80':'#f87171';
  }

  // Rank Anxiety
  function checkRankAnxiety() {
    if(!isInstalled('rankAnxiety')||!playerData) return;
    var threshold = getPluginSetting('rankAnxiety','threshold')||30;
    var mk = 'Competitive3v3';
    var g  = playerData.ModesGlicko&&playerData.ModesGlicko[mk];
    if(!g) return;
    var mmr = g.displayRating;
    var rank = getRank(mmr);
    var idx  = RANKS.indexOf(rank);
    if(idx>0) {
      var floor = RANKS[idx].min;
      var dist  = mmr - floor;
      var hud   = document.getElementById('km-hud');
      if(dist <= threshold) {
        if(hud) hud.style.borderLeftColor='#f87171';
        showToast('⚠ Only '+dist+' MMR from rank drop!','err');
      } else {
        if(hud) hud.style.borderLeftColor='';
      }
    }
  }

  // Opponent Memory
  function pluginOpponentMemory(oppId, oppName) {
    if(!isInstalled('opponentMemory')||!oppId) return;
    var history = opponentLog.filter(function(o){return o.opponentId===oppId;});
    if(!history.length) return;
    var wins=0,losses=0;
    history.forEach(function(o){if(o.result==='Win')wins++;else losses++;});
    showToast('🧠 vs '+streamerName(oppName||history[0].opponent||'Opponent')+': '+wins+'W / '+losses+'L','info');
  }

  // Focus Mode
  function pluginFocusMode(active) {
    if(!isInstalled('focusMode')) return;
    var el = document.getElementById('km-focus-overlay');
    if(active) {
      if(!el) {
        el = document.createElement('div'); el.id='km-focus-overlay';
        document.body.appendChild(el);
      }
      var op = (getPluginSetting('focusMode','opacity')||60)/100;
      el.style.opacity = op;
    } else {
      if(el) el.remove();
    }
  }

  // Quick Notes on Goal
  var sessionNotes = [];
  function pluginQuickNote() {
    if(!isInstalled('quickNotes')) return;
    var secs = Math.round((Date.now()-session.startTime)/1000);
    var stamp = Math.floor(secs/60)+':'+String(secs%60).padStart(2,'0');
    sessionNotes.push('Goal at '+stamp+' — score '+matchScore.me+'-'+matchScore.opp);
  }

  // Match Predictor
  function pluginMatchPredictor() {
    if(!isInstalled('matchPredictor')||!playerData) return;
    var mk = currentMode==='1v1'?'Competitive1v1':currentMode==='2v2'?'Competitive2v2':'Competitive3v3';
    var g  = playerData.ModesGlicko&&playerData.ModesGlicko[mk];
    if(!g) {
      showToast('🔮 Match Predictor: Waiting for rank data...', 'info');
      return;
    }
    var base = 50;
    if (opponentMMR) {
      var diff = g.displayRating - opponentMMR;
      base = 50 + (diff / 10);
    } else if (typeof session.streak === 'number') {
      base = 50 + Math.max(-12, Math.min(12, (session.streak || 0) * 3));
    }
    var prob = Math.round(Math.max(10, Math.min(90, base)));
    var emoji = prob > 60 ? '👍' : prob < 40 ? '⚠' : '✨';
    showToast('🔮 Match Predictor: '+emoji+' '+prob+'% win chance', 'info');
  }

  // Daily Challenge
  var CHALLENGES = [
    'Score 3 goals in a single session',
    'Win 2 matches in a row',
    'Maintain 50%+ shot accuracy',
    'Win a 1v1 match',
    'Score first goal in 3 matches',
    'Win without conceding',
    'Play 5 matches today',
    'Score 5 goals total today',
  ];
  var todayChallenge = lsGet('dailyChallenge5', {date:'',challenge:'',done:false});
  if(todayChallenge.date !== today) {
    todayChallenge = {date:today, challenge:CHALLENGES[Math.floor(Math.random()*CHALLENGES.length)], done:false};
    lsSet('dailyChallenge5', todayChallenge);
  }
  function checkDailyChallenge() {
    if(!isInstalled('dailyChallenge')||todayChallenge.done) return;
    var c = todayChallenge.challenge;
    var done = false;
    if(c.includes('3 goals')&&session.goals>=3) done=true;
    if(c.includes('2 matches')&&session.streak>=2) done=true;
    if(c.includes('50%')&&session.shots>0&&(session.goals/session.shots)>=0.5) done=true;
    if(c.includes('5 matches')&&session.matches>=5) done=true;
    if(c.includes('5 goals')&&session.goals>=5) done=true;
    if(done) {
      todayChallenge.done=true; lsSet('dailyChallenge5',todayChallenge);
      showToast('📅 Daily Challenge Complete!','ok');
    }
  }

  // Performance Coach
  function pluginPerformanceCoach() {
    if(!isInstalled('performanceCoach')||session.matches<5||session.matches%5!==0) return;
    var tips = [];
    var acc = session.shots>0?(session.goals/session.shots):0;
    if(acc<0.3) tips.push('Your shot accuracy is low — focus on shot quality over quantity');
    if(session.losses>session.wins) tips.push('Losing more than winning — try a different mode or take a short break');
    if(session.conceded>session.goals) tips.push('Conceding a lot — focus on defensive positioning');
    if(session.streak>0) tips.push('You are on a '+session.streak+' win streak — keep the momentum!');
    if(!tips.length) tips.push('Looking good! Keep playing your game.');
    showToast('🏋️ Coach: '+tips[0],'info');
  }

  // Streak Saver
  function pluginStreakSaver() {
    if(!isInstalled('streakSaver')) return;
    var min = getPluginSetting('streakSaver','minStreak')||3;
    if(session.streak>=min) showToast('💎 Protect your '+session.streak+'-win streak!','warn');
  }

  // Night Mode Auto
  function checkNightMode() {
    if(!isInstalled('nightMode')) return;
    var hour = getPluginSetting('nightMode','hour')||22;
    if(new Date().getHours()>=hour && theme.preset!=='black') {
      theme.preset='black'; lsSet('theme5',theme); applyTheme();
      showToast('🌙 Night Mode activated','info');
    }
  }

  // Rank Up Simulator
  function pluginRankUpSim() {
    if(!isInstalled('rankUpSim')||!playerData) return;
    var mk='Competitive3v3';
    var g=playerData.ModesGlicko&&playerData.ModesGlicko[mk]; if(!g) return;
    var mmr=g.displayRating, rank=getRank(mmr), idx=RANKS.indexOf(rank);
    if(idx>=RANKS.length-1){showToast('🚀 You are SSL — max rank!','ok');return;}
    var needed=RANKS[idx+1].min - mmr;
    var avg=matchHistory.length>0
      ? matchHistory.filter(function(m){return m.diff>0;}).reduce(function(a,m){return a+(m.diff||0);},0)/Math.max(1,matchHistory.filter(function(m){return m.diff>0;}).length)
      : 20;
    var wins=Math.ceil(needed/Math.max(1,avg));
    showToast('🚀 ~'+wins+' wins to '+RANKS[idx+1].name,'info');
  }

  // XP Calculator
  function pluginXPCalc() {
    if(!isInstalled('xpCalc')||!playerData) return;
    var milestones=[1000,5000,10000,25000,50000,100000,250000,500000,999999];
    var xp=playerData.AccountXp||0;
    var next=milestones.find(function(m){return m>xp;});
    if(!next){showToast('⭐ Max XP reached!','ok');return;}
    var avgXP=xpHistory.length>0?xpHistory.reduce(function(a,x){return a+x.gained;},0)/xpHistory.length:25;
    var matches=Math.ceil((next-xp)/Math.max(1,avgXP));
    showToast('⭐ ~'+matches+' matches to '+next.toLocaleString()+' XP','info');
  }

  // Goal Streak
  var goalStreakCount = 0;
  function pluginGoalStreakScore() {
    if(!isInstalled('goalStreak')) return;
    goalStreakCount++;
    updateLeaderboard();
  }
  function pluginGoalStreakConcede() {
    if(!isInstalled('goalStreak')) return;
    goalStreakCount=0;
    updateLeaderboard();
  }

  // ─── WIN PREDICTOR ────────────────────────────────────────────────────────
  function pluginWinPredictorUpdate() {
    if (!isInstalled('winPredictor')) return;
    var el = document.getElementById('km-lb-winprob');
    if (!el) {
      // Inject into leaderboard widget
      var lb = document.getElementById('km-leaderboard');
      if (!lb) return;
      var d = document.createElement('div');
      d.id = 'km-lb-winprob';
      d.style.cssText = 'font-family:"DM Mono",monospace;font-size:10px;color:rgba(255,255,255,0.3);text-align:center;margin-top:4px;padding-top:4px;border-top:1px solid rgba(255,255,255,0.06);';
      lb.appendChild(d);
      el = d;
    }
    var myScore  = matchScore.me;
    var oppScore = matchScore.opp;
    var scoreDiff = myScore - oppScore;
    // Base probability from score differential
    var prob = 50 + (scoreDiff * 18);
    // Adjust for MMR diff if known
    if (opponentMMR && playerData && playerData.ModesGlicko) {
      var mk = currentMode==='1v1'?'Competitive1v1':currentMode==='2v2'?'Competitive2v2':'Competitive3v3';
      var myMMR = playerData.ModesGlicko[mk] ? playerData.ModesGlicko[mk].displayRating : null;
      if (myMMR) {
        var mmrAdv = (myMMR - opponentMMR) / 10;
        prob += Math.max(-20, Math.min(20, mmrAdv));
      }
    }
    prob = Math.max(5, Math.min(95, Math.round(prob)));
    var col = prob >= 60 ? '#4ade80' : prob <= 40 ? '#f87171' : '#fbbf24';
    el.innerHTML = '<span style="color:'+col+';font-size:12px;font-weight:700;">'+prob+'%</span> win prob';
  }

  // ─── SHOT ANALYSIS ─────────────────────────────────────────────────────────
  var shotAnalysisData = { myShots:0, myGoals:0, oppShots:0, oppGoals:0 };

  function pluginShotAnalysisShot(isMe) {
    if (!isInstalled('shotAnalysis')) return;
    if (isMe) shotAnalysisData.myShots++;
    else shotAnalysisData.oppShots++;
  }
  function pluginShotAnalysisGoal(isMe) {
    if (!isInstalled('shotAnalysis')) return;
    if (isMe) shotAnalysisData.myGoals++;
    else shotAnalysisData.oppGoals++;
    renderShotAnalysisHUD();
  }
  function renderShotAnalysisHUD() {
    if (!isInstalled('shotAnalysis')) return;
    var el = document.getElementById('km-shot-analysis-hud');
    if (!el) {
      el = document.createElement('div');
      el.id = 'km-shot-analysis-hud';
      el.style.cssText = 'position:fixed;top:12px;left:12px;z-index:99996;background:rgba(10,12,18,0.9);border:1px solid rgba(255,255,255,0.12);border-radius:12px;padding:10px;min-width:240px;font-family:"DM Mono",monospace;font-size:11px;color:#fff;pointer-events:auto;cursor:move;backdrop-filter:blur(6px);';
      document.body.appendChild(el);
      makeDraggable(el, el);
    }
    var d = shotAnalysisData;
    var myAcc  = d.myShots  > 0 ? Math.round((d.myGoals  / d.myShots)  * 100) : 0;
    var oppAcc = d.oppShots > 0 ? Math.round((d.oppGoals / d.oppShots) * 100) : 0;
    var meLabel = streamerName(matchPlayers.me || 'You');
    var oppLabel = streamerName(matchPlayers.opponent || (currentMode==='2v2' ? 'Opponents' : 'Opponent'));
    var grid = '<div style="display:grid;grid-template-columns:1fr 1fr;gap:4px;">'
      +'<div style="font-size:9px;color:rgba(255,255,255,0.45);text-transform:uppercase;">'+meLabel+'</div>'
      +'<div style="font-size:9px;color:rgba(255,255,255,0.45);text-transform:uppercase;">'+oppLabel+'</div>'
      +'<div style="font-size:13px;font-weight:800;color:#4ade80;">'+d.myGoals+'G</div>'
      +'<div style="font-size:13px;font-weight:800;color:#f87171;">'+d.oppGoals+'G</div>'
      +'<div style="font-size:12px;color:#60a5fa;">'+d.myShots+'S</div>'
      +'<div style="font-size:12px;color:#fbbf24;">'+d.oppShots+'S</div>'
      +'<div style="font-size:11px;color:rgba(255,255,255,0.9);">'+myAcc+'% acc</div>'
      +'<div style="font-size:11px;color:rgba(255,255,255,0.9);">'+oppAcc+'% acc</div>'
      +'</div>';
    el.innerHTML = '<div style="font-size:10px;letter-spacing:1px;color:rgba(255,255,255,0.7);font-weight:600;margin-bottom:6px;">SHOT ANALYSIS</div>' + grid;
  }
  function pluginShotAnalysisPostMatch() {
    if (!isInstalled('shotAnalysis')) return;
    var el = document.getElementById('km-shot-analysis-hud');
    if (el) { setTimeout(function(){if(el.parentNode)el.remove();}, 5000); }
    var threshold = getPluginSetting('shotAnalysis','threshold') || 40;
    var d = shotAnalysisData;
    var myAcc = d.myShots > 0 ? Math.round((d.myGoals / d.myShots) * 100) : 0;
    if (myAcc < threshold && d.myShots >= 2) {
      showToast('📡 Accuracy '+myAcc+'% — try taking more shots!', 'warn');
    } else if (myAcc >= threshold) {
      showToast('📡 Accuracy '+myAcc+'% — solid shooting!', 'ok');
    }
    shotAnalysisData = { myShots:0, myGoals:0, oppShots:0, oppGoals:0 };
  }

  var autoCoachData = { shots:0, goals:0, conceded:0, startedAt:0, lastTipAt:0 };
  function pluginAutoCoachOnMatchStart() {
    if (!isInstalled('autoCoach')) return;
    autoCoachData = { shots:0, goals:0, conceded:0, startedAt:Date.now(), lastTipAt:0 };
    showToast('🧠 Auto Coach is ON. Focus: shot selection + positional awareness.', 'ok');
  }
  function pluginAutoCoachOnShot(isMe) {
    if (!isInstalled('autoCoach')) return;
    if (isMe) autoCoachData.shots++;
    var now = Date.now();
    if (now - autoCoachData.lastTipAt > 15000 && autoCoachData.shots > 2 && autoCoachData.goals === 0) {
      showToast('🧠 Hint: reset and take a cleaner shot. Less rush, more precision.', 'warn');
      autoCoachData.lastTipAt = now;
    }
  }
  function pluginAutoCoachOnGoal(isMe) {
    if (!isInstalled('autoCoach')) return;
    if (isMe) {
      autoCoachData.goals++;
      if (getPluginSetting('autoCoach','goalHint') !== 'off') {
        showToast('🧠 Good finish! For consistency, aim low and use backboard angles.', 'ok');
      }
    } else {
      autoCoachData.conceded++;
      if (getPluginSetting('autoCoach','mistakeAlert') !== 'off') {
        showToast('🧠 Conceded goal: rotate back quickly and challenge cleanly.', 'warn');
      }
    }
    autoCoachData.lastTipAt = Date.now();
  }
  function pluginAutoCoachPostMatch() {
    if (!isInstalled('autoCoach')) return;
    var accuracy = autoCoachData.shots > 0 ? Math.round((autoCoachData.goals / autoCoachData.shots) * 100) : 0;
    showToast('🧠 Match summary: ' + autoCoachData.goals + ' goals, ' + autoCoachData.conceded + ' conceded, ' + accuracy + '% shot accuracy.', 'info');
  }

  // ─── TROLL PLUGIN ──────────────────────────────────────────────────────────
  // SAFETY CONTRACT: pluginTrollMount() checks window._kmTrollActive (session-only RAM flag).
  // This flag is NEVER set at boot, NEVER read from localStorage, NEVER persisted.
  // The user must flip the "Enable" toggle in the current session to set it.
  // On reload: window._kmTrollActive = undefined → troll cannot activate.

  var _trollRaf = null, _trollOverlay = null, _trollScareTimeout = null;

  function _trollFreq(s) { return s==='rare'?8000 : s==='sometimes'?3500 : s==='often'?1500 : 0; }

  function pluginTrollMount() {
    if (!isInstalled('trollPlugin') || !window._kmTrollActive) { pluginTrollUnmount(); return; }
    if (_trollRaf) return;

    // Inject shared overlay
    if (!_trollOverlay) {
      _trollOverlay = document.createElement('div');
      _trollOverlay.id = 'km-troll-overlay';
      _trollOverlay.style.cssText = 'position:fixed;inset:0;pointer-events:none;z-index:9999980;display:none;';
      document.body.appendChild(_trollOverlay);
    }

    // Inject a style for rainbow animation
    if (!document.getElementById('km-troll-style')) {
      var st = document.createElement('style'); st.id = 'km-troll-style';
      st.textContent = [
        '@keyframes km-troll-rainbow{0%{filter:hue-rotate(0deg)}100%{filter:hue-rotate(360deg)}}',
        '@keyframes km-troll-shake{0%,100%{transform:translate(0,0)}25%{transform:translate(-6px,4px)}75%{transform:translate(6px,-4px)}}',
        '#km-troll-scare{animation:km-troll-shake 0.08s linear infinite}',
      ].join('');
      document.head.appendChild(st);
    }

    function flash(bg, dur) {
      if (!_trollOverlay) return;
      _trollOverlay.style.background = bg;
      _trollOverlay.style.display = 'block';
      setTimeout(function(){ if(_trollOverlay) _trollOverlay.style.display='none'; }, dur);
    }

    function doRainbow() {
      if (!window._kmTrollActive) return;
      var freq = _trollFreq(getPluginSetting('trollPlugin','rainbow')||'sometimes');
      if (!freq) return;
      // Multi-flash hue rotate cycle
      var count = 0, hue = 0;
      var flashLoop = setInterval(function(){
        if (!_trollOverlay || !window._kmTrollActive) { clearInterval(flashLoop); return; }
        hue += 51;
        flash('hsla('+hue+',100%,55%,0.38)', 90);
        if (++count >= 7) clearInterval(flashLoop);
      }, 100);
      setTimeout(doRainbow, freq + Math.random()*freq);
    }

    function doDark() {
      if (!window._kmTrollActive) return;
      var freq = _trollFreq(getPluginSetting('trollPlugin','darkness')||'sometimes');
      if (!freq) return;
      flash('rgba(0,0,0,0.9)', 500 + Math.random()*700);
      setTimeout(doDark, freq + Math.random()*freq);
    }

    function doScare() {
      if (!window._kmTrollActive) return;
      var freq = _trollFreq(getPluginSetting('trollPlugin','jumpscare')||'rare');
      if (!freq) return;
      var faces = ['😱','👻','🎃','💀','🤡','👹','🙀','🫨'];
      var el = document.createElement('div');
      el.id = 'km-troll-scare';
      el.style.cssText = 'position:fixed;inset:0;z-index:9999990;display:flex;align-items:center;justify-content:center;background:#000;pointer-events:none;';
      el.innerHTML = '<span style="font-size:160px;line-height:1;filter:drop-shadow(0 0 60px red);">'+faces[Math.floor(Math.random()*faces.length)]+'</span>';
      document.body.appendChild(el);
      // Audio sting
      try {
        var ac = new (window.AudioContext||window.webkitAudioContext)();
        [880,660,440].forEach(function(f,i){
          var o=ac.createOscillator(), g=ac.createGain();
          o.frequency.value=f; o.connect(g); g.connect(ac.destination);
          g.gain.setValueAtTime(0.25, ac.currentTime+i*0.04);
          g.gain.exponentialRampToValueAtTime(0.001, ac.currentTime+0.3+i*0.04);
          o.start(ac.currentTime+i*0.04); o.stop(ac.currentTime+0.35+i*0.04);
        });
      } catch(e){}
      setTimeout(function(){ if(el.parentNode) el.remove(); }, 350+Math.random()*200);
      setTimeout(doScare, freq*3 + Math.random()*freq*2); // scares less frequent
    }

    // Kick off each effect independently with random initial delays so they don't sync
    var darknessFreq = _trollFreq(getPluginSetting('trollPlugin','darkness')||'sometimes');
    var rainbowFreq  = _trollFreq(getPluginSetting('trollPlugin','rainbow')||'sometimes');
    var scareFreq    = _trollFreq(getPluginSetting('trollPlugin','jumpscare')||'rare');
    if (darknessFreq) setTimeout(doDark,    1000 + Math.random()*darknessFreq);
    if (rainbowFreq)  setTimeout(doRainbow, 2000 + Math.random()*rainbowFreq);
    if (scareFreq)    setTimeout(doScare,   8000 + Math.random()*scareFreq*2);

    // Heartbeat RAF just to keep the RAF var non-null so double-mount guard works
    (function beat(){ _trollRaf = requestAnimationFrame(beat); })();
    kmLog('🎭 Troll Mode active — chaos initiated');
  }

  function pluginTrollUnmount() {
    if (_trollRaf) { cancelAnimationFrame(_trollRaf); _trollRaf = null; }
    if (_trollOverlay) { _trollOverlay.remove(); _trollOverlay = null; }
    var el = document.getElementById('km-troll-scare'); if (el) el.remove();
    var st = document.getElementById('km-troll-style'); if (st) st.remove();
  }

  // ─── PERFORMANCE METRICS ──────────────────────────────────────────────────
  var perfEl = null, perfFrames = 0, perfLast = performance.now(), perfFPS = 0;
  var perfFT = 0, perfPingHistory = [], perfRAF = null;

  function pluginPerfMetricsMount() {
    if (!isInstalled('perfMetrics')) { pluginPerfMetricsUnmount(); return; }
    if (perfEl) return;
    var pos = getPluginSetting('perfMetrics','pos') || 'top-right';
    var posCSS = pos === 'top-left'      ? 'top:14px;left:14px'
               : pos === 'top-right'     ? 'top:14px;right:14px'
               : pos === 'bottom-left'   ? 'bottom:90px;left:14px'
               :                           'bottom:90px;right:14px';
    perfEl = document.createElement('div');
    perfEl.id = 'km-perf-metrics';
    perfEl.style.cssText = 'position:fixed;'+posCSS+';z-index:99997;'
      +'background:rgba(4,6,12,0.92);border:1px solid rgba(255,255,255,0.1);border-left:2px solid #4ade80;'
      +'padding:8px 12px;font-family:"DM Mono",monospace;font-size:10px;pointer-events:none;'
      +'backdrop-filter:blur(14px);min-width:140px;';
    document.body.appendChild(perfEl);
    var lastFrame = performance.now();
    function perfTick(now) {
      perfFrames++;
      perfFT = now - lastFrame;
      lastFrame = now;
      if (now - perfLast >= 1000) {
        perfFPS = _hudFps || Math.round(perfFrames * 1000 / (now - perfLast));
        perfFrames = 0;
        perfLast = now;
        renderPerfMetrics();
      }
      perfRAF = requestAnimationFrame(perfTick);
    }
    perfRAF = requestAnimationFrame(perfTick);
  }

  function pluginPerfMetricsUnmount() {
    if (perfEl)  { perfEl.remove(); perfEl = null; }
    if (perfRAF) { cancelAnimationFrame(perfRAF); perfRAF = null; }
    perfFrames = 0; perfFPS = 0;
    netBridgeStop();
    // Note: ping stays running (it feeds the HUD, always-on)
  }

  function renderPerfMetrics() {
    if (!perfEl) return;
    var showFPS    = (getPluginSetting('perfMetrics','fps')       || 'on')  === 'on';
    var showFT     = (getPluginSetting('perfMetrics','frame')     || 'on')  === 'on';
    var showMem    = (getPluginSetting('perfMetrics','memory')    || 'off') === 'on';
    var showPing   = (getPluginSetting('perfMetrics','ping')      || 'on')  === 'on';
    var showJitter = (getPluginSetting('perfMetrics','jitter')    || 'on')  === 'on';
    var netBridge  = (getPluginSetting('perfMetrics','netbridge') || 'off') === 'on';

    // Start/stop Network Bridge when setting changes
    if (netBridge) netBridgeStart(); else netBridgeStop();

    var fpsCol = perfFPS >= 55 ? '#4ade80' : perfFPS >= 30 ? '#fbbf24' : '#f87171';
    var ftMs   = perfFT.toFixed(1);
    var stats  = getPingStats();
    var lines  = [];

    if (showFPS) lines.push(
      '<div style="display:flex;justify-content:space-between;gap:16px;margin:1px 0;">'
      +'<span style="color:rgba(255,255,255,0.3);">FPS</span>'
      +'<span style="color:'+fpsCol+';font-size:14px;font-weight:700;line-height:1;">'+perfFPS+'</span></div>'
    );
    if (showFT) lines.push(
      '<div style="display:flex;justify-content:space-between;gap:16px;margin:1px 0;">'
      +'<span style="color:rgba(255,255,255,0.3);">FRAME</span>'
      +'<span style="color:rgba(255,255,255,0.7);">'+ftMs+'ms</span></div>'
    );
    if (showMem && performance.memory) {
      var used  = Math.round(performance.memory.usedJSHeapSize/1048576);
      var total = Math.round(performance.memory.totalJSHeapSize/1048576);
      var memCol = used/total > 0.8 ? '#f87171' : 'rgba(255,255,255,0.6)';
      lines.push(
        '<div style="display:flex;justify-content:space-between;gap:16px;margin:1px 0;">'
        +'<span style="color:rgba(255,255,255,0.3);">MEM</span>'
        +'<span style="color:'+memCol+';">'+used+'MB</span></div>'
      );
    }

    // ── Ping section: real RTT sparkline ─────────────────────────────────────
    if (showPing && _pingHistory.length) {
      var pingCol = stats.last < 60 ? '#4ade80' : stats.last < 120 ? '#fbbf24' : '#f87171';
      var maxP = Math.max.apply(null, _pingHistory.concat([1]));
      var pingBars = _pingHistory.slice(-40).map(function(p) {
        var h = Math.max(2, Math.round((p / maxP) * 18));
        var c = p < 60 ? '#4ade80' : p < 120 ? '#fbbf24' : '#f87171';
        return '<div style="width:2px;height:'+h+'px;background:'+c+'88;align-self:flex-end;flex-shrink:0;"></div>';
      }).join('');

      // Divider
      lines.push('<div style="height:1px;background:rgba(255,255,255,0.06);margin:4px 0 3px;"></div>');
      // Label + current value
      lines.push(
        '<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:3px;">'
        +'<span style="font-size:8px;letter-spacing:1.5px;color:rgba(255,255,255,0.25);">PING</span>'
        +'<span style="color:'+pingCol+';font-size:13px;font-weight:700;">'+stats.last+'ms</span>'
        +'</div>'
      );
      // Sparkline bars
      lines.push(
        '<div style="display:flex;gap:1px;align-items:flex-end;height:18px;margin-bottom:3px;">'+pingBars+'</div>'
      );
      // Min / Avg / Max row
      lines.push(
        '<div style="display:flex;justify-content:space-between;font-size:9px;color:rgba(255,255,255,0.28);">'
        +'<span>↓'+stats.min+'</span>'
        +'<span style="color:rgba(255,255,255,0.45);">avg '+stats.avg+'</span>'
        +'<span>↑'+stats.max+'</span>'
        +'</div>'
      );
    }

    // ── Jitter section ────────────────────────────────────────────────────────
    if (showJitter && _jitterHistory.length) {
      var jCol = stats.jitter < 8 ? '#4ade80' : stats.jitter < 25 ? '#fbbf24' : '#f87171';
      var stableCol = stats.stable >= 80 ? '#4ade80' : stats.stable >= 50 ? '#fbbf24' : '#f87171';
      var maxJ = Math.max.apply(null, _jitterHistory.concat([1]));
      var jBars = _jitterHistory.map(function(j) {
        var h = Math.max(1, Math.round((j / maxJ) * 16));
        var c = j < 8 ? '#4ade80' : j < 25 ? '#fbbf24' : '#f87171';
        return '<div style="width:2px;height:'+h+'px;background:'+c+'99;align-self:flex-end;flex-shrink:0;"></div>';
      }).join('');

      lines.push('<div style="height:1px;background:rgba(255,255,255,0.06);margin:4px 0 3px;"></div>');
      lines.push(
        '<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:3px;">'
        +'<span style="font-size:8px;letter-spacing:1.5px;color:rgba(255,255,255,0.25);">JITTER</span>'
        +'<span style="color:'+jCol+';font-size:12px;font-weight:700;">±'+stats.jitter+'ms</span>'
        +'</div>'
      );
      lines.push(
        '<div style="display:flex;gap:1px;align-items:flex-end;height:16px;margin-bottom:3px;">'+jBars+'</div>'
      );
      // Stability score bar
      lines.push(
        '<div style="display:flex;align-items:center;gap:6px;">'
        +'<span style="font-size:8px;color:rgba(255,255,255,0.22);">STABILITY</span>'
        +'<div style="flex:1;height:3px;background:rgba(255,255,255,0.08);overflow:hidden;">'
          +'<div style="height:100%;width:'+stats.stable+'%;background:'+stableCol+';transition:width 0.4s ease;"></div>'
        +'</div>'
        +'<span style="font-size:9px;color:'+stableCol+';font-weight:700;">'+stats.stable+'%</span>'
        +'</div>'
      );
    }

    // ── Network Bridge status badge ───────────────────────────────────────────
    if (netBridge) {
      lines.push('<div style="height:1px;background:rgba(255,255,255,0.06);margin:4px 0 3px;"></div>');
      lines.push(
        '<div style="display:flex;align-items:center;gap:5px;">'
        +'<div style="width:5px;height:5px;border-radius:50%;background:#4ade80;animation:km-rec-blink 2s ease-in-out infinite;flex-shrink:0;"></div>'
        +'<span style="font-size:8px;letter-spacing:1px;color:#4ade80;">NET BRIDGE ACTIVE</span>'
        +'</div>'
      );
    }

    perfEl.innerHTML = lines.join('');
  }

  function pluginPerfMetricsUpdate() {
    if (!isInstalled('perfMetrics') || !perfEl) return;
    renderPerfMetrics();
  }

  // ─── AUTO QUEUE ────────────────────────────────────────────────────────────
  var autoQueuePending = false;
  var autoQueueTimer   = null;
  var inQueue          = false;
  var queueStartTime   = null;

  function pluginAutoQueueOnMatchEnd() {
    if (!isInstalled('autoQueue')) return;
    var delay = parseInt(getPluginSetting('autoQueue','delay')) || 3;
    if (autoQueueTimer) clearTimeout(autoQueueTimer);
    autoQueuePending = true;
    showToast('🔁 Auto-queue in '+delay+'s...','info');
    kmLog('AutoQueue: scheduling re-queue in '+delay+'s');
    autoQueueTimer = setTimeout(function() {
      autoQueuePending = false;
      pluginAutoQueueFire();
    }, delay * 1000);
  }

  function pluginAutoQueueFire() {
    if (!isInstalled('autoQueue')) return;
    kmLog('AutoQueue: attempting to click play button');
    // Try multiple known button patterns in the game UI
    var selectors = [
      '[class*="play"]', '[class*="queue"]', '[class*="match"]',
      'button', '.play-btn', '#play-btn', '[class*="Play"]',
      '[class*="Queue"]', '[class*="casual"]', '[class*="competitive"]',
    ];
    var found = false;
    for (var i = 0; i < selectors.length; i++) {
      var els = document.querySelectorAll(selectors[i]);
      for (var j = 0; j < els.length; j++) {
        var el = els[j];
        var txt = (el.textContent || el.value || el.getAttribute('aria-label') || '').toLowerCase();
        if (txt.includes('play') || txt.includes('queue') || txt.includes('match') || txt.includes('find')) {
          el.click();
          kmLog('AutoQueue: clicked "'+el.textContent.trim().slice(0,30)+'"');
          showToast('🔁 Re-queued!','ok');
          found = true;
          break;
        }
      }
      if (found) break;
    }
    if (!found) {
      showToast('🔁 AutoQueue: no play button found','warn');
      kmLog('AutoQueue: no matching button — game may not be on menu screen yet');
    }
  }

  function pluginAutoQueueOnQueueStart() {
    if (!isInstalled('autoQueue')) return;
    inQueue = true;
    queueStartTime = Date.now();
    kmLog('AutoQueue: queue started');
  }

  function pluginAutoQueueOnMatchFound() {
    if (!isInstalled('autoQueue')) return;
    inQueue = false;
    if (queueStartTime) {
      var elapsed = Math.round((Date.now() - queueStartTime) / 1000);
      kmLog('AutoQueue: match found after '+elapsed+'s in queue');
    }
  }

  // ─── SCREENSHOT & CLIPS ────────────────────────────────────────────────────
  var _kmScrHotkey = null;

  function pluginScreenshotMount() {
    if (!isInstalled('screenshotRec') || !isInstalled('streamerMode')) { pluginScreenshotUnmount(); return; }
    if (_kmScrHotkey) return;
    _kmScrHotkey = function(e) {
      if (!isInstalled('screenshotRec')) return;
      var hotkey = (getPluginSetting('screenshotRec','hotkey') || 'F9').toLowerCase();
      if (e.key.toLowerCase() === hotkey) {
        e.preventDefault();
        pluginScreenshotTake('hotkey');
      }
    };
    document.addEventListener('keydown', _kmScrHotkey, true);
    kmLog('Screenshot plugin mounted, hotkey: '+(getPluginSetting('screenshotRec','hotkey')||'F9'));
  }

  function pluginScreenshotUnmount() {
    if (_kmScrHotkey) { document.removeEventListener('keydown', _kmScrHotkey, true); _kmScrHotkey = null; }
  }

  function pluginScreenshotTake(source) {
    var canvas = document.querySelector('canvas');
    if (!canvas) { showToast('No canvas found','warn'); return; }
    try {
      var dataURL = canvas.toDataURL('image/png');
      var ts = new Date().toISOString().replace(/[:.]/g,'-').slice(0,19);
      var a = document.createElement('a');
      a.download = 'rocketgoal-'+source+'-'+ts+'.png';
      a.href = dataURL;
      a.click();
      showToast('📸 Screenshot saved!','ok');
      kmLog('Screenshot saved: '+a.download);
    } catch(e) {
      showToast('Screenshot failed: '+e.message,'err');
    }
  }

  function pluginScreenshotOnGoal() {
    if (!isInstalled('screenshotRec') || !isInstalled('streamerMode')) return;
    var auto = getPluginSetting('screenshotRec','autogoal') || 'off';
    if (auto === 'on') {
      setTimeout(function() { pluginScreenshotTake('goal'); }, 300);
    }
  }

  // ─── STREAMER MODE ────────────────────────────────────────────────────────
  function streamerName(name) {
    if (!isInstalled('streamerMode')) return name;
    if ((getPluginSetting('streamerMode','hidenames')||'on') === 'on') return 'Player';
    return name;
  }
  function streamerMMR(val) {
    if (!isInstalled('streamerMode')) return val;
    if ((getPluginSetting('streamerMode','blurMMR')||'on') === 'on') return '???';
    return val;
  }
  function applyDevPreset(preset) {
    if (preset === 'streamer') {
      if (!isInstalled('streamerMode')) installedPlugins.push('streamerMode');
      if (!isInstalled('screenshotRec')) installedPlugins.push('screenshotRec');
      if (!isInstalled('clipRecorder')) installedPlugins.push('clipRecorder');
      setPluginSetting('streamerMode','hidenames','on');
      setPluginSetting('streamerMode','blurMMR','on');
      setPluginSetting('clipRecorder','quality','16k');
      setPluginSetting('clipRecorder','autogoal','on');
    } else if (preset === 'practice') {
      setPluginSetting('shotAnalysis','threshold',40);
      setPluginSetting('clipRecorder','autogoal','off');
      setPluginSetting('streamerMode','hidenames','off');
      setPluginSetting('streamerMode','blurMMR','off');
    } else if (preset === 'performance') {
      if (!isInstalled('optimizer')) installedPlugins.push('optimizer');
      setPluginSetting('optimizer','stripshadows','on');
      setPluginSetting('optimizer','lowqual','on');
      setPluginSetting('optimizer','lessgarbage','on');
    }
    lsSet('plugins5', installedPlugins);
    pluginStreamerMount();
    pluginClipRecorderMount();
    pluginOptimizerMount();
  }

  var _devSceneTimer = null;
  var _devToolsHotkeyListener = null;
  function pluginDevToolsMount() {
    if (!isInstalled('devTools')) { pluginDevToolsUnmount(); return; }
    if (document.getElementById('km-devtools-panel')) return;
    var el = document.createElement('div');
    el.id = 'km-devtools-panel';
    el.style.cssText = 'position:fixed;bottom:12px;right:12px;z-index:99999;background:rgba(10,12,18,0.94);border:1px solid rgba(255,255,255,0.18);border-radius:10px;padding:10px;min-width:230px;font-family:"DM Mono",monospace;color:#fff;pointer-events:auto;';
    el.innerHTML = '' +
      '<div style="font-size:10px;font-weight:700;letter-spacing:1px;margin-bottom:5px;color:#60a5fa;">DEV TOOLS</div>' +
      '<div style="display:flex;gap:6px;margin-bottom:6px;align-items:center;">' +
        '<select id="km-dev-tools-preset" style="flex:1;padding:4px;background:#111;border:1px solid rgba(255,255,255,0.15);color:#fff;font-size:11px;">' +
          '<option value="default">Default</option>' +
          '<option value="streamer">Streamer</option>' +
          '<option value="practice">Practice</option>' +
          '<option value="performance">Performance</option>' +
        '</select>' +
        '<button id="km-dev-tools-apply" style="padding:4px 8px;font-size:11px;border:1px solid rgba(255,255,255,0.15);background:#1f2937;color:#fff;">Apply</button>' +
      '</div>' +
      '<div style="margin-bottom:6px;display:flex;gap:6px;">' +
        '<button id="km-dev-tools-stream" style="padding:4px 6px;font-size:10px;border:1px solid rgba(255,255,255,0.15);background:#111;color:#fff;">Toggle Stream</button>' +
        '<button id="km-dev-tools-export" style="padding:4px 6px;font-size:10px;border:1px solid rgba(255,255,255,0.15);background:#111;color:#fff;">Export</button>' +
      '</div>' +
      '<div style="margin-bottom:5px;display:flex;gap:6px;">' +
        '<button id="km-dev-tools-import" style="padding:4px 6px;font-size:10px;border:1px solid rgba(255,255,255,0.15);background:#111;color:#fff;">Import</button>' +
        '<button id="km-dev-tools-timer" style="padding:4px 6px;font-size:10px;border:1px solid rgba(255,255,255,0.15);background:#111;color:#fff;">Start timer</button>' +
      '</div>' +
      '<div id="km-dev-tools-timer-label" style="font-size:10px;color:rgba(255,255,255,0.6);">Scene timer: 00:00</div>';
    document.body.appendChild(el);

    document.getElementById('km-dev-tools-apply').addEventListener('click', function() {
      var preset = document.getElementById('km-dev-tools-preset').value;
      setPluginSetting('devTools', 'preset', preset);
      applyDevPreset(preset);
      showToast('Dev preset applied: ' + preset, 'ok');
    });
    document.getElementById('km-dev-tools-stream').addEventListener('click', function() {
      if (isInstalled('streamerMode')) {
        installedPlugins = installedPlugins.filter(function(x){return x!=='streamerMode';});
        showToast('Streamer Tools disabled', 'warn');
      } else {
        installedPlugins.push('streamerMode');
        setPluginSetting('streamerMode','hidenames','on');
        setPluginSetting('streamerMode','blurMMR','on');
        showToast('Streamer Tools enabled', 'ok');
      }
      lsSet('plugins5', installedPlugins);
      pluginStreamerMount();
    });
    document.getElementById('km-dev-tools-export').addEventListener('click', function() {
      var payload = JSON.stringify({installed:installedPlugins,settings:pluginSettings,toggles:toggles}, null, 2);
      navigator.clipboard.writeText(payload).then(function(){ showToast('Config copied to clipboard', 'ok'); }, function(){ showToast('Copy failed', 'err'); });
    });
    document.getElementById('km-dev-tools-import').addEventListener('click', function() {
      var data = prompt('Paste KeyMod JSON config:');
      if (!data) return;
      try {
        var obj = JSON.parse(data);
        if (obj.installed) installedPlugins = obj.installed;
        if (obj.settings) pluginSettings = obj.settings;
        if (obj.toggles) toggles = obj.toggles;
        lsSet('plugins5', installedPlugins);
        lsSet('pluginSettings5', pluginSettings);
        lsSet('toggles5', toggles);
        showToast('Config imported. Reloading...', 'ok');
        setTimeout(function(){ window.location.reload(); }, 500);
      } catch (e) {
        showToast('Invalid config JSON', 'err');
      }
    });
    document.getElementById('km-dev-tools-timer').addEventListener('click', function() {
      if (_devSceneTimer) {
        clearInterval(_devSceneTimer);
        _devSceneTimer = null;
        this.textContent = 'Start timer';
        document.getElementById('km-dev-tools-timer-label').textContent = 'Scene timer: 00:00';
      } else {
        var seconds = 0;
        _devSceneTimer = setInterval(function() {
          seconds++;
          var mm = String(Math.floor(seconds/60)).padStart(2,'0');
          var ss = String(seconds%60).padStart(2,'0');
          document.getElementById('km-dev-tools-timer-label').textContent = 'Scene timer: '+mm+':'+ss;
        }, 1000);
        document.getElementById('km-dev-tools-timer').textContent = 'Stop timer';
      }
    });

    var hotkey = (getPluginSetting('devTools','streamerHotkey') || 'F6').toLowerCase();
    window.addEventListener('keydown', _devToolsHotkeyListener = function(e) {
      if (!isInstalled('devTools')) return;
      if (e.key.toLowerCase() === hotkey) {
        e.preventDefault();
        if (isInstalled('streamerMode')) {
          installedPlugins = installedPlugins.filter(function(x){return x!=='streamerMode';});
          showToast('Streamer Tools disabled', 'warn');
        } else {
          installedPlugins.push('streamerMode');
          showToast('Streamer Tools enabled', 'ok');
        }
        lsSet('plugins5', installedPlugins);
        pluginStreamerMount();
      }
    }, true);
  }

  function pluginDevToolsUnmount() {
    var existing = document.getElementById('km-devtools-panel');
    if (existing) existing.remove();
    if (_devSceneTimer) { clearInterval(_devSceneTimer); _devSceneTimer = null; }
    if (_devToolsHotkeyListener) { window.removeEventListener('keydown', _devToolsHotkeyListener, true); _devToolsHotkeyListener = null; }
  }

  function pluginStreamerMount() {
    var enabled = isInstalled('streamerMode');
    if (!enabled) {
      pluginScreenshotUnmount();
      pluginClipRecorderUnmount();
    } else {
      if (isInstalled('screenshotRec')) pluginScreenshotMount(); else pluginScreenshotUnmount();
      if (isInstalled('clipRecorder')) pluginClipRecorderMount(); else pluginClipRecorderUnmount();
    }
    pluginDevToolsMount();
  }

  // ─── PLAYSTYLE CLASSIFIER ─────────────────────────────────────────────────
  function pluginPlaystyleOnMatchStart() {
    if (!isInstalled('playstyleAI') || !matchPlayers.opponent) return;
    var name = matchPlayers.opponent;
    var history = opponentLog.filter(function(e){ return e.name === name; });
    if (history.length < 2) return;

    // Count goals and shots across history entries
    var totalGoals = 0, totalShots = 0, earlyGoals = 0, totalMatches = history.length;
    history.forEach(function(e) {
      if (e.score) {
        var parts = e.score.split('-');
        totalGoals += parseInt(parts[1]||0); // opponent goals
      }
      if (e.oppShots) totalShots += e.oppShots;
      if (e.oppGoals) totalGoals += e.oppGoals;
    });

    var accuracy = totalShots > 0 ? Math.round((totalGoals/totalShots)*100) : 0;
    var goalsPerMatch = totalMatches > 0 ? (totalGoals/totalMatches).toFixed(1) : 0;
    var wins = history.filter(function(e){return e.result==='Win';}).length; // our wins vs them
    var winRate = Math.round((wins/totalMatches)*100);

    var label, detail;
    if (accuracy >= 65) {
      label = 'Sniper'; detail = accuracy+'% shot accuracy';
    } else if (winRate <= 30) {
      label = 'Beater'; detail = 'you win only '+winRate+'% vs them';
    } else if (goalsPerMatch >= 2.5) {
      label = 'Aggressive'; detail = goalsPerMatch+' goals/match avg';
    } else if (winRate >= 70) {
      label = 'Grinder'; detail = 'low skill, you win '+winRate+'%';
    } else {
      label = 'Balanced'; detail = totalMatches+' matches played';
    }

    var dispName = streamerName(name);
    setTimeout(function() {
      showToast('🧠 '+dispName+' — '+label+' ('+detail+')', 'warn');
    }, 2000);
    kmLog('Playstyle: '+name+' = '+label);
  }

  // ─── PEAK GHOST ───────────────────────────────────────────────────────────
  var peakGhostEl = null;
  var allTimeStats = { goalsPerMatch:0, winsPerMatch:0, loaded:false };

  function pluginPeakGhostInit() {
    if (!isInstalled('peakGhost')) return;
    // Compute all-time averages from match history
    if (matchHistory && matchHistory.length > 5) {
      var totalGoals = matchHistory.reduce(function(a,m){ return a+(m.goals||0); }, 0);
      var totalWins  = matchHistory.filter(function(m){ return m.result==='Win'; }).length;
      allTimeStats.goalsPerMatch = totalGoals / matchHistory.length;
      allTimeStats.winsPerMatch  = totalWins  / matchHistory.length;
      allTimeStats.loaded = true;
    }
  }

  function pluginPeakGhostUpdate() {
    if (!isInstalled('peakGhost') || !allTimeStats.loaded) { pluginPeakGhostUnmount(); return; }
    if (!peakGhostEl) {
      peakGhostEl = document.createElement('div');
      peakGhostEl.id = 'km-peak-ghost';
      peakGhostEl.style.cssText = 'position:fixed;bottom:130px;right:22px;z-index:99996;'
        +'background:rgba(0,0,0,0.82);border:1px solid rgba(255,255,255,0.07);border-left:2px solid #a78bfa;'
        +'padding:8px 12px;font-family:"DM Mono",monospace;font-size:10px;pointer-events:none;'
        +'backdrop-filter:blur(10px);min-width:130px;';
      document.body.appendChild(peakGhostEl);
    }
    var sessionGoalsPerMatch = session.matches > 0 ? session.goals / session.matches : 0;
    var sessionWR = session.matches > 0 ? session.wins / session.matches : 0;
    var gDiff = sessionGoalsPerMatch - allTimeStats.goalsPerMatch;
    var wDiff = sessionWR - allTimeStats.winsPerMatch;
    function arrow(d) { return d > 0.05 ? '📈' : d < -0.05 ? '📉' : '➡️'; }
    function col(d)   { return d > 0.05 ? '#4ade80' : d < -0.05 ? '#f87171' : 'rgba(255,255,255,0.5)'; }
    peakGhostEl.innerHTML =
      '<div style="font-size:8px;letter-spacing:2px;color:rgba(255,255,255,0.22);margin-bottom:5px">PEAK GHOST</div>'
      +'<div style="display:flex;justify-content:space-between;gap:14px;margin:2px 0">'
        +'<span style="color:rgba(255,255,255,0.3)">Goals/M</span>'
        +'<span style="color:'+col(gDiff)+'">'+arrow(gDiff)+' '+sessionGoalsPerMatch.toFixed(1)+'</span>'
      +'</div>'
      +'<div style="display:flex;justify-content:space-between;gap:14px;margin:2px 0">'
        +'<span style="color:rgba(255,255,255,0.3)">Win Rate</span>'
        +'<span style="color:'+col(wDiff)+'">'+arrow(wDiff)+' '+Math.round(sessionWR*100)+'%</span>'
      +'</div>';
  }

  function pluginPeakGhostUnmount() {
    if (peakGhostEl) { peakGhostEl.remove(); peakGhostEl = null; }
  }

  // ─── TACTICAL AUDIO ───────────────────────────────────────────────────────
  // ─── ANIMATED BACKGROUND ─────────────────────────────────────────────────
  var animBGEl = null, animBGRaf = null;

  function pluginAnimBGMount() {
    pluginAnimBGUnmount();
    // Animated background has been removed.
  }

  function pluginAnimBGUnmount() {
    if (animBGRaf) { cancelAnimationFrame(animBGRaf); animBGRaf = null; }
    if (animBGEl)  { animBGEl.remove(); animBGEl = null; }
  }

  function pluginAnimBGDraw() {
    return;
  }

  // ─── POST-MATCH STATS OVERLAY ─────────────────────────────────────────────
  function showPostMatchStats(result, oldMMR, newMMR, xpDiff) {
    var old = document.getElementById('km-post-match'); if(old) old.remove();
    var el = document.createElement('div');
    el.id = 'km-post-match';
    var isWin = result === 'Win';
    var accent = isWin ? '#4ade80' : '#f87171';
    var mmrDiff = (newMMR !== null && oldMMR !== null) ? (newMMR - oldMMR) : null;

    // Session averages
    var sessionAcc = session.shots > 0 ? Math.round((session.goals/session.shots)*100) : 0;
    var matchAcc   = shotAnalysisData && shotAnalysisData.myShots > 0
      ? Math.round((shotAnalysisData.myGoals/shotAnalysisData.myShots)*100) : 0;
    var sessionGPM = session.matches > 0 ? (session.goals/session.matches).toFixed(1) : '0';
    var matchGPM   = '—'; // approx from score
    var oppName    = streamerName(matchPlayers.opponent || 'Opponent');
    var oppMMRStr  = opponentMMR ? streamerMMR(opponentMMR) : '?';
    var oppAccStr  = shotAnalysisData && shotAnalysisData.oppShots > 0
      ? Math.round((shotAnalysisData.oppGoals/shotAnalysisData.oppShots)*100)+'%' : '—';

    function row(label, val, sessVal, highlight) {
      return '<tr style="border-bottom:1px solid rgba(255,255,255,0.05);">'
        +'<td style="padding:5px 10px 5px 0;color:rgba(255,255,255,0.35);font-size:10px;text-transform:uppercase;letter-spacing:0.5px;white-space:nowrap">'+label+'</td>'
        +'<td style="padding:5px 12px;color:'+(highlight||'#fff')+';font-family:\'DM Mono\',monospace;font-weight:700;font-size:13px;">'+val+'</td>'
        +(sessVal !== undefined ? '<td style="padding:5px 0;color:rgba(255,255,255,0.28);font-size:11px;">'+sessVal+'</td>' : '')
        +'</tr>';
    }

    var table =
      '<table style="width:100%;border-collapse:collapse;">'
      +'<thead><tr>'
        +'<th style="text-align:left;font-size:8px;letter-spacing:1.5px;color:rgba(255,255,255,0.2);padding-bottom:6px;font-weight:400">STAT</th>'
        +'<th style="text-align:left;font-size:8px;letter-spacing:1.5px;color:rgba(255,255,255,0.2);padding-bottom:6px;font-weight:400">THIS MATCH</th>'
        +'<th style="text-align:left;font-size:8px;letter-spacing:1.5px;color:rgba(255,255,255,0.2);padding-bottom:6px;font-weight:400">SESSION AVG</th>'
      +'</tr></thead>'
      +'<tbody>'
      + row('Score', matchScore.me+' – '+matchScore.opp, session.goals+'G / '+session.matches+'M')
      + (matchAcc > 0 ? row('Accuracy', matchAcc+'%', sessionAcc+'%', matchAcc > sessionAcc ? '#4ade80' : matchAcc < sessionAcc ? '#f87171' : '#fff') : '')
      + (mmrDiff !== null ? row('MMR', (mmrDiff >= 0 ? '▲ +' : '▼ ')+mmrDiff, 'Total: '+(mmrDiff >= 0 ? '+' : '')+mmrDiff, mmrDiff >= 0 ? '#4ade80' : '#f87171') : '')
      + (xpDiff > 0 ? row('XP', '+'+xpDiff, '') : '')
      +'</tbody>'
      +'</table>';

    var oppSection = '';
    if (matchPlayers.opponent) {
      oppSection = '<div style="margin-top:10px;padding-top:10px;border-top:1px solid rgba(255,255,255,0.07);">'
        +'<div style="font-size:8px;letter-spacing:1.5px;color:rgba(255,255,255,0.2);margin-bottom:6px">OPPONENT</div>'
        +'<div style="display:flex;justify-content:space-between;align-items:center;">'
          +'<div>'
            +'<div style="font-size:13px;font-weight:700;color:rgba(255,255,255,0.8)">'+oppName+'</div>'
            +'<div style="font-size:10px;color:rgba(255,255,255,0.3);margin-top:2px">'+oppMMRStr+' MMR &nbsp;·&nbsp; Acc: '+oppAccStr+'</div>'
          +'</div>'
          +(opponentMMR ? '<div style="font-size:10px;color:rgba(255,255,255,0.25)">'+getRank(opponentMMR).emoji+' '+getRank(opponentMMR).name+'</div>' : '')
        +'</div>'
        +'</div>';
    }

    el.style.cssText = 'position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:999992;'
      +'background:rgba(4,6,12,0.97);border:1px solid rgba(255,255,255,0.08);border-top:2px solid '+accent+';'
      +'padding:18px 20px;font-family:"DM Mono",monospace;min-width:300px;max-width:380px;'
      +'box-shadow:0 32px 80px rgba(0,0,0,0.9);pointer-events:none;';

    el.innerHTML =
      '<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:12px;">'
        +'<div style="font-family:\'Bebas Neue\',sans-serif;font-size:24px;letter-spacing:3px;color:'+accent+'">'+result.toUpperCase()+'</div>'
        +'<div style="font-size:10px;color:rgba(255,255,255,0.3);text-align:right;">'+
          (currentMode||'match').replace('Competitive','')+'</div>'
      +'</div>'
      + table
      + oppSection;

    document.body.appendChild(el);
    setTimeout(function(){ el.style.transition='opacity 0.5s'; el.style.opacity='0'; }, 7000);
    setTimeout(function(){ if(el.parentNode) el.remove(); }, 7600);
  }

  // ─── CLIP RECORDER ────────────────────────────────────────────────────────
  var clipRec = {
    mediaRecorder: null,
    chunks: [],
    recording: false,
    startTime: 0,
    maxTimer: null,
    hotkey: null,
    preBuffer: [],       // rolling ~5s of chunks before goal
    preBufferTimer: null,
  };

  var CLIP_QUALITY = {
    q16k:   { videoBitsPerSecond: 16000000 },
    high:   { videoBitsPerSecond: 8000000 },
    medium: { videoBitsPerSecond: 4000000 },
    low:    { videoBitsPerSecond: 1500000 },
  };

  function pluginClipRecorderMount() {
    if (!isInstalled('clipRecorder') || !isInstalled('streamerMode')) { pluginClipRecorderUnmount(); return; }
    if (clipRec.hotkey) return;  // already mounted

    var hk = (getPluginSetting('clipRecorder','hotkey') || 'F8').toLowerCase();
    clipRec.hotkey = function(e) {
      if (!isInstalled('clipRecorder')) return;
      if (e.key.toLowerCase() !== hk) return;
      e.preventDefault();
      if (clipRec.recording) {
        pluginClipRecorderStop('manual');
      } else {
        pluginClipRecorderStart();
      }
    };
    document.addEventListener('keydown', clipRec.hotkey, true);

    // Start pre-buffer capture for goal auto-clips
    pluginClipRecorderPreBuffer();

    kmLog('ClipRecorder mounted, hotkey: ' + hk.toUpperCase());
    showToast('🎬 ClipRecorder ready — ' + hk.toUpperCase() + ' to record', 'ok');
  }

  function pluginClipRecorderUnmount() {
    pluginClipRecorderStop('unmount');
    if (clipRec.hotkey) {
      document.removeEventListener('keydown', clipRec.hotkey, true);
      clipRec.hotkey = null;
    }
    pluginClipRecorderStopPreBuffer();
  }

  function pluginClipRecorderGetStream() {
    // Try canvas capture first (game canvas), fallback to display media
    var canvas = document.querySelector('canvas');
    if (canvas && canvas.captureStream) {
      try {
        var fps = 30;
        return Promise.resolve(canvas.captureStream(fps));
      } catch(e) {}
    }
    // Fallback: ask for screen share
    if (navigator.mediaDevices && navigator.mediaDevices.getDisplayMedia) {
      return navigator.mediaDevices.getDisplayMedia({ video: { frameRate: 30 }, audio: false });
    }
    return Promise.reject(new Error('No capture source available'));
  }

  function pluginClipRecorderStart() {
    if (clipRec.recording) return;
    pluginClipRecorderGetStream().then(function(stream) {
      var quality = getPluginSetting('clipRecorder','quality') || 'high';
      var opts = CLIP_QUALITY[quality] || CLIP_QUALITY.high;
      // Try supported MIME types in order of preference
      var mimes = ['video/mp4;codecs=h264,aac','video/mp4','video/webm;codecs=vp9','video/webm;codecs=vp8','video/webm'];
      var mime = mimes.find(function(m) { return MediaRecorder.isTypeSupported(m); }) || '';
      if (mime) opts.mimeType = mime;

      try {
        clipRec.mediaRecorder = new MediaRecorder(stream, opts);
      } catch(e) {
        // Retry without explicit options
        clipRec.mediaRecorder = new MediaRecorder(stream);
      }
      clipRec.chunks = [];
      clipRec.startTime = Date.now();
      clipRec.recording = true;

      clipRec.mediaRecorder.ondataavailable = function(e) {
        if (e.data && e.data.size > 0) clipRec.chunks.push(e.data);
      };
      clipRec.mediaRecorder.onstop = function() {
        pluginClipRecorderSave(clipRec.chunks);
        clipRec.recording = false;
        clipRec.chunks = [];
        stream.getTracks().forEach(function(t) { t.stop(); });
      };
      clipRec.mediaRecorder.onerror = function(e) {
        kmLog('ClipRecorder error: ' + e.error);
        showToast('🎬 Recording error', 'err');
        clipRec.recording = false;
      };

      clipRec.mediaRecorder.start(200); // collect chunks every 200ms

      // Max length timer
      var maxLen = parseInt(getPluginSetting('clipRecorder','maxlen')) || 30;
      if (clipRec.maxTimer) clearTimeout(clipRec.maxTimer);
      clipRec.maxTimer = setTimeout(function() {
        if (clipRec.recording) pluginClipRecorderStop('maxlen');
      }, maxLen * 1000);

      // Update HUD
      pluginClipRecorderUpdateUI(true);
      showToast('🎬 Recording started', 'ok');
      kmLog('ClipRecorder: recording started (' + quality + ', max ' + maxLen + 's)');
    }).catch(function(err) {
      showToast('🎬 Could not start recording: ' + (err.message||'denied'), 'err');
      kmLog('ClipRecorder: stream error — ' + err.message);
    });
  }

  function pluginClipRecorderStop(reason) {
    if (clipRec.maxTimer) { clearTimeout(clipRec.maxTimer); clipRec.maxTimer = null; }
    if (!clipRec.recording || !clipRec.mediaRecorder) {
      pluginClipRecorderUpdateUI(false);
      return;
    }
    try {
      clipRec.mediaRecorder.stop();
    } catch(e) {
      clipRec.recording = false;
    }
    pluginClipRecorderUpdateUI(false);
    if (reason !== 'unmount') {
      var elapsed = ((Date.now() - clipRec.startTime) / 1000).toFixed(1);
      showToast('🎬 Clip saved (' + elapsed + 's)', 'ok');
      kmLog('ClipRecorder: stopped (' + reason + ', ' + elapsed + 's)');
    }
  }

  function pluginClipRecorderSave(chunks) {
    if (!chunks || !chunks.length) return;
    var mime = (clipRec.mediaRecorder && clipRec.mediaRecorder.mimeType) || 'video/webm';
    var blob = new Blob(chunks, { type: mime });
    // Determine extension — prefer mp4, fall back to webm
    var isMP4 = mime.indexOf('mp4') !== -1;
    var ext   = isMP4 ? 'mp4' : 'webm';
    var ts    = new Date().toISOString().replace(/[:.]/g,'-').slice(0,19);
    var url   = URL.createObjectURL(blob);
    var a     = document.createElement('a');
    a.href = url; a.download = 'rocketgoal-clip-' + ts + '.' + ext;
    a.click();
    setTimeout(function() { URL.revokeObjectURL(url); }, 10000);
    kmLog('ClipRecorder: saved ' + (blob.size/1024/1024).toFixed(1) + 'MB as ' + a.download + ' (' + mime + ')');
    // If WebM, show a helpful compatibility toast
    if (!isMP4) {
      setTimeout(function() {
        showToast('📹 Saved as .webm — open with VLC or Chrome if Windows says unsupported', 'info');
      }, 800);
    }
  }

  // Pre-buffer: keep a rolling window of canvas frames
  // On goal, grab the buffered chunks + record 3 more seconds = ~5-8s goal clip
  function pluginClipRecorderPreBuffer() {
    if (!isInstalled('clipRecorder')) return;
    if ((getPluginSetting('clipRecorder','autogoal')||'on') === 'off') return;
    pluginClipRecorderStopPreBuffer();

    var canvas = document.querySelector('canvas');
    if (!canvas || !canvas.captureStream) return;

    try {
      var stream = canvas.captureStream(30);
      var quality = getPluginSetting('clipRecorder','quality') || 'high';
      var opts = CLIP_QUALITY[quality] || CLIP_QUALITY.high;
      var mimes = ['video/mp4;codecs=h264,aac','video/mp4','video/webm;codecs=vp9','video/webm;codecs=vp8','video/webm'];
      var mime = mimes.find(function(m) { return MediaRecorder.isTypeSupported(m); }) || '';
      if (mime) opts.mimeType = mime;

      clipRec.preRec = new MediaRecorder(stream, opts);
      clipRec.preChunks = [];

      clipRec.preRec.ondataavailable = function(e) {
        if (e.data && e.data.size > 0) {
          clipRec.preChunks.push(e.data);
          // Keep rolling ~5s buffer (at 200ms chunks = 25 chunks)
          if (clipRec.preChunks.length > 25) clipRec.preChunks.shift();
        }
      };
      clipRec.preRec.start(200);
    } catch(e) {
      kmLog('ClipRecorder pre-buffer failed: ' + e.message);
    }
  }

  function pluginClipRecorderStopPreBuffer() {
    if (clipRec.preRec) {
      try { clipRec.preRec.stop(); } catch(e){}
      clipRec.preRec = null;
      clipRec.preChunks = [];
    }
  }

  function pluginClipRecorderAutoGoal() {
    if (!isInstalled('clipRecorder')) return;
    if ((getPluginSetting('clipRecorder','autogoal')||'on') === 'off') return;
    if (clipRec.recording) return; // already recording manually

    // Grab pre-buffer chunks and record 3s more post-goal
    var preChunks = (clipRec.preChunks || []).slice();
    kmLog('ClipRecorder: auto-clip triggered, pre-buffer chunks: ' + preChunks.length);

    var canvas = document.querySelector('canvas');
    if (!canvas || !canvas.captureStream) {
      showToast('🎬 Auto-clip: no canvas', 'warn');
      return;
    }

    try {
      var stream = canvas.captureStream(30);
      var quality = getPluginSetting('clipRecorder','quality') || 'high';
      var opts = Object.assign({}, CLIP_QUALITY[quality] || CLIP_QUALITY.high);
      var mimes = ['video/mp4;codecs=h264,aac','video/mp4','video/webm;codecs=vp9','video/webm;codecs=vp8','video/webm'];
      var mime = mimes.find(function(m) { return MediaRecorder.isTypeSupported(m); }) || '';
      if (mime) opts.mimeType = mime;

      var postRec = new MediaRecorder(stream, opts);
      var postChunks = [];

      postRec.ondataavailable = function(e) {
        if (e.data && e.data.size > 0) postChunks.push(e.data);
      };
      postRec.onstop = function() {
        var allChunks = preChunks.concat(postChunks);
        if (allChunks.length) {
          pluginClipRecorderSave(allChunks);
          showToast('🎬 Goal clip saved!', 'ok');
        }
        stream.getTracks().forEach(function(t) { t.stop(); });
      };

      postRec.start(200);
      setTimeout(function() {
        if (postRec.state !== 'inactive') postRec.stop();
      }, 3000);
    } catch(e) {
      kmLog('ClipRecorder auto-goal error: ' + e.message);
    }
  }

  function pluginClipRecorderUpdateUI(recording) {
    var hud = document.getElementById('km-hud');
    var indicator = document.getElementById('km-rec-dot');
    if (recording && hud && !indicator) {
      var dot = document.createElement('span');
      dot.id = 'km-rec-dot';
      dot.style.cssText = 'display:inline-block;width:8px;height:8px;background:#f87171;border-radius:50%;'
        +'animation:km-rec-blink 1s ease-in-out infinite;margin-left:2px;vertical-align:middle;';
      hud.appendChild(dot);
    } else if (!recording && indicator) {
      indicator.remove();
    }
  }

  // ─── OPTIMIZER ────────────────────────────────────────────────────────────
  var optimizerActive = false;
  var optimizerStyle  = null;

  // ─── OPTIMIZER RUNTIME STATE ─────────────────────────────────────────────
  var _optVisLockDescs  = null;
  var _optSilencedLog   = false;
  var _optShadowStyle   = null;
  var _optExtraStyle    = null;  // body hw-acc, canvas tweaks
  var _optBlurSaved     = null;  // saved window.onblur
  var _optOrigConsole   = null;  // saved console fns before silence
  var _optWebSockOrig   = null;  // saved WebSocket.prototype.send
  var _optCanvasOrig    = null;  // saved { w, h, styleW, styleH } before upscaler
  var _optFrustumStyle  = null;  // style element for frustum clip-path

  function _optGet(id, def) {
    var v = getPluginSetting('optimizer', id);
    if (v === null || v === undefined) return def;
    return v === 'on' || v === true;
  }

  function pluginOptimizerMount() {
    if (!isInstalled('optimizer')) { pluginOptimizerUnmount(); return; }
    if (optimizerActive) return;
    optimizerActive = true;

    var css = [], extraCss = [];
    var o = _optGet;  // shorthand: o('id', default)

    // ── GROUP 1: Memory & Cleanup ──────────────────────────────────────────
    // #1 Hide ad banners
    if (o('hideads','on')) {
      css.push('[class*="playgama"],[class*="banner"],[id*="banner"],[id*="playgama"],'
        +'[class*="ad-"],[class*="-ad"],[id*="ad-"],[class*="advertisement"],'
        +'[class*="sponsor"],[id*="sponsor"],[class*="promo"]'
        +'{display:none!important;visibility:hidden!important;pointer-events:none!important;height:0!important;overflow:hidden!important;}');
      setTimeout(function() {
        var n=0;
        document.querySelectorAll('[id*="playgama"],[class*="playgama"],[id*="banner-container"]').forEach(function(el){el.remove();n++;});
        if(n) kmLog('Optimizer: removed '+n+' ad DOM nodes');
      }, 500);
    }
    // #2 Block analytics XHR
    if (o('blockga','on')) pluginOptimizerBlockGA();
    // #3 Hide chat/social
    if (o('hidechat','on')) {
      css.push('[class*="chat"],[id*="chat"],[class*="social"],[id*="social"],'
        +'[class*="leaderboard-global"],[class*="feed"],[class*="notification-bell"]{display:none!important;}');
    }
    // #4 Clear perf timing logs — clears accumulated timing entries from memory
    if (o('clearmeasures',false)) {
      try { window.performance.clearMeasures(); } catch(e) {}
    }
    // #5 Clear perf markers
    if (o('clearmarks',false)) {
      try { window.performance.clearMarks(); } catch(e) {}
    }
    // #6 Block tracking beacons (sendBeacon used by analytics)
    if (o('blockbeacon','on')) {
      if (!window._kmBeaconBlocked) {
        window._kmBeaconBlocked = true;
        navigator.sendBeacon = function() { return true; };
      }
    }

    // ── GROUP 2: GPU & Rendering ───────────────────────────────────────────
    // #7 GPU canvas hints — dedicated compositor layer, no CPU compositing
    if (o('gpucanvas','on')) {
      extraCss.push('canvas{will-change:transform!important;transform:translateZ(0)!important;backface-visibility:hidden!important;}');
    }
    // #8 Hardware-accelerate body element
    if (o('bodyhwacc','on')) {
      extraCss.push('body{will-change:transform;transform:translateZ(0);}');
    }
    // #9 Strip UI shadows & blur (expensive for compositor)
    if (o('stripshadows',false)) {
      if (!_optShadowStyle) {
        _optShadowStyle = document.createElement('style');
        _optShadowStyle.id = 'km-opt-noshadow';
        _optShadowStyle.textContent = '*:not(canvas):not(#km-menu):not([id^="km-"]):not([class^="km-"])'
          +'{box-shadow:none!important;text-shadow:none!important;filter:none!important;}';
        document.head.appendChild(_optShadowStyle);
      }
    }
    // #10 Remove canvas outline/border (eliminates GPU border-draw overhead)
    if (o('canvasnoborder','on')) {
      extraCss.push('canvas{box-shadow:none!important;outline:none!important;border:none!important;}');
    }
    // #11 Force pixelated rendering (stops GPU anti-aliasing per-pixel math)
    if (o('lowqual',false)) {
      extraCss.push('canvas{image-rendering:pixelated!important;image-rendering:crisp-edges!important;}');
    }

    // #11b Lock devicePixelRatio to 1.0 — prevents Unity rendering at 2x on HiDPI screens.
    // On a Retina/4K display Unity renders at 2x by default, doubling GPU load.
    // This forces DPR=1 which cuts GPU work in half on high-density displays.
    if (o('hires',false)) {
      if (!window._kmDPROrig) {
        try {
          window._kmDPROrig = window.devicePixelRatio;
          Object.defineProperty(window, 'devicePixelRatio', { get: function(){ return 1; }, configurable: true });
          kmLog('Optimizer: devicePixelRatio locked to 1 (was '+window._kmDPROrig+')');
        } catch(e) {}
      }
    }

    // #12 Upscaler — render at a higher canvas resolution and display at full width.
    // This can produce sharper visuals while still fitting the viewport.
    (function() {
      var up = getPluginSetting('optimizer','upscaler') || 'off';
      if (up === 'off') return;
      var mult = parseFloat(up.replace('x','')) || 1;
      if (mult <= 1) return;
      setTimeout(function() {
        var cv = document.querySelector('canvas');
        if (!cv) { kmLog('Optimizer: upscaler — no canvas found yet'); return; }
        if (!_optCanvasOrig) {
          _optCanvasOrig = {
            w: cv.width, h: cv.height,
            styleW: cv.style.width, styleH: cv.style.height,
            styleIR: cv.style.imageRendering
          };
        }
        var newW = Math.round(window.innerWidth * mult);
        var newH = Math.round(window.innerHeight * mult);
        cv.width = newW;
        cv.height = newH;
        cv.style.width = '100vw';
        cv.style.height = '100vh';
        cv.style.imageRendering = 'auto';
        kmLog('Optimizer: upscaler ' + up + ' applied (' + newW + 'x' + newH + ')');
        showToast('⚡ Upscaler ' + up + ' active', 'ok');
      }, 800);
    })();

    // #13 Frustum clip-path culling — reduces compositor paint area.
    // CSS clip-path tells the browser's compositor to discard pixels outside
    // the clip region at rasterization time, not just hide them. On a 1920px
    // wide canvas the browser normally composites all 1920px every frame.
    // Clipping to a slightly inset inset() reduces the paint rect.
    // Note: this is a compositor-level hint, not a true frustum cull — it
    // has no effect on what Unity renders internally, only on what the
    // browser paints to screen. Useful on low-end GPUs with slow compositing.
    if (o('frustum',false) && !_optFrustumStyle) {
      _optFrustumStyle = document.createElement('style');
      _optFrustumStyle.id = 'km-opt-frustum';
      // clip-path on the GAME canvas causes black screen — skip canvas, apply to body wrapper only
      // This still reduces compositor paint rect on elements surrounding the canvas
      _optFrustumStyle.textContent = 'body > *:not(canvas):not(#km-menu):not([id^="km-"]){contain:layout paint;}';
      document.head.appendChild(_optFrustumStyle);
      kmLog('Optimizer: frustum containment active (safe mode)');
    }

    // ── GROUP 3: CPU & Threading ───────────────────────────────────────────
    // #12 Page visibility lock — prevents browser from throttling WebGL when tabbed out
    if (o('vislock','on') && !_optVisLockDescs) {
      try {
        _optVisLockDescs = {
          visState: Object.getOwnPropertyDescriptor(document, 'visibilityState'),
          hidden:   Object.getOwnPropertyDescriptor(document, 'hidden'),
        };
        Object.defineProperty(document, 'visibilityState', { get: function(){ return 'visible'; }, configurable:true });
        Object.defineProperty(document, 'hidden',          { get: function(){ return false; },    configurable:true });
      } catch(e) { _optVisLockDescs = null; }
    }
    // #13 Disable onblur throttle — keeps FPS when you click another window
    if (o('noblur','on')) {
      _optBlurSaved = window.onblur;
      window.onblur = null;
      window.onfocus = null;
    }
    // #14 Lock page scroll (prevents scroll from triggering layout reflows)
    if (o('nooverflow','on')) {
      css.push('html,body{overflow:hidden!important;scroll-behavior:auto!important;}');
    }
    // #15 Disable right-click menu (prevents context menu from pausing game loop)
    if (o('nocontextmenu','on')) {
      document._kmCtxMenu = document.oncontextmenu;
      document.oncontextmenu = function(e){ e.preventDefault(); return false; };
    }
    // #16 Silence game console — Unity spam costs JS thread CPU per message
    if (o('silencelog',false) && !_optSilencedLog) {
      // Delay silencing so startup logs (login, init) still appear in KeyMod console
      setTimeout(function() {
        if (!_optSilencedLog) return; // was restored already
        console.log   = function(){};
        console.warn  = function(){};
        console.error = function(){};
        kmLog('Optimizer: game console silenced (startup logs preserved)');
      }, 5000);
      _optSilencedLog = true;
    }

    // ── GROUP 4: Network & Data ────────────────────────────────────────────
    // #17 Block analytics fetch calls
    if (o('blockfetch','on')) {
      if (!window._kmFetchPatched) {
        window._kmFetchPatched = true;
        var _origFetch = window.fetch;
        window.fetch = function() {
          var url = typeof arguments[0] === 'string' ? arguments[0] : (arguments[0] && arguments[0].url) || '';
          if (url.indexOf('analytics') !== -1 || url.indexOf('gameanalytics') !== -1
            || url.indexOf('playfab') !== -1 || url.indexOf('doubleclick') !== -1
            || url.indexOf('telemetry') !== -1) {
            return Promise.resolve(new Response('', { status: 200 }));
          }
          return _origFetch.apply(this, arguments);
        };
      }
    }
    // #18 Limit WebSocket packet size — blocks large tracking payloads (>2KB)
    // while allowing small game packets through (typically <200 bytes)
    if (o('blockwebsock',false) && !_optWebSockOrig) {
      _optWebSockOrig = WebSocket.prototype.send;
      WebSocket.prototype.send = function(data) {
        // Allow small game packets, block large telemetry payloads
        var size = typeof data === 'string' ? data.length : (data && data.byteLength) || 0;
        if (size > 2048) { return; }  // drop oversized packets
        return _optWebSockOrig.apply(this, arguments);
      };
    }
    // #19 Stop background asset loading — fires window.stop() to halt any
    // deferred image/CSS loads that aren't the game itself
    if (o('stopload',false)) {
      try { window.stop(); } catch(e) {}
    }

    // ── GROUP 5: Input & UI ────────────────────────────────────────────────
    // #20 Force canvas focus on mount — ensures keyboard events go to game
    if (o('canvasfocus','on')) {
      setTimeout(function() {
        var cv = document.querySelector('canvas');
        if (cv) { cv.focus(); cv.setAttribute('tabindex','0'); }
      }, 300);
    }

    // #22 Lock scroll to top — ensures canvas isn't offset by scroll
    if (o('scrolltop','on')) {
      window.scrollTo(0, 0);
      window.addEventListener('scroll', function(){ window.scrollTo(0,0); }, { passive:true });
    }
    // #23 Body overflow hidden — prevents layout reflow from body scroll
    // (handled in CSS above with nooverflow, this adds the inline style too)
    if (o('overflowbody','on')) {
      document.body.style.overflow = 'hidden';
    }
    // #24 Prefer high-performance GPU — hint to browser to pick discrete GPU
    if (o('prefersperf',false)) {
      // Set powerPreference on any new WebGL contexts; can't change existing ones
      var _origGetCtx = HTMLCanvasElement.prototype.getContext;
      HTMLCanvasElement.prototype.getContext = function(type, opts) {
        if (type === 'webgl' || type === 'webgl2') {
          opts = Object.assign({ powerPreference: 'high-performance' }, opts || {});
        }
        return _origGetCtx.call(this, type, opts);
      };
    }
    // #25 CSS containment on KeyMod UI — stops UI reflows affecting game layout
    if (o('csscontain','on')) {
      // layout style only — paint containment on UI overlays can cause black screen artifacts
      extraCss.push('#km-menu{contain:layout style!important;}#km-hud{contain:layout style!important;}#km-leaderboard{contain:layout style!important;}');
    }

    // Apply CSS
    if (css.length) {
      optimizerStyle = document.createElement('style');
      optimizerStyle.id = 'km-optimizer-style';
      optimizerStyle.textContent = css.join('\n');
      document.head.appendChild(optimizerStyle);
    }
    if (extraCss.length) {
      _optExtraStyle = document.createElement('style');
      _optExtraStyle.id = 'km-optimizer-extra';
      _optExtraStyle.textContent = extraCss.join('\n');
      document.head.appendChild(_optExtraStyle);
    }

    showToast('⚡ Optimizer active', 'ok');
    kmLog('Optimizer mounted');
  }

  function pluginOptimizerUnmount() {
    if (!optimizerActive) return;
    optimizerActive = false;
    if (optimizerStyle)  { optimizerStyle.remove();  optimizerStyle = null; }
    if (_optShadowStyle) { _optShadowStyle.remove();  _optShadowStyle = null; }
    if (_optExtraStyle)  { _optExtraStyle.remove();   _optExtraStyle = null; }

    // Restore visibility lock
    if (_optVisLockDescs) {
      try {
        if (_optVisLockDescs.visState) Object.defineProperty(document, 'visibilityState', _optVisLockDescs.visState);
        if (_optVisLockDescs.hidden)   Object.defineProperty(document, 'hidden',          _optVisLockDescs.hidden);
      } catch(e) {}
      _optVisLockDescs = null;
    }
    // Restore blur handlers
    if (_optBlurSaved !== null) { window.onblur = _optBlurSaved; _optBlurSaved = null; }
    // Restore context menu
    if (document._kmCtxMenu !== undefined) { document.oncontextmenu = document._kmCtxMenu; delete document._kmCtxMenu; }
    // Restore console
    if (_optSilencedLog) {
      _optSilencedLog = false;
      console.log   = function(){ _log.apply(console, arguments);  _silentSniff('log',  arguments); };
      console.warn  = function(){ _warn.apply(console, arguments); _silentSniff('warn', arguments); };
      console.error = function(){ _err.apply(console, arguments);  _silentSniff('err',  arguments); };
    }
    // Restore WebSocket
    if (_optWebSockOrig) { WebSocket.prototype.send = _optWebSockOrig; _optWebSockOrig = null; }

    // Restore DPR if locked
    if (window._kmDPROrig !== undefined) {
      try {
        Object.defineProperty(window, 'devicePixelRatio', { get: function(){ return window._kmDPROrig; }, configurable: true });
      } catch(e) {}
      delete window._kmDPROrig;
    }

    // Restore canvas resolution if we scaled it
    if (_optCanvasOrig) {
      var cv = document.querySelector('canvas');
      if (cv) {
        cv.width            = _optCanvasOrig.w;
        cv.height           = _optCanvasOrig.h;
        cv.style.width      = _optCanvasOrig.styleW;
        cv.style.height     = _optCanvasOrig.styleH;
        cv.style.imageRendering = _optCanvasOrig.styleIR;
      }
      _optCanvasOrig = null;
    }
    // Remove frustum clip
    if (_optFrustumStyle) { _optFrustumStyle.remove(); _optFrustumStyle = null; }

    document.body.style.overflow = '';
    kmLog('Optimizer unmounted');
  }

  function pluginOptimizerBlockGA() {
    if (window._kmXHRPatched) return;
    window._kmXHRPatched = true;
    var _origOpen = XMLHttpRequest.prototype.open;
    XMLHttpRequest.prototype.open = function(method, url) {
      if (typeof url === 'string' && (
        url.indexOf('gameanalytics.com') !== -1 ||
        url.indexOf('api.gameanalytics')  !== -1 ||
        url.indexOf('rubick.gameanalytics') !== -1
      )) {
        this._kmBlocked = true;
        return;
      }
      return _origOpen.apply(this, arguments);
    };
    var _origSend = XMLHttpRequest.prototype.send;
    XMLHttpRequest.prototype.send = function() {
      if (this._kmBlocked) return;
      return _origSend.apply(this, arguments);
    };
    kmLog('Optimizer: analytics XHR blocked');
  }

  // ─── DÉJÀ VU DOSSIER ─────────────────────────────────────────────────────
  // Per-opponent encounter tracking with notes, record, and MMR history
  var dejaVuNotes = lsGet('dejaVuNotes5', {});  // name -> { note, mmrHistory:[], encounters:0 }

  function pluginDejaVuOnMatchStart(oppId, oppName) {
    if (!isInstalled('dejaVu') || !oppId) return;
    if ((getPluginSetting('dejaVu','showOnMatch') || 'on') !== 'on') return;

    var history = opponentLog.filter(function(o) { return o.opponentId === oppId; });
    if (!history.length) {
      showToast('🗂️ First encounter: ' + (oppName || 'Opponent'), 'info');
      kmLog('DejaVu: first time vs ' + (oppName || oppId));
      return;
    }

    var wins   = history.filter(function(o) { return o.result === 'Win'; }).length;
    var losses = history.length - wins;
    var note   = dejaVuNotes[oppId] ? dejaVuNotes[oppId].note : null;
    var lastMMR = history[0].mmrGuess ? '~' + history[0].mmrGuess + ' MMR' : '';

    var label = oppName || history[0].opponent || 'Opponent';
    var msg = '🗂️ ' + label + ' — ' + history.length + 'x played ('
      + wins + 'W ' + losses + 'L)' + (lastMMR ? '  ' + lastMMR : '');
    showToast(msg, wins >= losses ? 'ok' : 'warn');
    if (note) {
      setTimeout(function() {
        showToast('📝 Note: "' + note + '"', 'info');
      }, 1200);
    }
    kmLog('DejaVu: ' + msg + (note ? ' | Note: "' + note + '"' : ''));
  }

  function pluginDejaVuSaveNote(oppId, oppName, note) {
    if (!oppId) return;
    if (!dejaVuNotes[oppId]) dejaVuNotes[oppId] = { note: '', mmrHistory: [], encounters: 0, name: oppName || '' };
    dejaVuNotes[oppId].note = note;
    dejaVuNotes[oppId].name = oppName || dejaVuNotes[oppId].name || '';
    dejaVuNotes[oppId].encounters = (opponentLog.filter(function(o) { return o.opponentId === oppId; }).length);
    lsSet('dejaVuNotes5', dejaVuNotes);
    showToast('📝 Note saved for ' + (oppName || 'Opponent'), 'ok');
    kmLog('DejaVu: note saved for ' + (oppName || oppId) + ': "' + note + '"');
  }

  function pluginDejaVuNotePrompt(oppId, oppName) {
    if (!isInstalled('dejaVu') || !oppId) return;
    if ((getPluginSetting('dejaVu','notePrompt') || 'off') !== 'on') return;

    // Show a small note input overlay
    var old = document.getElementById('km-dejavu-prompt'); if (old) old.remove();
    var existing = (dejaVuNotes[oppId]) ? (dejaVuNotes[oppId].note || '') : '';
    var label = oppName || (dejaVuNotes[oppId] && dejaVuNotes[oppId].name) || 'Opponent';

    var el = document.createElement('div');
    el.id = 'km-dejavu-prompt';
    el.style.cssText = 'position:fixed;bottom:110px;right:22px;z-index:999995;'
      + 'background:rgba(6,8,16,0.97);border:1px solid rgba(255,255,255,0.1);border-top:2px solid #3b82f6;'
      + 'padding:14px 16px;width:290px;font-family:"DM Mono",monospace;'
      + 'box-shadow:0 20px 60px rgba(0,0,0,0.8);';
    el.innerHTML = '<div style="font-size:9px;letter-spacing:2px;color:rgba(255,255,255,0.3);margin-bottom:8px;text-transform:uppercase">📝 Note for ' + label + '</div>'
      + '<input id="km-dejavu-input" type="text" placeholder="e.g. fakes kickoffs, weak backboard" '
      + 'style="width:100%;background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.12);'
      + 'color:#fff;font-family:\'DM Mono\',monospace;font-size:11px;padding:7px 10px;outline:none;box-sizing:border-box;" '
      + 'value="' + existing.replace(/"/g, '&quot;') + '">'
      + '<div style="display:flex;gap:6px;margin-top:8px">'
      + '<button id="km-dejavu-save" style="flex:1;padding:6px;background:#3b82f6;border:none;color:#fff;font-family:\'DM Mono\',monospace;font-size:10px;cursor:pointer;letter-spacing:0.5px">Save</button>'
      + '<button id="km-dejavu-cancel" style="flex:1;padding:6px;background:transparent;border:1px solid rgba(255,255,255,0.1);color:rgba(255,255,255,0.4);font-family:\'DM Mono\',monospace;font-size:10px;cursor:pointer">Dismiss</button>'
      + '</div>';
    document.body.appendChild(el);

    var inp = el.querySelector('#km-dejavu-input');
    if (inp) { inp.focus(); inp.select(); }

    el.querySelector('#km-dejavu-save').addEventListener('click', function() {
      var val = inp ? inp.value.trim() : '';
      pluginDejaVuSaveNote(oppId, oppName, val);
      el.remove();
    });
    el.querySelector('#km-dejavu-cancel').addEventListener('click', function() { el.remove(); });

    // Auto-dismiss after 15s
    setTimeout(function() { if (el.parentNode) el.remove(); }, 15000);
  }

  // ─── FLIP TIMER ───────────────────────────────────────────────────────────
  var _ftEl       = null;
  var _ftRaf      = null;
  var _ftLastJump = 0;    // performance.now() of last detected jump
  var _ftActive   = false;
  var _ftKeyDown  = {};

  function pluginFlipTimerMount() {
    if (!isInstalled('flipTimer')) { pluginFlipTimerUnmount(); return; }
    if (_ftEl) return;

    var pos = getPluginSetting('flipTimer','pos') || 'top-center';
    var posCSS = pos === 'bottom-center' ? 'bottom:110px;left:50%;transform:translateX(-50%)'
               : pos === 'top-left'      ? 'top:60px;left:22px;transform:none'
               : pos === 'top-right'     ? 'top:60px;right:22px;transform:none'
               :                           'top:60px;left:50%;transform:translateX(-50%)';

    _ftEl = document.createElement('div');
    _ftEl.id = 'km-flip-timer';
    _ftEl.style.cssText = 'position:fixed;'+posCSS+';z-index:99996;pointer-events:none;'
      +'width:180px;text-align:center;font-family:"DM Mono",monospace;';
    document.body.appendChild(_ftEl);

    // Key listener
    var jumpKey  = (getPluginSetting('flipTimer','jumpkey') || 'Space').toLowerCase();
    var device   = getPluginSetting('flipTimer','device') || 'KBM';

    if (device === 'KBM') {
      window._ftKeyHandler = function(e) {
        var k = e.key === ' ' ? 'space' : e.key.toLowerCase();
        if (k === jumpKey.toLowerCase() && !_ftKeyDown[k]) {
          _ftKeyDown[k] = true;
          _ftLastJump = performance.now();
          _ftActive = true;
        }
      };
      window._ftKeyUpHandler = function(e) {
        var k = e.key === ' ' ? 'space' : e.key.toLowerCase();
        _ftKeyDown[k] = false;
      };
      window.addEventListener('keydown', window._ftKeyHandler,   true);
      window.addEventListener('keyup',   window._ftKeyUpHandler, true);
    }
    // Controller support via Gamepad API polling
    if (device === 'Controller') {
      var btnMap = { A:0,B:1,X:2,Y:3,LB:4,RB:5,LT:6,RT:7,L3:10,R3:11 };
      var jumpBtn = btnMap[getPluginSetting('flipTimer','jumpbtn') || 'A'] || 0;
      var prevPressed = false;
      window._ftGamepadInterval = setInterval(function() {
        var gp = navigator.getGamepads ? navigator.getGamepads()[0] : null;
        if (!gp) return;
        var pressed = gp.buttons[jumpBtn] && gp.buttons[jumpBtn].pressed;
        if (pressed && !prevPressed) {
          _ftLastJump = performance.now();
          _ftActive = true;
        }
        prevPressed = pressed;
      }, 16); // ~60Hz poll
    }

    var _ftSize = getPluginSetting('flipTimer','size') || 'medium';
    var _ftDims = _ftSize === 'small'  ? { w:140, big:18, small:9, bar:3, pad:'5px 10px' }
                : _ftSize === 'large'  ? { w:220, big:30, small:11, bar:5, pad:'9px 18px' }
                :                        { w:180, big:24, small:10, bar:4, pad:'7px 14px' };
    _ftEl.style.width = _ftDims.w + 'px';

    function ftDraw() {
      if (!_ftEl || !isInstalled('flipTimer')) { pluginFlipTimerUnmount(); return; }
      _ftRaf = requestAnimationFrame(ftDraw);

      var windowMs  = parseInt(getPluginSetting('flipTimer','window') || '1450');

      if (!_ftActive || !inMatch) {
        // IDLE state — green pill showing window duration
        _ftEl.innerHTML =
          '<div style="background:rgba(0,0,0,0.72);border:1px solid rgba(74,222,128,0.25);'
          +'border-left:3px solid #4ade80;padding:'+_ftDims.pad+';border-radius:7px;'
          +'display:flex;align-items:center;justify-content:space-between;gap:10px;">'
          +'<div style="display:flex;flex-direction:column;gap:2px;">'
          +'<div style="font-size:'+_ftDims.small+'px;color:rgba(255,255,255,0.3);letter-spacing:0.5px;font-family:monospace;">FLIP TIMER</div>'
          +'<div style="font-size:'+_ftDims.small+'px;color:rgba(74,222,128,0.6);font-family:monospace;">READY</div>'
          +'</div>'
          +'<div style="font-size:'+_ftDims.big+'px;font-weight:700;color:#4ade80;font-family:monospace;font-variant-numeric:tabular-nums;">'
          +windowMs+'<span style="font-size:'+(Math.round(_ftDims.small*0.9))+'px;opacity:0.4;margin-left:1px">ms</span>'
          +'</div>'
          +'</div>';
        return;
      }

      var elapsed   = performance.now() - _ftLastJump;
      var remaining = windowMs - elapsed;
      var pct       = Math.max(0, Math.min(1, remaining / windowMs));

      if (remaining > 0) {
        // Active countdown — color transitions green → yellow → red
        var col = pct > 0.55 ? '#4ade80' : pct > 0.28 ? '#fbbf24' : '#f87171';
        var borderCol = col;
        _ftEl.innerHTML =
          '<div style="background:rgba(0,0,0,0.82);border:1px solid rgba(255,255,255,0.1);'
          +'border-left:3px solid '+borderCol+';padding:'+_ftDims.pad+';border-radius:7px;">'
          +'<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:'+Math.round(_ftDims.bar+2)+'px;">'
          +'<div style="font-size:'+_ftDims.small+'px;color:rgba(255,255,255,0.3);letter-spacing:0.5px;font-family:monospace;">FLIP WINDOW</div>'
          +'<div style="font-size:'+_ftDims.big+'px;font-weight:700;color:'+col+';font-family:monospace;font-variant-numeric:tabular-nums;line-height:1;">'
          +Math.ceil(remaining)+'<span style="font-size:'+(Math.round(_ftDims.small*0.9))+'px;opacity:0.4;margin-left:1px">ms</span></div>'
          +'</div>'
          +'<div style="height:'+_ftDims.bar+'px;background:rgba(255,255,255,0.08);border-radius:'+_ftDims.bar+'px;overflow:hidden;">'
          +'<div style="height:100%;width:'+(pct*100).toFixed(2)+'%;background:'+col+';border-radius:'+_ftDims.bar+'px;transition:width 0.04s linear,background 0.2s;"></div>'
          +'</div>'
          +'</div>';
      } else {
        // NO FLIP — bright red, pulsing
        var noFlipAge = elapsed - windowMs;
        _ftEl.innerHTML =
          '<div style="background:rgba(180,0,0,0.9);border:2px solid rgba(248,113,113,0.5);'
          +'padding:'+_ftDims.pad+';border-radius:7px;text-align:center;'
          +'animation:km-ft-pulse 0.35s ease-in-out infinite alternate;">'
          +'<div style="font-size:'+_ftDims.big+'px;font-weight:800;color:#fff;letter-spacing:2px;'
          +'font-family:monospace;text-shadow:0 0 12px rgba(255,100,100,0.8);">NO FLIP</div>'
          +'</div>';
        if (noFlipAge > 1800) _ftActive = false;
      }
    }
    _ftRaf = requestAnimationFrame(ftDraw);

    // Inject pulse keyframe
    if (!document.getElementById('km-ft-style')) {
      var s = document.createElement('style'); s.id = 'km-ft-style';
      s.textContent = '@keyframes km-ft-pulse{from{opacity:1;transform:scale(1)}to{opacity:0.7;transform:scale(0.97)}}';
      document.head.appendChild(s);
    }
    // Make flip timer select dropdowns taller for easier scrolling
    var ftStyle = document.getElementById('km-ft-select-style');
    if (!ftStyle) {
      ftStyle = document.createElement('style'); ftStyle.id = 'km-ft-select-style';
      ftStyle.textContent = '.km-plugin-setting[data-plugin="flipTimer"]{max-height:160px;overflow-y:auto;}';
      document.head.appendChild(ftStyle);
    }
    kmLog('Flip Timer mounted — device:' + device + ' key:' + (getPluginSetting('flipTimer','jumpkey')||'Space'));
  }

  function pluginFlipTimerUnmount() {
    if (_ftRaf) { cancelAnimationFrame(_ftRaf); _ftRaf = null; }
    if (_ftEl)  { _ftEl.remove(); _ftEl = null; }
    if (window._ftKeyHandler)    window.removeEventListener('keydown', window._ftKeyHandler, true);
    if (window._ftKeyUpHandler)  window.removeEventListener('keyup', window._ftKeyUpHandler, true);
    if (window._ftGamepadInterval) { clearInterval(window._ftGamepadInterval); window._ftGamepadInterval = null; }
    _ftActive = false; _ftKeyDown = {};
  }

  // ─── THEME PLUGIN ─────────────────────────────────────────────────────────
  // Theme is locked to BakkesMod unless this plugin is installed.
  // On install: apply the saved or selected preset.
  // On uninstall: force back to bakkesmod.

  function pluginThemeOnInstall() {
    // Theme plugin removed. Keep default theme.
  }

  function pluginThemeOnUninstall() {
    // Theme plugin removed. Keep default theme.
  }

  function pluginThemeOnSettingChange() {
    // Theme plugin removed.
  }

  // ─── LOADING SCREEN PLUGIN ────────────────────────────────────────────────
  // Loading screen plugin removed.

  function pluginLoadingGetStyle() {
    return 'off';
  }

  // ─── VISUAL FX ────────────────────────────────────────────────────────────
  var _vfxStyle     = null;
  var _vfxPresets = {
    off:        { vibrance:100, blur:0, contrast:100, blacks:0, whites:100, hue:0 },
    vivid:      { vibrance:160, blur:0, contrast:115, blacks:5,  whites:105, hue:0 },
    cinematic:  { vibrance:80,  blur:0, contrast:130, blacks:15, whites:90,  hue:0 },
    night:      { vibrance:60,  blur:0, contrast:90,  blacks:20, whites:80,  hue:200 },
    washed:     { vibrance:50,  blur:1, contrast:85,  blacks:0,  whites:115, hue:0 },
    vigilante:  { vibrance:0,   blur:0, contrast:140, blacks:10, whites:95,  hue:0 },
    retro:      { vibrance:70,  blur:1, contrast:105, blacks:8,  whites:108, hue:30 },
  };

  function pluginVFXMount() {
    if (!isInstalled('visualFX')) { pluginVFXUnmount(); return; }

    var preset = getPluginSetting('visualFX','preset') || 'off';
    var vals;
    if (preset !== 'custom' && _vfxPresets[preset]) {
      vals = _vfxPresets[preset];
      // Sync slider values to preset
      Object.keys(vals).forEach(function(k) { setPluginSetting('visualFX',k,vals[k]); });
    } else {
      vals = {
        vibrance: parseInt(getPluginSetting('visualFX','vibrance')) || 100,
        blur:     parseFloat(getPluginSetting('visualFX','blur'))   || 0,
        contrast: parseInt(getPluginSetting('visualFX','contrast')) || 100,
        blacks:   parseInt(getPluginSetting('visualFX','blacks'))   || 0,
        whites:   parseInt(getPluginSetting('visualFX','whites'))   || 100,
        hue:      parseInt(getPluginSetting('visualFX','hue'))      || 0,
      };
    }

    if (preset === 'off') { pluginVFXUnmount(); return; }

    // Build CSS filter string
    // Vibrance > 100 = saturate more, < 100 = desaturate
    // blacks/whites = brightness + levels approximation via brightness+contrast
    var saturate  = vals.vibrance;
    var brightness= Math.round(100 - vals.blacks / 2 + (vals.whites - 100) / 4);
    var filterStr = [
      'saturate(' + saturate   + '%)',
      'blur('     + vals.blur  + 'px)',
      'contrast(' + vals.contrast + '%)',
      'brightness(' + brightness + '%)',
      'hue-rotate(' + vals.hue + 'deg)',
    ].join(' ');

    if (!_vfxStyle) {
      _vfxStyle = document.createElement('style');
      _vfxStyle.id = 'km-vfx-style';
      document.head.appendChild(_vfxStyle);
    }
    _vfxStyle.textContent = 'canvas{filter:' + filterStr + '!important;}';
    kmLog('VFX: ' + preset + ' filter=' + filterStr);
  }

  function pluginVFXUnmount() {
    if (_vfxStyle) { _vfxStyle.remove(); _vfxStyle = null; }
  }

  // Night mode check on load
  setInterval(checkNightMode, 60000);

  function boot() {
    injectStyles();

    (function() {
      // Only apply GPU hints to the Unity game canvas — not every canvas on the page.
      // We wait for the canvas to have content (non-zero size) before promoting it,
      // applying too early to a blank canvas creates a black GPU layer.
      function _applyGPUHints() {
        document.querySelectorAll('canvas').forEach(function(cv) {
          if (cv.dataset.kmGpu) return;
          // Skip KeyMod overlay canvases
          if (cv.id && cv.id.indexOf('km-') === 0) return;
          // Skip tiny or zero-size canvases — Unity canvas is full-screen
          if (cv.offsetWidth < 200 || cv.offsetHeight < 200) return;
          cv.dataset.kmGpu = '1';
          cv.style.willChange = 'transform';
          cv.style.transform  = 'translateZ(0)';
          cv.style.backfaceVisibility = 'hidden';
        });
      }
      // Don't run immediately at boot — wait for DOM to settle
      // Unity canvas appears after page load, not at document-start
      setTimeout(_applyGPUHints, 2000);
      var _gpuObs = new MutationObserver(function(muts) {
        muts.forEach(function(m) {
          m.addedNodes.forEach(function(n) {
            if (n.tagName === 'CANVAS') setTimeout(_applyGPUHints, 500);
            if (n.querySelectorAll) n.querySelectorAll('canvas').forEach(function(){ setTimeout(_applyGPUHints, 500); });
          });
        });
      });
      _gpuObs.observe(document.documentElement, { childList: true, subtree: true });
    })();

    (function() {
      if (window._kmBootXHRPatched) return;
      window._kmBootXHRPatched = true;
      var _xhrOpen = XMLHttpRequest.prototype.open;
      XMLHttpRequest.prototype.open = function(m, u) {
        if (typeof u === 'string' && (
          u.indexOf('gameanalytics') !== -1 ||
          u.indexOf('rubick.gameanalytics') !== -1 ||
          u.indexOf('googletagmanager') !== -1 ||
          u.indexOf('google-analytics') !== -1 ||
          u.indexOf('doubleclick') !== -1
        )) {
          this._kmBootBlocked = true;
          this._km_url = u;  // still track URL for RTT patch
          return;
        }
        return _xhrOpen.apply(this, arguments);
      };
      var _xhrSend = XMLHttpRequest.prototype.send;
      XMLHttpRequest.prototype.send = function() {
        if (this._kmBootBlocked) return;
        return _xhrSend.apply(this, arguments);
      };
    })();

    (function() {
      var _ptrLockActive = false;
      document.addEventListener('click', function(e) {
        if (_ptrLockActive) return;
        var cv = document.querySelector('canvas');
        if (cv && !document.pointerLockElement && e.target === cv) {
          cv.requestPointerLock && cv.requestPointerLock().catch && cv.requestPointerLock().catch(function(){});
        }
      }, true);
      document.addEventListener('pointerlockchange', function() {
        _ptrLockActive = !!document.pointerLockElement;
      });
      // Press Escape to release without leaving game
      document.addEventListener('keydown', function(e) {
        if (e.key === 'Escape' && _ptrLockActive) {
          document.exitPointerLock && document.exitPointerLock();
        }
      }, true);
    })();

    buildLoadingScreen();
    buildHUD();
    var _menuBuildFn = function() {
      buildMenu();
      buildSupportPopup();
      addResizeHandle();
    };
    if (window.requestIdleCallback) {
      requestIdleCallback(_menuBuildFn, { timeout: 1500 });
    } else {
      setTimeout(_menuBuildFn, 0);
    }
    window.addEventListener('keydown', function(e) {
      if (e.key === 'F2' && !e.repeat) {
        e.preventDefault(); e.stopImmediatePropagation(); toggleMenu();
      }
    }, true);
    window.addEventListener('keyup', function(e) {
      if (e.key === 'F2') _kmKeyLock = false;
    }, true);

    setTimeout(function() { toggleMenu(true); }, 1200);
    pluginPerfMetricsMount();
    pluginStreamerMount();
    pluginOptimizerMount();
    console.log('KeyMod v5.0.0 by @keydopz — F2 / backtick / 1 to toggle');
  }

  document.body ? boot() : document.addEventListener('DOMContentLoaded', boot);

})();