[战斗模拟器]配装导入

配装导入

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         [战斗模拟器]配装导入
// @version      1.1
// @description  配装导入
// @author       GPT-DiamondMoo
// @match        https://www.milkywayidle.com/*
// @match        https://www.milkywayidlecn.com/*
// @match        https://test.milkywayidle.com/*
// @match        https://test.milkywayidlecn.com/*
// @match        https://shykai.github.io/MWICombatSimulatorTest/*
// @icon         https://www.milkywayidle.com/favicon.svg
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addValueChangeListener
// @license      MIT
// @namespace    http://tampermonkey.net/
// ==/UserScript==

const STORAGE_KEY = "mwi_combat_loadouts_v1";
const IMPORT_REQUEST_KEY = "mwi_import_request";
const UI_POS_GAME = "mwi_ui_pos_game_v1";
const UI_POS_SIM  = "mwi_ui_pos_sim_v1";
const UI_POS_LAST_GAME = "mwi_ui_last_pos_game_v1";
const UI_POS_LAST_SIM = "mwi_ui_last_pos_sim_v1";
const KEY_LAST_ACTIVE_CHAR = "mwi_last_active_char";
const KEY_FIRST_OPEN = "mwi_first_open";
const KEY_SIM_PANEL_OPEN = "mwi_sim_panel_open";
const KEY_SETTINGS = "mwi_settings_v1";
const HOST = location.hostname;
const IS_SIM = HOST.includes("shykai.github.io");
const IS_MILKY = HOST.includes("milkywayidle");
const IS_TEST = HOST.includes("test.milkywayidle");
const $ = s => document.querySelector(s);
const $$ = s => Array.from(document.querySelectorAll(s));
let currentScale = 1.0;
let panelPositionLock = { horizontal: "right", vertical: "bottom" };

function safeJSONParse(s, f=null){ try { return JSON.parse(s); } catch { return f } }
function nowTs(){ return Date.now(); }

const DEFAULT_SETTINGS = {
  READ_ONLY_IRONCOW: false,
  BLOCK_TEST_SERVERS: true,
  ENABLE_AUTO_READ_ON_ENTER: true,
  SIM_SCALE: 1.0,
  MWI_SCALE: 1.0,
  SIM_THEME: "follow",
  GAME_THEME: "dark"
};

GM_addStyle(`
.mwi-ui { box-sizing: border-box; font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto; user-select: none !important; }
#mwi-floating-btn, #mwi-panel, #mwi-panel * { -webkit-user-select:none !important; -moz-user-select:none !important; user-select:none !important; }
#mwi-floating-btn{position:fixed;left:12px;top:25%;z-index:2147483647;width:44px;height:33px;border-radius:8px;display:flex;align-items:center;justify-content:center;cursor:grab;transition:box-shadow .18s,transform .12s;font-weight:700}
#mwi-floating-btn.round{border-radius:50%}
#mwi-panel{position:fixed;left:60px;top:24%;width:360px;max-height:560px;overflow:auto;z-index:2147483646;border-radius:12px;padding:12px;display:none;flex-direction:column;gap:8px;box-shadow:0 18px 38px rgba(0,0,0,0.25);border:1px solid var(--mwi-panel-border);font-size:14px}
.mwi-row{display:flex;flex-wrap:wrap;gap:8px}
.mwi-title { font-size: 16px !important; }
.mwi-section-label { font-size: 12px !important; }
.mwi-btn-text { font-size: 14px !important; }
.mwi-small-muted{font-size:12px !important;color:var(--mwi-muted,#888)}
.mwi-slot-btn{width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:all .12s;border:1px solid var(--mwi-btn-border);font-size:14px !important;}
.mwi-item-btn{height:32px;min-width:110px;border-radius:8px;padding:0 8px;display:flex;align-items:center;justify-content:center;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;transition:all .12s;border:1px solid var(--mwi-btn-border);font-size:14px !important;}
.mwi-delete-btn{margin-left:6px;color:var(--mwi-danger,#ff6b6b);cursor:pointer;font-weight:700;font-size:14px !important;}
.mwi-active{box-shadow:0 6px 12px rgba(0,0,0,0.12);}
.mwi-item-btn:hover, .mwi-slot-btn:hover { transform:translateY(-2px); box-shadow:0 6px 12px rgba(0,0,0,0.08); }
.mwi-item-btn.selected{border-color:var(--mwi-accent,#69a8ff); background:var(--mwi-selected-bg,#e0efff); color:var(--mwi-selected-color,#111);}
.mwi-slot-btn.selected{border-color:var(--mwi-accent,#69a8ff); background:var(--mwi-selected-bg,#e0efff); color:var(--mwi-selected-color,#111);}
#mwi-toast{position:fixed;left:50%;transform:translateX(-50%);bottom:12%;padding:8px 12px;border-radius:8px;background:rgba(0,0,0,0.75);color:#fff;opacity:0;transition:opacity .25s;font-size:13px;pointer-events:none;z-index:2147483648}
:root.mwi-theme-light {
  --mwi-bg: #ffffff;
  --mwi-fg:#111;
  --mwi-muted:#666;
  --mwi-selected-bg:#fff4d2;
  --mwi-selected-color:#111;
  --mwi-accent:#ffca7a;
  --mwi-danger:#c0392b;
  --mwi-panel-border:rgba(0,0,0,0.06);
  --mwi-floating-bg: linear-gradient(180deg,#f5f5f7,#e8eaed);
  --mwi-floating-border: rgba(0,0,0,0.08);
  --mwi-btn-border: rgba(0,0,0,0.1);
}
:root.mwi-theme-light #mwi-panel, :root.mwi-theme-light #mwi-settings-modal { background:var(--mwi-bg); color:var(--mwi-fg); border-color: var(--mwi-panel-border); }
:root.mwi-theme-light #mwi-floating-btn { background: var(--mwi-floating-bg); color:var(--mwi-fg); border:1px solid var(--mwi-floating-border); }
:root.mwi-theme-dark {
  --mwi-bg:#212121;
  --mwi-fg:#e8f0e8;
  --mwi-muted:#9aa79a;
  --mwi-selected-bg:#274a3a;
  --mwi-selected-color:#e6f6e8;
  --mwi-accent:#3f7f5f;
  --mwi-danger:#ff6b6b;
  --mwi-panel-border:rgba(255,255,255,0.1);
  --mwi-floating-bg: linear-gradient(180deg,#333333,#212121);
  --mwi-floating-border: rgba(255,255,255,0.1);
  --mwi-btn-border: rgba(255,255,255,0.15);
}
:root.mwi-theme-dark #mwi-panel, :root.mwi-theme-dark #mwi-settings-modal { background:var(--mwi-bg); color:var(--mwi-fg); border-color: var(--mwi-panel-border); }
:root.mwi-theme-dark #mwi-floating-btn { background: var(--mwi-floating-bg); color:var(--mwi-fg); border:1px solid var(--mwi-floating-border); }
:root.mwi-sim-scale #mwi-panel{transform-origin:top left}
:root.mwi-sim-scale #mwi-floating-btn{transform-origin:top left}
#mwi-settings-modal{position:fixed;left:50%;top:50%;transform:translate(-50%,-50%);z-index:2147483650;width:520px;max-width:95%;background:#fff;border-radius:12px;padding:16px;box-shadow:0 22px 54px rgba(0,0,0,0.6);display:none;color:#111;transition:transform .2s ease;}
#mwi-settings-modal.dark{background:var(--mwi-bg);color:var(--mwi-fg);border:1px solid var(--mwi-panel-border);}
#mwi-settings-modal h3{margin:0 0 8px 0;font-size:16px !important;}
#mwi-settings-list{display:flex;flex-direction:column;gap:8px}
.mwi-settings-row{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:8px;border-radius:8px}
.mwi-settings-row label{flex:1;font-size:14px !important;}
.mwi-settings-controls{min-width:220px;display:flex;gap:8px;align-items:center}
#s_mwi_settings_close{position:absolute;right:8px;top:8px;cursor:pointer;font-size:14px !important;z-index:2147483651;}
#s_mwi_settings_btn{position:absolute;right:8px;bottom:8px;width:30px;height:30px;border-radius:50%;display:flex;align-items:center;justify-content:center;cursor:pointer;border:1px solid var(--mwi-btn-border);background:rgba(255,255,255,0.06);color:inherit}
#mwi-refresh-btn {
  background: var(--mwi-bg) !important;
  color: var(--mwi-fg) !important;
  border: 1px solid var(--mwi-btn-border) !important;
  min-width: 70px !important;
  height: 26px !important;
  border-radius: 8px !important;
  transition: all .12s !important;
  font-size: 14px !important;
}
#mwi-refresh-btn:hover {
  transform: translateY(-2px) !important;
  box-shadow: 0 6px 12px rgba(0,0,0,0.08) !important;
}
#mwi-refresh-btn:disabled {
  opacity: 0.7 !important;
  cursor: not-allowed !important;
  transform: none !important;
  box-shadow: none !important;
}
`);

async function getSettings(){
  const raw = await GM_getValue(KEY_SETTINGS, null);
  if(!raw){ await GM_setValue(KEY_SETTINGS, JSON.stringify(DEFAULT_SETTINGS)); return Object.assign({}, DEFAULT_SETTINGS); }
  const parsed = safeJSONParse(raw, {});
  return Object.assign({}, DEFAULT_SETTINGS, parsed);
}

async function saveSettings(obj){
  await GM_setValue(KEY_SETTINGS, JSON.stringify(obj));
  await GM_setValue(KEY_SETTINGS + "_ts", Date.now());
}

async function saveAllData(o){ await GM_setValue(STORAGE_KEY, JSON.stringify(o)); }
async function loadAllData(){ return safeJSONParse(await GM_getValue(STORAGE_KEY, "{}"), {}); }

function readGameStateFromPage(){
  try{
    const el = document.querySelector('[class^="GamePage"]');
    if(!el) return null;
    const fiberKey = Object.keys(el).find(k => k.startsWith('__reactFiber$') || k.startsWith('__reactInternalInstance$'));
    const fiber = el?.[fiberKey];
    if(!fiber) return null;
    const stateNode = fiber.return?.stateNode || fiber?.return?.stateNode;
    if(!stateNode) return null;
    return stateNode.state || stateNode.props?.state || stateNode;
  }catch{return null}
}

function parseEquipmentFromWearableMap(map){
  const out = [];
  if(!map) return out;
  for(const loc of Object.keys(map)){
    const raw = map[loc];
    if(!raw) continue;
    const parts = String(raw).split("::");
    let itemHrid = parts.find(p => typeof p === "string" && p.startsWith("/items/")) || "";
    let enh = 0;
    const last = parts[parts.length - 1];
    if(/^\d+$/.test(last)) enh = Number(last);
    out.push({ itemLocationHrid: loc, itemHrid: itemHrid, enhancementLevel: enh });
  }
  return out;
}

function parseFoodAndDrinks(loadout){
  const foods = (loadout.foodItemHrids || []).filter(Boolean);
  const drinks = (loadout.drinkItemHrids || []).filter(Boolean);
  const f=[]; const d=[];
  for(let i=0;i<3;i++){ f.push({itemHrid:foods[i]||""}); d.push({itemHrid:drinks[i]||""}); }
  return { food: { "/action_types/combat": f }, drinks: { "/action_types/combat": d } };
}

function parseAbilitiesFromMap(abilityMap, abilityLevelMap){
  const plain={};
  try{ if(abilityLevelMap?.get) for(const [k,v] of abilityLevelMap.entries()) plain[k]=v; else Object.assign(plain, abilityLevelMap||{}); }catch{}
  const keys = Object.keys(abilityMap||{}).sort((a,b)=>Number(a)-Number(b));
  const out=[];
  for(const k of keys){
    const hrid=abilityMap[k]||""; let level="1";
    if(hrid && plain[hrid] && Number.isFinite(Number(plain[hrid].level))) level=String(plain[hrid].level);
    out.push({abilityHrid:hrid, level});
  }
  while(out.length<5) out.push({abilityHrid:"", level:"1"});
  return out.slice(0,5);
}

function parseCombatLevels(skillMap){
  const get=hrid=>{ try{ if(!skillMap) return 1; if(skillMap.get) return Number(skillMap.get(hrid)?.level||1); if(skillMap[hrid]) return Number(skillMap[hrid].level||1);}catch{} return 1; };
  return {
    attackLevel:get("/skills/attack"),
    magicLevel:get("/skills/magic"),
    meleeLevel:get("/skills/melee"),
    rangedLevel:get("/skills/ranged"),
    defenseLevel:get("/skills/defense"),
    staminaLevel:get("/skills/stamina"),
    intelligenceLevel:get("/skills/intelligence")
  };
}

function parseHouseRooms(dict){ const out={}; if(!dict) return out; for(const k of Object.keys(dict)) out[k]=dict[k].level||0; return out; }

function convertStateToLoadoutJson(state, characterID, loadout){
  const equipment = parseEquipmentFromWearableMap(loadout.wearableMap || {});
  const { food, drinks } = parseFoodAndDrinks(loadout);
  const abilities = parseAbilitiesFromMap(loadout.abilityMap || {}, state.characterAbilityMap || {});
  const triggerMap = Object.assign({}, state.abilityCombatTriggersDict || {}, state.consumableCombatTriggersDict || {});
  const zone = state.zone || loadout.zone || "/actions/combat/fly";
  const simulationTime = String(loadout.simulationTime || state.simulationTime || 24);
  const houseRooms = parseHouseRooms(state.characterHouseRoomDict || {});
  const combatLevels = parseCombatLevels(state.characterSkillMap || {});
  const charMeta = state.character || {};
  return {
    player: { ...combatLevels, equipment },
    food,
    drinks,
    abilities,
    triggerMap,
    zone,
    simulationTime,
    houseRooms,
    characterName: charMeta.name || `Player ${characterID.slice(-1)}`
  };
}

async function readAndSaveCurrentPageConfig(){
  const settings = await getSettings();
  if(IS_TEST && settings.BLOCK_TEST_SERVERS) return null;
  const state = readGameStateFromPage();
  if(!state || !state.character) return null;
  const c = state.character;
  if(settings.READ_ONLY_IRONCOW && c.gameMode !== "ironcow") return null;
  const all = await loadAllData();
  const charId = String(c.id);
  const store = all[charId] || { characterMeta: {}, loadouts: {}, triggerMap: {}, lastSavedAt: null };
  store.characterMeta = { id: c.id, name: c.name, gameMode: c.gameMode, createdAt: c.createdAt };
  const loadouts = state.characterLoadoutDict || {};
  for(const lid of Object.keys(loadouts)){
    const l = loadouts[lid]; if(!l) continue;
    if(l.actionTypeHrid !== "/action_types/combat") continue;
    const parsed = convertStateToLoadoutJson(state, charId, l);
    store.loadouts[l.id] = { id: l.id, name: l.name, data: parsed };
    store.triggerMap = store.triggerMap || {};
    Object.assign(store.triggerMap, parsed.triggerMap || {});
  }
  store.lastSavedAt = new Date().toISOString();
  all[charId] = store;
  await saveAllData(all);
  await GM_setValue(KEY_LAST_ACTIVE_CHAR, charId);
  return store;
}

GM_addValueChangeListener(IMPORT_REQUEST_KEY, (_,__, newVal) => {
  if(!newVal) return;
  try{
    const req = safeJSONParse(newVal, null);
    if(!req || !req.payload) return;
    const payload = req.payload;
    const slot = req.slot || 1;
    const input = document.querySelector(`#inputSetGroupCombatplayer${slot}`);
    if(input){
      input.value = payload;
      input.dispatchEvent(new Event("input",{bubbles:true}));
      input.dispatchEvent(new Event("change",{bubbles:true}));
      setTimeout(()=>$("#buttonImportSet")?.click(),150);
    } else {
      const all = $("#inputSetGroupCombatAll");
      if(all){
        all.value = payload;
        all.dispatchEvent(new Event("input",{bubbles:true}));
        all.dispatchEvent(new Event("change",{bubbles:true}));
        setTimeout(()=>$("#buttonImportSet")?.click(),150);
      }
    }
    if(payload && slot >=1 && slot <=5){
      const charData = safeJSONParse(payload, {});
      const charName = charData.characterName || `Player ${slot}`;
      const tabLink = $(`#player${slot}-tab`);
      if(tabLink) tabLink.innerText = charName;
    }
  }catch(e){console.error("Import listener error:", e)}
});

async function importLoadoutToSimulator(data, slotNum){
  try{
    const payload = JSON.stringify(data || {});
    await GM_setValue(IMPORT_REQUEST_KEY, JSON.stringify({ slot: slotNum, payload, ts: nowTs() }));
    if(IS_SIM){
      const input = document.querySelector(`#inputSetGroupCombatplayer${slotNum}`);
      if(input){
        input.value = payload;
        input.dispatchEvent(new Event("input",{bubbles:true}));
        input.dispatchEvent(new Event("change",{bubbles:true}));
        setTimeout(()=>$("#buttonImportSet")?.click(),150);
      } else {
        const allInput = $("#inputSetGroupCombatAll");
        if(allInput){
          allInput.value = payload;
          allInput.dispatchEvent(new Event("input",{bubbles:true}));
          allInput.dispatchEvent(new Event("change",{bubbles:true}));
          setTimeout(()=>$("#buttonImportSet")?.click(),150);
        }
      }
      if(data?.characterName && slotNum >=1 && slotNum <=5){
        const tabLink = $(`#player${slotNum}-tab`);
        if(tabLink) tabLink.innerText = data.characterName;
      }
    } else {
      try{
        await navigator.clipboard.writeText(payload);
        showToast("已复制配装数据");
      }catch{
        showToast("复制失败,请手动复制");
      }
    }
  }catch(e){
    console.error("Import error:", e);
    showToast("导入失败");
  }
}

function waitForGameReady(timeoutMs = 8000, interval = 300){
  return new Promise((resolve) => {
    const start = Date.now();
    const tick = async () => {
      const st = readGameStateFromPage();
      if(st && st.character && (st.characterLoadoutDict || st.characterLoadoutDict === undefined)) { resolve(st); return; }
      if(Date.now() - start > timeoutMs) { resolve(null); return; }
      setTimeout(tick, interval);
    };
    tick();
  });
}

function constrainBtnToScreen(btn){
  const btnRect = btn.getBoundingClientRect();
  const btnWidth = btnRect.width || 44;
  const btnHeight = btnRect.height || 33;
  const minOffset = 8;
  let newLeft = parseFloat(btn.style.left) || 0;
  let newTop = parseFloat(btn.style.top) || 0;
  newLeft = Math.max(minOffset, Math.min(newLeft, window.innerWidth - btnWidth - minOffset));
  newTop = Math.max(minOffset, Math.min(newTop, window.innerHeight - btnHeight - minOffset));
  btn.style.left = `${newLeft}px`;
  btn.style.top = `${newTop}px`;
}

function positionPanelNearButton(btn, panel){
  const btnRect = btn.getBoundingClientRect();
  panel.style.display = panel.style.display === "flex" ? "flex" : "none";
  const panelW = 360 * currentScale;
  const panelH = 360 * currentScale;
  const minOffset = 8;
  let left, top;
  const rightSpace = window.innerWidth - btnRect.right;
  const leftSpace = btnRect.left;

  if(panelPositionLock.horizontal === "right"){
    if(rightSpace >= panelW + minOffset){
      left = btnRect.right + minOffset;
    } else {
      panelPositionLock.horizontal = "left";
      left = Math.max(minOffset, btnRect.left - panelW - minOffset);
    }
  } else {
    if(leftSpace >= panelW + minOffset){
      left = Math.max(minOffset, btnRect.left - panelW - minOffset);
    } else {
      panelPositionLock.horizontal = "right";
      left = btnRect.right + minOffset;
    }
  }

  const btnCenterY = btnRect.top + btnRect.height / 2;
  const bottomSpace = window.innerHeight - btnRect.bottom;
  const topSpace = btnRect.top;

  if(panelPositionLock.vertical === "bottom"){
    if(bottomSpace >= panelH + minOffset){
      top = btnRect.top;
    } else {
      panelPositionLock.vertical = "top";
      top = btnRect.bottom - panelH;
    }
  } else {
    if(topSpace >= panelH + minOffset){
      top = btnRect.bottom - panelH;
    } else {
      panelPositionLock.vertical = "bottom";
      top = btnRect.top;
    }
  }

  left = Math.max(minOffset, Math.min(left, window.innerWidth - panelW - minOffset));
  top = Math.max(minOffset, Math.min(top, window.innerHeight - panelH - minOffset));
  panel.style.left = `${Math.round(left)}px`;
  panel.style.top = `${Math.round(top)}px`;
}

async function createFloatingUI(){
  if($("#mwi-floating-btn")) return;
  const settings = await getSettings();
  window.__mwi_settings_cache = Object.assign({}, DEFAULT_SETTINGS, settings);
  const btn = document.createElement("div");
  btn.id = "mwi-floating-btn";
  btn.className = "mwi-ui";
  btn.title = "配装";
  btn.innerText = "配装";
  document.body.appendChild(btn);
  const panel = document.createElement("div");
  panel.id = "mwi-panel";
  panel.className = "mwi-ui";
  panel.innerHTML = `
    <div style="display:flex;justify-content:space-between;align-items:center">
      <div style="display:flex;align-items:center;gap:8px">
        <h4 class="mwi-title" style="margin:0;">配装导入</h4>
        <div id="mwi-role-last-saved" class="mwi-section-label mwi-small-muted">(未选择)</div>
      </div>
      <div style="display:flex;align-items:center;gap:8px">
        <button id="mwi-refresh-btn" class="mwi-item-btn mwi-btn-text" style="min-width:70px;height:26px;padding:0 8px">刷新</button>
      </div>
    </div>
    <div id="mwi-select-label"><div class="mwi-section-label mwi-small-muted">序号</div></div>
    <div class="mwi-row" id="mwi-slot-row"></div>
    <div><div class="mwi-section-label mwi-small-muted">角色</div></div>
    <div class="mwi-row" id="mwi-char-row"></div>
    <div><div class="mwi-section-label mwi-small-muted">配装</div></div>
    <div class="mwi-row" id="mwi-load-row"></div>
  `;
  document.body.appendChild(panel);
  const settingsModal = buildSettingsModal();
  document.body.appendChild(settingsModal.modal);
  const root = document.documentElement;
  const currentSite = IS_SIM ? "sim" : "game";
  const UI_POS_LAST_CURRENT = IS_SIM ? UI_POS_LAST_SIM : UI_POS_LAST_GAME;
  currentScale = IS_SIM ? window.__mwi_settings_cache.SIM_SCALE : window.__mwi_settings_cache.MWI_SCALE;

  function applyGameTheme(theme){
    root.classList.remove("mwi-theme-dark","mwi-theme-light");
    if(theme === "dark") root.classList.add("mwi-theme-dark");
    else if(theme === "light") root.classList.add("mwi-theme-light");
  }

  function applySimTheme(theme){
    root.classList.remove("mwi-theme-dark","mwi-theme-light");
    if(theme === "dark") root.classList.add("mwi-theme-dark");
    else if(theme === "light") root.classList.add("mwi-theme-light");
    else {
      const chk = $("#darkModeToggle");
      if(chk) root.classList.add(chk.checked ? "mwi-theme-dark" : "mwi-theme-light");
    }
  }

  function applyScalesAndTheme(){
    const s = window.__mwi_settings_cache;
    currentScale = IS_SIM ? s.SIM_SCALE : s.MWI_SCALE;
    if(IS_MILKY){
      panel.style.transform = `scale(${currentScale})`;
      panel.style.transformOrigin = "top left";
      btn.style.transform = `scale(${currentScale})`;
      btn.style.transformOrigin = "top left";
      const modal = $("#mwi-settings-modal");
      if(modal) modal.style.transform = `translate(-50%, -50%) scale(${currentScale})`;
      applyGameTheme(s.GAME_THEME);
    } else {
      root.classList.add("mwi-sim-scale");
      panel.style.transform = `scale(${currentScale})`;
      panel.style.transformOrigin = "top left";
      btn.style.transform = `scale(${currentScale})`;
      btn.style.transformOrigin = "top left";
      const modal = $("#mwi-settings-modal");
      if(modal) modal.style.transform = `translate(-50%, -50%) scale(${currentScale})`;
      applySimTheme(s.SIM_THEME);
    }
    panel.style.fontSize = "14px";
    const modal = $("#mwi-settings-modal");
    if(modal){
      modal.classList.toggle("dark", IS_SIM ? s.SIM_THEME === "dark" : s.GAME_THEME === "dark");
    }
    positionPanelNearButton(btn, panel);
  }

  applyScalesAndTheme();

  if(IS_SIM){
    const chk = $("#darkModeToggle");
    if(chk) root.classList.remove("mwi-theme-dark","mwi-theme-light"), root.classList.add(chk.checked ? "mwi-theme-dark" : "mwi-theme-light");
    const obs = new MutationObserver(()=>{
      if(window.__mwi_settings_cache.SIM_THEME === "follow"){
        const c = $("#darkModeToggle");
        if(c) {
          root.classList.remove("mwi-theme-dark","mwi-theme-light");
          root.classList.add(c.checked ? "mwi-theme-dark":"mwi-theme-light");
        }
      }
    });
    obs.observe(document.body, { childList: true, subtree: true });
  }

  const uiKey = IS_SIM ? UI_POS_SIM : UI_POS_GAME;

  async function loadAndApplySavedBtnPos(){
    const pos = safeJSONParse(await GM_getValue(uiKey, null), null);
    const last = safeJSONParse(await GM_getValue(UI_POS_LAST_CURRENT, null), null);
    if(last && last.ts && (!pos || last.ts >= (pos.ts||0))){
      btn.style.left = last.left;
      btn.style.top = last.top;
      await GM_setValue(uiKey, JSON.stringify(last));
    } else if(pos && pos.left && pos.top){
      btn.style.left = pos.left;
      btn.style.top = pos.top;
    } else {
      btn.style.left = "12px";
      btn.style.top = "25%";
    }
    constrainBtnToScreen(btn);
    positionPanelNearButton(btn, panel);
  }

  await loadAndApplySavedBtnPos();

  let dragging=false, startX=0, startY=0, startLeft=0, startTop=0, moved=false;
  const DRAG_THRESHOLD=6;

  btn.addEventListener("pointerdown", e=>{
    if(e.button!==0) return;
    dragging=true; moved=false;
    startX=e.clientX; startY=e.clientY;
    startLeft=btn.offsetLeft; startTop=btn.offsetTop;
    btn.setPointerCapture && btn.setPointerCapture(e.pointerId);
    btn.style.transition="none";
  });

  document.addEventListener("pointermove", e=>{
    if(!dragging) return;
    const dx = e.clientX - startX, dy = e.clientY - startY;
    if(!moved && Math.hypot(dx,dy) > DRAG_THRESHOLD) moved=true;
    let newLeft = startLeft + dx;
    let newTop = startTop + dy;
    btn.style.left = `${newLeft}px`;
    btn.style.top = `${newTop}px`;
    constrainBtnToScreen(btn);
    positionPanelNearButton(btn, panel);
  });

  document.addEventListener("pointerup", async e=>{
    if(!dragging) return;
    dragging=false;
    btn.style.transition="all .18s ease";
    btn.releasePointerCapture && btn.releasePointerCapture(e.pointerId);
    constrainBtnToScreen(btn);
    const payload = { left: btn.style.left, top: btn.style.top, ts: nowTs() };
    await GM_setValue(uiKey, JSON.stringify(payload));
    await GM_setValue(UI_POS_LAST_CURRENT, JSON.stringify(payload));
  });

  GM_addValueChangeListener(UI_POS_LAST_CURRENT, async (_,__, newVal) => {
    try{
      const p = safeJSONParse(newVal, null);
      if(!p) return;
      btn.style.left = p.left;
      btn.style.top = p.top;
      constrainBtnToScreen(btn);
      await GM_setValue(uiKey, JSON.stringify(p));
      positionPanelNearButton(btn, panel);
    }catch{}
  });

  const slotRow = $("#mwi-slot-row"), selectLabel = $("#mwi-select-label"), charRow = $("#mwi-char-row"), loadRow = $("#mwi-load-row");
  const roleLastSavedEl = $("#mwi-role-last-saved"), refreshBtn = $("#mwi-refresh-btn");
  let selectedSlot = 1;
  let selectedCharacterId = null;

  function renderSlots(){
    if(!slotRow) return;
    slotRow.innerHTML="";
    for(let i=1;i<=5;i++){
      const b=document.createElement("div");
      b.className="mwi-slot-btn mwi-ui mwi-btn-text";
      b.innerText=String(i);
      if(i===selectedSlot) b.classList.add("selected");
      b.onclick=()=>{
        selectedSlot=i;
        $$(".mwi-slot-btn").forEach(x=>x.classList.remove("selected"));
        b.classList.add("selected");
        renderLoadouts();
      };
      slotRow.appendChild(b);
    }
  }

  async function updateRoleLastSavedDisplay(){
    const all = await loadAllData();
    if(!selectedCharacterId || !all[selectedCharacterId]){
      roleLastSavedEl.innerText="(未选择)";
      return;
    }
    const t = all[selectedCharacterId].lastSavedAt;
    roleLastSavedEl.innerText = t ? `最后同步:${new Date(t).toLocaleString()}` : "(未读取)";
  }

  async function renderCharacters(){
    const all = await loadAllData();
    charRow.innerHTML = "";
    const ids = Object.keys(all);
    if(ids.length === 0){
      charRow.innerHTML = '<div class="mwi-section-label mwi-small-muted">暂无已读取角色</div>';
      roleLastSavedEl.innerText="(未选择)";
      return;
    }
    if(!selectedCharacterId || !all[selectedCharacterId]){
      if(IS_MILKY){
        const st = readGameStateFromPage();
        if(st && st.character) selectedCharacterId = String(st.character.id);
        else selectedCharacterId = ids[0];
      } else {
        const last = await GM_getValue(KEY_LAST_ACTIVE_CHAR, null);
        if(last && all[last]) selectedCharacterId = last;
        else selectedCharacterId = ids[0];
      }
    }
    for(const id of ids){
      const meta = all[id].characterMeta || {};
      const name = meta.name || ("#"+id);
      const wrap = document.createElement("div");
      wrap.style.display="flex"; wrap.style.alignItems="center"; wrap.style.gap="6px";
      const b = document.createElement("div");
      b.className="mwi-item-btn mwi-ui mwi-btn-text";
      if(id===selectedCharacterId) b.classList.add("selected");
      b.innerHTML = `<div style="font-weight:600">${name}</div>`;
      b.onclick = ()=>{
        selectedCharacterId=id;
        $$(".mwi-item-btn").forEach(x=>{
          if(x.parentElement?.querySelector(".mwi-delete-btn")) x.classList.remove("selected");
        });
        b.classList.add("selected");
        renderLoadouts();
        updateRoleLastSavedDisplay();
      };
      const del = document.createElement("div");
      del.className="mwi-delete-btn mwi-btn-text";
      del.innerText="✕";
      del.onclick = async e => {
        e.stopPropagation();
        if(confirm(`确定从本地删除角色 ${name} 吗?`)){
          const all2 = await loadAllData();
          delete all2[id];
          await saveAllData(all2);
          if(id === selectedCharacterId) selectedCharacterId = null;
          renderCharacters();
          renderLoadouts();
          updateRoleLastSavedDisplay();
        }
      };
      wrap.appendChild(b); wrap.appendChild(del); charRow.appendChild(wrap);
    }
    updateRoleLastSavedDisplay();
  }

  async function renderLoadouts(){
    loadRow.innerHTML="";
    if(!selectedCharacterId){
      loadRow.innerHTML = '<div class="mwi-section-label mwi-small-muted">请选择角色</div>';
      return;
    }
    const all = await loadAllData();
    const char = all[selectedCharacterId];
    if(!char || !char.loadouts || Object.keys(char.loadouts).length===0){
      loadRow.innerHTML = '<div class="mwi-section-label mwi-small-muted">该角色暂无配装</div>';
      return;
    }
    for(const lo of Object.values(char.loadouts)){
      const b = document.createElement("div");
      b.className="mwi-item-btn mwi-ui mwi-btn-text";
      b.innerText = lo.name || ("#"+lo.id);
      b.onclick = async ()=>{
        if(!selectedCharacterId || !selectedSlot) return;
        const charData = all[selectedCharacterId];
        const loadoutData = lo.data;
        if(IS_MILKY){
          try{
            const payload = JSON.stringify(loadoutData || {});
            await navigator.clipboard.writeText(payload);
            showToast(`已复制【${charData.characterMeta.name || "未知角色"}】-【${lo.name || "未知配装"}】`);
          }catch{
            showToast("复制失败,请手动操作");
          }
        } else {
          await importLoadoutToSimulator(loadoutData, selectedSlot);
          showToast(`已导入配装到序号${selectedSlot}`);
        }
        b.classList.add("selected");
        setTimeout(()=>b.classList.remove("selected"),700);
      };
      loadRow.appendChild(b);
    }
  }

  refreshBtn.innerText = "刷新";
  refreshBtn.addEventListener("click", async ()=>{
    refreshBtn.innerText = "读取中...";
    refreshBtn.disabled = true;
    try{
      const currentSelectedCharId = selectedCharacterId;
      await readAndSaveCurrentPageConfig();
      await renderCharacters();
      await renderLoadouts();
      if(currentSelectedCharId){
        const all = await loadAllData();
        if(all[currentSelectedCharId]){
          selectedCharacterId = currentSelectedCharId;
          $$(".mwi-item-btn").forEach(x=>{
            if(x.parentElement?.querySelector(".mwi-delete-btn")) x.classList.remove("selected");
          });
          const charBtns = $$(".mwi-item-btn");
          charBtns.forEach(btn=>{
            const charName = btn.querySelector("div").innerText;
            const targetChar = all[currentSelectedCharId]?.characterMeta?.name;
            if(charName === targetChar) btn.classList.add("selected");
          });
        }
      }
      $$(".mwi-slot-btn").forEach(x=>x.classList.remove("selected"));
      $(`.mwi-slot-btn:nth-child(${selectedSlot})`).classList.add("selected");
      showToast("刷新成功");
    }catch(e){
      //console.error("Refresh error:", e);
      //showToast("刷新失败");
    }
    refreshBtn.disabled=false;
    refreshBtn.innerText="刷新";
  });

  function refreshPanelUI(){
    renderSlots();
    renderCharacters();
    renderLoadouts();
  }

  if(IS_MILKY){
    if(selectLabel) selectLabel.style.display="none";
    if(slotRow) slotRow.style.display="none";
  }

  if(IS_SIM){
    if(refreshBtn) refreshBtn.style.display="none";
    root.classList.add("mwi-sim-scale");
  }

  const toast = document.createElement("div");
  toast.id="mwi-toast";
  document.body.appendChild(toast);
  let toastTimer=null;

  function showToast(t){
    toast.innerText = t;
    toast.style.opacity="1";
    clearTimeout(toastTimer);
    toastTimer = setTimeout(()=>toast.style.opacity="0",1500);
  }

  const firstOpen = await GM_getValue(KEY_FIRST_OPEN, null);
  const simPanelState = await GM_getValue(KEY_SIM_PANEL_OPEN, "closed");

  if(!firstOpen){
    panel.style.display="flex";
    await GM_setValue(KEY_FIRST_OPEN, true);
  }
  else {
    if(IS_SIM) panel.style.display = simPanelState === "open" ? "flex" : "none";
    else panel.style.display = "none";
  }

  if(panel.style.display === "flex"){
    setTimeout(()=>{
      renderCharacters();
      renderLoadouts();
      updateRoleLastSavedDisplay();
      positionPanelNearButton(btn, panel);
    }, 20);
  }

  setTimeout(()=>{
    refreshPanelUI();
    positionPanelNearButton(btn, panel);
  },200);

  btn.addEventListener("click", async e=>{
    if(moved){ moved=false; return; }
    if(panel.style.display === "flex"){
      panel.style.display="none";
      if(IS_SIM) await GM_setValue(KEY_SIM_PANEL_OPEN,"closed");
    }
    else {
      panel.style.display="flex";
      if(IS_MILKY){
        const st = readGameStateFromPage();
        if(st && st.character) selectedCharacterId = String(st.character.id);
      }
      else {
        const last = await GM_getValue(KEY_LAST_ACTIVE_CHAR, null);
        if(last){
          const all = await loadAllData();
          if(all[last]) selectedCharacterId = last;
        }
      }
      refreshPanelUI();
      positionPanelNearButton(btn, panel);
      if(IS_SIM) await GM_setValue(KEY_SIM_PANEL_OPEN,"open");
    }
  });

  const settingsBtn = document.createElement("div");
  settingsBtn.id = "s_mwi_settings_btn";
  settingsBtn.className = "mwi-ui";
  settingsBtn.title = "设置";
  settingsBtn.innerText = "⚙️";
  panel.appendChild(settingsBtn);
  let settingsModalOpen = false;

  settingsBtn.addEventListener("click", async ()=>{
    const modal = $("#mwi-settings-modal");
    if(settingsModalOpen){
      modal.style.display = "none";
      settingsModalOpen = false;
    } else {
      await syncSettingsToModal();
      modal.classList.toggle("dark", IS_SIM ? window.__mwi_settings_cache.SIM_THEME === "dark" : window.__mwi_settings_cache.GAME_THEME === "dark");
      modal.style.display = "block";
      settingsModalOpen = true;
    }
  });

  const closeBtn = document.createElement("div");
  closeBtn.id = "s_mwi_settings_close";
  closeBtn.innerText = "✕";
  closeBtn.style.position = "absolute";
  closeBtn.style.right = "8px";
  closeBtn.style.top = "8px";
  closeBtn.style.cursor = "pointer";
  closeBtn.style.fontSize = "14px";
  closeBtn.style.zIndex = "2147483651";
  closeBtn.addEventListener("click", ()=>{
    const modal = $("#mwi-settings-modal");
    modal.style.display = "none";
    settingsModalOpen = false;
  });
  settingsModal.modal.appendChild(closeBtn);

  GM_addValueChangeListener(KEY_SETTINGS, async (name, oldVal, newVal) => {
    try{
      const s = safeJSONParse(newVal, null);
      if(!s) return;
      window.__mwi_settings_cache = Object.assign({}, DEFAULT_SETTINGS, s);
      applyScalesAndTheme();
      await syncSettingsToModal();
    }catch(e){console.error("Settings sync error:", e)}
  });

  GM_addValueChangeListener(KEY_SETTINGS + "_ts", async ()=>{
    try{
      const fresh = await getSettings();
      window.__mwi_settings_cache = Object.assign({}, DEFAULT_SETTINGS, fresh);
      applyScalesAndTheme();
      await syncSettingsToModal();
    }catch(e){console.error("Settings ts sync error:", e)}
  });

  window.__mwi_panel = { refresh: refreshPanelUI, setSelectedSlot: (n)=>{ selectedSlot = n; refreshPanelUI(); } };
}

function buildSettingsModal(){
  const modal = document.createElement("div");
  modal.id = "mwi-settings-modal";
  modal.className = "mwi-ui";
  modal.innerHTML = `
    <h3 class="mwi-title">配装导入 设置</h3>
    <div id="mwi-settings-list"></div>
  `;
  const list = modal.querySelector("#mwi-settings-list");
  const settingsControls = {
    chkReadOnly: null,
    chkBlockTest: null,
    chkAutoRead: null,
    simScaleInput: null,
    simScaleLabel: null,
    mwiScaleInput: null,
    mwiScaleLabel: null,
    simThemeSelect: null,
    gameThemeSelect: null
  };

  (async()=>{
    const s = await getSettings();
    window.__mwi_settings_cache = Object.assign({}, DEFAULT_SETTINGS, s);

    function row(labelHtml, controlEl){
      const r = document.createElement("div");
      r.className="mwi-settings-row";
      const lab = document.createElement("label");
      lab.innerHTML=labelHtml;
      const wrap = document.createElement("div");
      wrap.className="mwi-settings-controls";
      wrap.appendChild(controlEl);
      r.appendChild(lab);
      r.appendChild(wrap);
      return r;
    }

    const chkReadOnly = document.createElement("input");
    chkReadOnly.type="checkbox";
    chkReadOnly.checked = s.READ_ONLY_IRONCOW;
    chkReadOnly.onchange = async ()=>{
      window.__mwi_settings_cache.READ_ONLY_IRONCOW = chkReadOnly.checked;
      await saveSettings(window.__mwi_settings_cache);
    };
    settingsControls.chkReadOnly = chkReadOnly;
    list.appendChild(row("屏蔽标准角色", chkReadOnly));

    const chkBlockTest = document.createElement("input");
    chkBlockTest.type="checkbox";
    chkBlockTest.checked = !!s.BLOCK_TEST_SERVERS;
    chkBlockTest.onchange = async ()=>{
      window.__mwi_settings_cache.BLOCK_TEST_SERVERS = chkBlockTest.checked;
      await saveSettings(window.__mwi_settings_cache);
    };
    settingsControls.chkBlockTest = chkBlockTest;
    list.appendChild(row("屏蔽测试服角色", chkBlockTest));

    const chkAutoRead = document.createElement("input");
    chkAutoRead.type="checkbox";
    chkAutoRead.checked = !!s.ENABLE_AUTO_READ_ON_ENTER;
    chkAutoRead.onchange = async ()=>{
      window.__mwi_settings_cache.ENABLE_AUTO_READ_ON_ENTER = chkAutoRead.checked;
      await saveSettings(window.__mwi_settings_cache);
    };
    settingsControls.chkAutoRead = chkAutoRead;
    list.appendChild(row("进入游戏时读取角色配置", chkAutoRead));

    if(IS_SIM){
      const simScaleInput = document.createElement("input");
      simScaleInput.type="range";
      simScaleInput.min="0.5";
      simScaleInput.max="2";
      simScaleInput.step="0.1";
      simScaleInput.value = s.SIM_SCALE || 1.0;
      const simScaleLabel = document.createElement("div");
      simScaleLabel.innerText = simScaleInput.value + "×";
      simScaleLabel.className = "mwi-btn-text";

      simScaleInput.oninput = async ()=>{
        const newScale = Number(simScaleInput.value);
        simScaleLabel.innerText = newScale + "×";

        window.__mwi_settings_cache.SIM_SCALE = newScale;
        document.querySelector("#mwi-panel")?.style.setProperty("transform", `scale(${newScale})`);
        document.querySelector("#mwi-floating-btn")?.style.setProperty("transform", `scale(${newScale})`);
        const modal = $("#mwi-settings-modal");
        modal?.style.setProperty("transform", `translate(-50%, -50%) scale(${newScale})`);

        currentScale = newScale;
        const btn = document.querySelector("#mwi-floating-btn");
        const panel = document.querySelector("#mwi-panel");
        if(btn && panel) positionPanelNearButton(btn, panel);

        await saveSettings(window.__mwi_settings_cache);
        showToast(`缩放倍率已保存: ${newScale}×`);
      };

      settingsControls.simScaleInput = simScaleInput;
      settingsControls.simScaleLabel = simScaleLabel;
      const simWrap = document.createElement("div");
      simWrap.appendChild(simScaleInput);
      simWrap.appendChild(simScaleLabel);
      list.appendChild(row("战斗模拟器缩放倍率", simWrap));
    } else {
      const mwiScaleInput = document.createElement("input");
      mwiScaleInput.type="range";
      mwiScaleInput.min="0.5";
      mwiScaleInput.max="2";
      mwiScaleInput.step="0.1";
      mwiScaleInput.value = s.MWI_SCALE || 1.0;
      const mwiScaleLabel = document.createElement("div");
      mwiScaleLabel.innerText = mwiScaleInput.value + "×";
      mwiScaleLabel.className = "mwi-btn-text";

      mwiScaleInput.oninput = async ()=>{
        const newScale = Number(mwiScaleInput.value);
        mwiScaleLabel.innerText = newScale + "×";

        window.__mwi_settings_cache.MWI_SCALE = newScale;
        document.querySelector("#mwi-panel")?.style.setProperty("transform", `scale(${newScale})`);
        document.querySelector("#mwi-floating-btn")?.style.setProperty("transform", `scale(${newScale})`);
        const modal = $("#mwi-settings-modal");
        modal?.style.setProperty("transform", `translate(-50%, -50%) scale(${newScale})`);

        currentScale = newScale;
        const btn = document.querySelector("#mwi-floating-btn");
        const panel = document.querySelector("#mwi-panel");
        if(btn && panel) positionPanelNearButton(btn, panel);

        await saveSettings(window.__mwi_settings_cache);
        showToast(`缩放倍率已保存: ${newScale}×`);
      };

      settingsControls.mwiScaleInput = mwiScaleInput;
      settingsControls.mwiScaleLabel = mwiScaleLabel;
      const mwiWrap = document.createElement("div");
      mwiWrap.appendChild(mwiScaleInput);
      mwiWrap.appendChild(mwiScaleLabel);
      list.appendChild(row("MWI缩放倍率", mwiWrap));
    }

    if(IS_SIM){
      const simThemeSelect = document.createElement("select");
      simThemeSelect.className = "mwi-btn-text";
      [["follow","跟随"],["dark","黑暗"],["light","明亮"]].forEach(([v,t])=>{
        const o=document.createElement("option");
        o.value=v;
        o.innerText=t;
        simThemeSelect.appendChild(o);
      });
      simThemeSelect.value = s.SIM_THEME || "follow";
      simThemeSelect.onchange = async ()=>{
        window.__mwi_settings_cache.SIM_THEME = simThemeSelect.value;
        if(simThemeSelect.value==="dark") document.documentElement.classList.add("mwi-theme-dark"), document.documentElement.classList.remove("mwi-theme-light");
        else if(simThemeSelect.value==="light") document.documentElement.classList.add("mwi-theme-light"), document.documentElement.classList.remove("mwi-theme-dark");
        else {
          const chk = $("#darkModeToggle");
          if(chk) document.documentElement.classList.remove("mwi-theme-dark","mwi-theme-light"), document.documentElement.classList.add(chk.checked?"mwi-theme-dark":"mwi-theme-light");
        }
        await saveSettings(window.__mwi_settings_cache);
      };
      settingsControls.simThemeSelect = simThemeSelect;
      list.appendChild(row("战斗模拟器主题", simThemeSelect));
    } else {
      const gameThemeSelect = document.createElement("select");
      gameThemeSelect.className = "mwi-btn-text";
      [["dark","黑暗"],["light","明亮"]].forEach(([v,t])=>{
        const o=document.createElement("option");
        o.value=v;
        o.innerText=t;
        gameThemeSelect.appendChild(o);
      });
      gameThemeSelect.value = s.GAME_THEME || "dark";
      gameThemeSelect.onchange = async ()=>{
        window.__mwi_settings_cache.GAME_THEME = gameThemeSelect.value;
        if(gameThemeSelect.value==="dark") document.documentElement.classList.add("mwi-theme-dark"), document.documentElement.classList.remove("mwi-theme-light");
        else document.documentElement.classList.add("mwi-theme-light"), document.documentElement.classList.remove("mwi-theme-dark");
        await saveSettings(window.__mwi_settings_cache);
      };
      settingsControls.gameThemeSelect = gameThemeSelect;
      list.appendChild(row("MWI页面主题", gameThemeSelect));
    }
  })();

  window.syncSettingsToModal = async function(){
    try{
      const freshSettings = await getSettings();
      window.__mwi_settings_cache = Object.assign({}, DEFAULT_SETTINGS, freshSettings);

      if(settingsControls.chkReadOnly) settingsControls.chkReadOnly.checked = freshSettings.READ_ONLY_IRONCOW;
      if(settingsControls.chkBlockTest) settingsControls.chkBlockTest.checked = !!freshSettings.BLOCK_TEST_SERVERS;
      if(settingsControls.chkAutoRead) settingsControls.chkAutoRead.checked = !!freshSettings.ENABLE_AUTO_READ_ON_ENTER;

      if(IS_SIM && settingsControls.simScaleInput){
        const savedScale = freshSettings.SIM_SCALE || 1.0;
        settingsControls.simScaleInput.value = savedScale;
        if(settingsControls.simScaleLabel) settingsControls.simScaleLabel.innerText = savedScale + "×";
        currentScale = savedScale;
      }

      if(IS_MILKY && settingsControls.mwiScaleInput){
        const savedScale = freshSettings.MWI_SCALE || 1.0;
        settingsControls.mwiScaleInput.value = savedScale;
        if(settingsControls.mwiScaleLabel) settingsControls.mwiScaleLabel.innerText = savedScale + "×";
        currentScale = savedScale;
      }

      if(IS_SIM && settingsControls.simThemeSelect) settingsControls.simThemeSelect.value = freshSettings.SIM_THEME || "follow";
      if(IS_MILKY && settingsControls.gameThemeSelect) settingsControls.gameThemeSelect.value = freshSettings.GAME_THEME || "dark";

      const btn = document.querySelector("#mwi-floating-btn");
      const panel = document.querySelector("#mwi-panel");
      if(btn && panel) positionPanelNearButton(btn, panel);
    }catch(e){console.error("Sync settings to modal error:", e)}
  };

  return { modal };
}

async function main(){
  try{
    const s = await getSettings();
    window.__mwi_settings_cache = Object.assign({}, DEFAULT_SETTINGS, s);
    await createFloatingUI();

    currentScale = IS_SIM ? s.SIM_SCALE : s.MWI_SCALE;

    if(IS_MILKY && window.__mwi_settings_cache.ENABLE_AUTO_READ_ON_ENTER){
      const flag = await GM_getValue("mwi_auto_read_once", null);
      if(!flag){
        await GM_setValue("mwi_auto_read_once", true);
        const st = await waitForGameReady(9000, 300);
        if(st) {
          await readAndSaveCurrentPageConfig();
          window.__mwi_panel?.refresh?.();
        }
      }
    }

    if(IS_SIM){
      const panel = document.querySelector("#mwi-panel");
      if(panel) panel.style.transform = `scale(${currentScale})`;
      const modal = $("#mwi-settings-modal");
      if(modal) modal.style.transform = `translate(-50%, -50%) scale(${currentScale})`;
      if(window.__mwi_settings_cache.SIM_THEME === "follow"){
        const chk = $("#darkModeToggle");
        if(chk){
          document.documentElement.classList.remove("mwi-theme-dark","mwi-theme-light");
          document.documentElement.classList.add(chk.checked ? "mwi-theme-dark" : "mwi-theme-light");
        }
      } else if(window.__mwi_settings_cache.SIM_THEME === "dark"){
        document.documentElement.classList.remove("mwi-theme-light");
        document.documentElement.classList.add("mwi-theme-dark");
      } else {
        document.documentElement.classList.remove("mwi-theme-dark");
        document.documentElement.classList.add("mwi-theme-light");
      }
    } else {
      const panel = document.querySelector("#mwi-panel");
      if(panel) panel.style.transform = `scale(${currentScale})`;
      const modal = $("#mwi-settings-modal");
      if(modal) modal.style.transform = `translate(-50%, -50%) scale(${currentScale})`;
      if(window.__mwi_settings_cache.GAME_THEME === "dark"){
        document.documentElement.classList.remove("mwi-theme-light");
        document.documentElement.classList.add("mwi-theme-dark");
      } else {
        document.documentElement.classList.remove("mwi-theme-dark");
        document.documentElement.classList.add("mwi-theme-light");
      }
    }

    GM_addValueChangeListener(KEY_SETTINGS + "_ts", async ()=>{
      try{
        const fresh = await getSettings();
        window.__mwi_settings_cache = Object.assign({}, DEFAULT_SETTINGS, fresh);
        currentScale = IS_SIM ? fresh.SIM_SCALE : fresh.MWI_SCALE;

        const modal = $("#mwi-settings-modal");
        if(modal){
          modal.classList.toggle("dark", IS_SIM ? window.__mwi_settings_cache.SIM_THEME === "dark" : window.__mwi_settings_cache.GAME_THEME === "dark");
          modal.style.transform = `translate(-50%, -50%) scale(${currentScale})`;
        }

        const panel = $("#mwi-panel"), btn = $("#mwi-floating-btn");
        if(panel && btn){
          if(IS_MILKY){
            panel.style.transform = `scale(${currentScale})`;
            btn.style.transform = `scale(${currentScale})`;
            if(window.__mwi_settings_cache.GAME_THEME === "dark"){
              document.documentElement.classList.remove("mwi-theme-light");
              document.documentElement.classList.add("mwi-theme-dark");
            } else {
              document.documentElement.classList.remove("mwi-theme-dark");
              document.documentElement.classList.add("mwi-theme-light");
            }
          } else {
            panel.style.transform = `scale(${currentScale})`;
            btn.style.transform = `scale(${currentScale})`;
            if(window.__mwi_settings_cache.SIM_THEME === "dark"){
              document.documentElement.classList.remove("mwi-theme-light");
              document.documentElement.classList.add("mwi-theme-dark");
            } else if(window.__mwi_settings_cache.SIM_THEME === "light"){
              document.documentElement.classList.remove("mwi-theme-dark");
              document.documentElement.classList.add("mwi-theme-light");
            } else {
              const chk = $("#darkModeToggle");
              if(chk){
                document.documentElement.classList.remove("mwi-theme-dark","mwi-theme-light");
                document.documentElement.classList.add(chk.checked?"mwi-theme-dark":"mwi-theme-light");
              }
            }
          }
        }

        const btnEl = document.querySelector("#mwi-floating-btn");
        const panelEl = document.querySelector("#mwi-panel");
        if(btnEl && panelEl) positionPanelNearButton(btnEl, panelEl);
      }catch(e){console.error("Settings ts sync main error:", e)}
    });

    try{
      if(IS_MILKY){
        const st = await waitForGameReady(9000, 300);
        if(st){
          await readAndSaveCurrentPageConfig();
          window.__mwi_panel?.refresh?.();
        }
      } else {
        const st = readGameStateFromPage();
        if(st && st.character){
          await readAndSaveCurrentPageConfig();
          window.__mwi_panel?.refresh?.();
        }
      }
    }catch(e){console.error("Initial load error:", e)}

    setTimeout(()=>{
      window.__mwi_panel?.refresh?.();
      const btn = document.querySelector("#mwi-floating-btn");
      const panel = document.querySelector("#mwi-panel");
      if(btn && panel) positionPanelNearButton(btn, panel);
    }, 800);
  }catch(e){console.error("Main init error:", e)}
}

main();