        body { 
    background-color: #0f172a; 
    /* ⚠️ 路徑是 ../assets 而不是 assets：CSS 裡的相對 url() 相對於「CSS 檔本身」解析，
       而這個檔案在 src/ 底下。Phase 0a 把內聯 <style> 抽出來時漏了這件事，
       導致背景圖從 ccb95af 起變成請求 src/assets/... 而 404（畫面只剩底色）。
       JS 裡的路徑不受影響——那些是相對於 index.html 解析的。 */
    background-image: url('../assets/background/background.png'); /* 👈 在這裡填入你的圖片檔名 */
    background-size: cover; 
    background-position: center; 
    background-repeat: no-repeat;
    color: #f8fafc; 
    font-family: "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; 
    overflow: hidden; 
    user-select: none; 
}
        /* 🎨 介面圖示（2026-09-27）：`uiIconSvg()`／index.html 裡的 `<svg class="uic"><use href="#i-…">`。
           預設跟字一樣大、吃文字顏色；各處自己設尺寸。 */
        .uic { display: inline-block; width: 1em; height: 1em; flex: 0 0 auto; vertical-align: -0.15em; }
        .panel { background: #1e293b; border: 1px solid #334155; border-radius: 0.75rem; box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.5); display: flex; flex-direction: column; }
        .panel-header { background: #0f172a; border-bottom: 1px solid #334155; padding: 0.75rem 1.25rem; font-size: 1.125rem; font-weight: bold; color: #fbbf24; border-top-left-radius: 0.75rem; border-top-right-radius: 0.75rem; }
        ::-webkit-scrollbar { width: 8px; }
        ::-webkit-scrollbar-track { background: #0f172a; border-radius: 4px; }
        ::-webkit-scrollbar-thumb { background: #475569; border-radius: 4px; }
        ::-webkit-scrollbar-thumb:hover { background: #64748b; }
        .btn { background: #334155; border: 1px solid #475569; color: #f8fafc; padding: 0.5rem 1rem; border-radius: 0.5rem; cursor: pointer; transition: all 0.2s; font-weight: 500; }
        .btn:hover:not(:disabled) { background: #475569; }
        .btn.active { background: #2563eb; border-color: #3b82f6; color: #ffffff; }
        .btn:disabled { opacity: 0.5; cursor: not-allowed; }
        .list-item { padding: 0.75rem; border-bottom: 1px solid #334155; cursor: pointer; display: flex; justify-content: space-between; align-items: center; font-size: 1rem; width: 100%;}
        .list-item:hover { background: #334155; }
        .set-active { background: #064e3b !important; border: 1px solid #047857 !important; }
        .mob-target { padding: 0.75rem; border: 2px solid #334155; border-radius: 0.5rem; background: #0f172a; cursor: pointer; flex: 1; text-align: center; display: flex; flex-direction: column; justify-content: center; transition: all 0.2s; min-height: 185px;}
        .mob-target.active { border-color: #ef4444; background: #450a0a; }
        .mob-target:hover:not(.active) { background: #1e293b; }
        /* ── 分頁裡的可捲清單（2026-08-27）─────────────────────────────────
           ⚠️ `flex-1 overflow-y-auto` **在 flex 容器裡其實不會捲**。
              flex 子項的 `min-height` 預設是 `auto`，意思是「不可以縮到比內容小」——
              於是 `flex-1` 完全約束不了它：清單有多長它就有多高，把父層撐爆，
              再被父層的 `overflow-hidden` 裁掉。**畫面上看起來就是「東西沒列出來」**，
              而且沒有捲軸可以發現真相（實測：內容 2213px 塞在 497px 的框裡，
              1700px 被裁掉；用搜尋卻找得到那些項目 —— 那是玩家回報的線索）。
              解法是 `min-height: 0`。這兩個 class 就是把它包起來，
              免得下一份清單又漏掉（`docs/loadtest.js` 有斷言擋著）。

           ⚠️ `overflow-x: hidden` 一定要明講。只寫 `overflow-y: auto` 的話，
              **另一軸會依規範自動變成 `auto`**（一軸不是 visible 時，另一軸的
              `visible` 就會被算成 `auto`）—— 於是任何一個斷不掉的長行都能讓整份清單
              橫著拉走。桌機因為欄位夠寬幾乎踩不到，手機的窄視窗一定會現形：
              手機版的原型上實測到 scrollWidth 1193 對 clientWidth 362、
              而且 `scrollLeft` 真的推得動 400px。
              症狀是「版面壞掉」而不是「有捲軸」，很難聯想到 CSS 的預設值。
              `docs/smoketest.sh` 的窄視窗那一趟有斷言守著。 */
        /* ⚔️ 2026-09-24：整個關掉的系統（PvP）。!important：手機版切分組時會改 display，不能讓它把藏起來的鈕翻出來 */
        [data-sys-off] { display: none !important; }
        .scroll-fill { flex: 1 1 0%; min-height: 0; overflow-y: auto; overflow-x: hidden; }   /* 吃掉剩下的空間 */
        .scroll-fit  { flex: 0 1 auto; min-height: 0; overflow-y: auto; overflow-x: hidden; } /* 依內容，但空間不夠時可以縮 */
        .tip-host { cursor: help; }
        /* 📊 段 45：物品說明的數值列。先前是一整串 ` / ` 串起來的流水帳，
           而決定「要不要穿」的正是這個畫面。
           ⚠️ 差值的顏色只看 `up`／`down`，方向由 `itemStatRows()` 決定 ——
              `ac` 越大越好但印成負數，讓 CSS 依顯示值猜方向一定會反。 */
        .idesc { color: #94a3b8; line-height: 1.7; margin-bottom: 8px; }
        /* 🛒 商店列的物品說明（2026-09-02）。⚠️ 它原本掛著 Tailwind 的
           `hidden md:block` —— 段 5 之後那一欄鎖在 500px，`md:`（768px）
           **永遠不會 fire**，所以說明一直在 DOM 裡卻沒有任何裝置看得到。
           ⚠️ 夾在兩行以內：最長的一句有 28 字，不夾的話那一列會比別列高一截，
           而段 15 才把這份清單調到一頁看得下 9 列。 */
        .shop-desc {
            font-size: 11.5px; line-height: 1.35; color: #94a3b8;
            display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
            overflow: hidden;
        }
        /* ⚠️ 段 46：三欄都用 `max-content` ＋ `justify-content: start`，整張表靠左收攏。
              先前是 `1fr auto` —— 標籤靠左、數值被推到最右邊，中間留下一大段空白，
              眼睛要橫越整個寬度才對得上（玩家回報「看不好對」）。
              現在標籤欄只有最長標籤那麼寬，數值就跟在後面。 */
        .istats { display: grid; grid-template-columns: max-content max-content max-content;
                  justify-content: start; gap: 3px 12px; font-size: 13px; }
        .istats .ilbl { color: #94a3b8; }
        .istats .ival { text-align: right; font-weight: 700; font-variant-numeric: tabular-nums; white-space: nowrap; }
        /* 差值自己一欄 —— 才不會因為數值長度不同而左右浮動 */
        .istats .idcol { text-align: right; min-width: 2.4em; }
        .idelta { font-size: 11.5px; font-weight: 700; }
        .idelta.up { color: #34d399; }
        .idelta.down { color: #f87171; }
        .inote { margin-top: 8px; line-height: 1.7; font-size: 12.5px; }
        .stat-row { display: contents; }
        .stat-row.row-hidden { display: none; }
        /* 📱 段 43：能力值分頁的分組。20 條衍生數值原本擠在同一個平鋪格線裡，
           Lv1 時大多是 0，有意義的那幾條浮不出來。
           ⚠️ 沿用設定頁 `.sgrp` 的形狀（段 17），不要發明第三種分組樣式。 */
        .dgrp { border-top: 1px solid #334155; padding-top: 10px; }
        /* 📋 2026-10-01：共用選單（dialog.js 的 uiPick；世界王面板的 ⇄ 用它） */
        .ui-pick-list { display: flex; flex-direction: column; gap: 6px; max-height: 60vh; overflow-y: auto; overflow-x: hidden; }
        .ui-pick-row { display: flex; align-items: center; gap: 10px; min-height: 48px; padding: 6px 10px; border-radius: 10px; border: 1px solid #334155; background: #0b1425; color: #e2e8f0; text-align: left; }
        .ui-pick-row.cur { border-color: #f59e0b; background: #422006; }
        .ui-pick-ico { width: 32px; height: 32px; border-radius: 6px; flex: 0 0 auto; }
        .ui-pick-n { font-size: 15px; font-weight: 700; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
        .ui-pick-sub { font-size: 12px; color: #94a3b8; margin-left: auto; flex: 0 0 auto; }
        .ui-pick-cur { font-size: 11px; color: #fde68a; border: 1px solid #a16207; border-radius: 999px; padding: 1px 7px; flex: 0 0 auto; }
        /* 🛡️ 2026-10-01：血盟畫面改版（玩家「文字很小又擠在一塊」→ 照設計稿：分頁、卡片、每個 buff 一張外框卡片） */
        .gd-seg { display: flex; gap: 8px; margin-bottom: 12px; }
        .gd-seg button { flex: 1; min-height: 44px; border-radius: 10px; border: 1px solid #475569; background: #1e293b; color: #cbd5e1; font-weight: 700; font-size: 15px; }
        .gd-seg button.on { border-color: #f59e0b; background: #422006; color: #fde68a; }
        .gd-card { border: 1px solid #334155; border-radius: 12px; background: rgba(15, 23, 42, .75); padding: 12px; margin-bottom: 10px; }
        .gd-h { display: flex; align-items: baseline; gap: 8px; }
        .gd-r { margin-left: auto; }
        .gd-title { font-size: 20px; font-weight: 800; color: #fcd34d; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
        .gd-meta { font-size: 13px; color: #94a3b8; }
        .sg-list { margin-bottom: 10px; }
        .sg-row { display: flex; justify-content: space-between; gap: 8px; font-size: 13px; color: #cbd5e1; padding: 2px 0; }   /* 🎁 攻城段 5：各盟進度／歷任城主的一列 */
        .sg-row span:last-child { text-align: right; white-space: nowrap; }
        .sg-row.sg-ours { color: #fde68a; font-weight: 700; }
        .gd-lv { font-size: 17px; font-weight: 800; color: #fde68a; }
        .gd-bar { height: 8px; background: #334155; border-radius: 6px; margin: 8px 0; overflow: hidden; }
        .gd-bar i { display: block; height: 100%; background: #eab308; }
        .gd-kv { display: flex; justify-content: space-between; gap: 8px; font-size: 14px; color: #cbd5e1; padding: 3px 0; }
        .gd-kv b { color: #f1f5f9; text-align: right; }
        .gd-sec { font-size: 14px; font-weight: 800; color: #fbbf24; margin: 16px 2px 8px; letter-spacing: .04em; }
        .gd-sub { font-size: 14px; font-weight: 700; color: #fbbf24; margin-bottom: 4px; }
        .gd-note { font-size: 12.5px; color: #94a3b8; margin: -2px 2px 10px; line-height: 1.5; }
        .gd-notice { font-size: 14px; color: #e2e8f0; line-height: 1.5; }
        .gd-foot { display: flex; gap: 8px; margin-top: 14px; }
        .m-act.gd-sm { min-height: 40px; padding: 0 12px; font-size: 14px; }
        .bf { border: 1px solid #334155; border-radius: 12px; padding: 11px 12px; margin-bottom: 10px; background: #0b1425; }
        .bf.on { border-color: #15803d; background: linear-gradient(180deg, rgba(20, 83, 45, .35), #0b1425 70%); }
        .bf.lock { opacity: .62; }
        .bf-top { display: flex; align-items: center; gap: 8px; }
        .bf-n { font-size: 16px; font-weight: 800; color: #f1f5f9; }
        .bf.on .bf-n { color: #86efac; }
        .bf-tag { font-size: 11.5px; padding: 2px 8px; border-radius: 999px; border: 1px solid #475569; color: #cbd5e1; white-space: nowrap; }
        .bf-tag.on { border-color: #16a34a; color: #86efac; }
        .bf-tag.lock { border-color: #78350f; color: #fcd34d; }
        .bf-unlock { margin-left: auto; font-size: 12px; color: #94a3b8; text-align: right; }
        .bf-v { font-size: 15px; font-weight: 700; color: #fde68a; margin: 8px 0 2px; }
        .bf-src { font-size: 12.5px; color: #94a3b8; }
        .bf-foot { display: flex; align-items: center; gap: 10px; margin-top: 10px; }
        .bf-foot .t { font-size: 12.5px; color: #94a3b8; flex: 1; }
        .bf-foot .m-act { min-width: 92px; }
        .don { display: flex; gap: 8px; align-items: center; }
        .don .acc-in { flex: 1; min-width: 0; }
        .don .unit { font-size: 15px; color: #cbd5e1; }
        .gd-mems { padding: 2px 10px; }
        .gd-mem { display: flex; align-items: center; flex-wrap: wrap; gap: 6px 8px; padding: 10px 2px; border-bottom: 1px solid #1e293b; font-size: 15px; }
        .gd-mem:last-child { border-bottom: 0; }
        .gd-mem .nm { font-weight: 700; color: #e2e8f0; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
        .sg-title { display: inline-block; margin-left: .35rem; padding: 0 .35rem; border: 1px solid #a16207; border-radius: .25rem; color: #fbbf24; font-size: .7rem; line-height: 1.25rem; vertical-align: middle; white-space: nowrap; }   /* 👑 攻城段 3：城主血盟的稱號 */
.gd-mem .r { font-size: 11.5px; padding: 1px 7px; border-radius: 999px; border: 1px solid #a16207; color: #fde68a; white-space: nowrap; }
        .gd-mem .r.v { border-color: #0369a1; color: #bae6fd; }
        .gd-mem .c { font-size: 13px; color: #94a3b8; }
        .gd-mem .sp { margin-left: auto; display: flex; gap: 6px; flex-wrap: wrap; justify-content: flex-end; }
        .gd-mem .sp .m-act { min-height: 36px; padding: 0 10px; font-size: 13px; }
        .gd-rk { display: flex; align-items: center; gap: 10px; padding: 12px; border: 1px solid #334155; border-radius: 12px; margin-bottom: 8px; background: #0b1425; }
        .gd-rk .no { width: 30px; text-align: center; font-size: 20px; font-weight: 800; color: #fbbf24; flex: 0 0 auto; }
        .gd-rk .mid { min-width: 0; flex: 1; }
        .gd-rk .nm { font-size: 16px; font-weight: 800; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
        .gd-rk .sub { font-size: 12.5px; color: #94a3b8; }
        .gd-rk.me { border-color: #f59e0b; }
        /* 👥 建立隊伍：公開／需審核兩顆切換鈕（取代瀏覽器的單選圓點） */
        .pt-seg { display: flex; gap: 8px; margin-top: 10px; }
        .pt-seg .m-act { flex: 1; }
        /* 🐲 2026-10-01：世界王房間的手動施放面板（src/ui/boss-quick.js；參考上游的世界王戰鬥畫面：兩欄卡片、右上 ⇄、底下「使用」） */
        #boss-quick { padding: 0 12px 12px; }
        /* 世界王／攻城房的排列由 boss-room-theme.css 處理。 */
        .bq-hd { font-size: 12px; color: #fbbf24; font-weight: 700; margin-bottom: 6px; }
        .bq-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
        .bq-card { position: relative; border: 1px solid #334155; border-radius: 10px; background: rgba(15, 23, 42, .7); padding: 8px 10px 6px; min-height: 92px; display: flex; flex-direction: column; }
        .bq-t { font-size: 12px; color: #fbbf24; font-weight: 700; }
        .bq-swap { position: absolute; top: -8px; right: -8px; width: 30px; height: 30px; border-radius: 50%; background: #1d4ed8; color: #fff; font-size: 15px; line-height: 30px; text-align: center; border: 1px solid #60a5fa; }
        .bq-mid { display: flex; align-items: center; gap: 8px; margin: 6px 0 4px; min-height: 36px; }
        .bq-ico { width: 34px; height: 34px; border-radius: 6px; flex: 0 0 auto; }
        .bq-n { font-size: 14px; font-weight: 700; color: #e2e8f0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
        .bq-sub { font-size: 11px; color: #94a3b8; margin-left: auto; flex: 0 0 auto; }
        .bq-use { margin-top: auto; min-height: 36px; border-radius: 8px; color: #4ade80; font-weight: 700; background: rgba(22, 101, 52, .25); border: 1px solid #166534; }
        .bq-use:active { transform: scale(.97); }
        /* ⏳ 冷卻倒數（src/ui/boss-quick.js 的 bossQuickApplyCd）：灰底、不能按，原色的填色從左邊線性長到滿，滿了拿掉 .bq-cd 變回原色。 */
        .bq-use { position: relative; overflow: hidden; }
        .bq-use .bq-lbl { position: relative; z-index: 1; }
        .bq-use .bq-cdfill { display: none; position: absolute; left: 0; top: 0; bottom: 0; width: 0; background: rgba(22, 101, 52, .55); }
        .bq-use.bq-cd { color: #94a3b8; background: rgba(51, 65, 85, .6); border-color: #475569; pointer-events: none; }
        .bq-use.bq-cd .bq-cdfill { display: block; }
        .bq-why { margin-top: auto; min-height: 36px; line-height: 36px; text-align: center; font-size: 12px; color: #64748b; }
        .dgh { font-size: 12px; font-weight: 700; color: #fbbf24; letter-spacing: .04em; margin-bottom: 6px; }
        .dgrid { display: grid; grid-template-columns: 1fr auto; gap: 4px 16px; font-size: 13px; }
        /* ⚠️ 用後代選擇器不是 `>` —— `.stat-row` 是 `display: contents`，它的
              兩個 div 在**版面上**是 grid 項目，但在 **DOM 上**仍是孫節點，
              `>` 選不到。用 `>` 的話近／遠距離那幾列的行高會跟其他組不一樣。 */
        .dgrid div { line-height: 1.6; }
        .dgrid .text-right { font-variant-numeric: tabular-nums; }
        /* 主屬性放大一級：它是玩家配點時真正在看的東西，但不必是衍生數值的兩倍 */
        .dgrid.pri { font-size: 15px; gap: 7px 16px; }
        /* ⚠️ `#tab-stats` 帶著 `text-lg` 與 `gap-5`（桌機時代留下的），會蓋掉上面的
              字級與段距 —— 分組之後那個間距太鬆，一頁只放得下三組。 */
        #tab-stats { font-size: 13px; gap: 14px; padding: 14px; }
        /* ⚠️ 「其中 …」那兩列是**上面那幾列的組成部分**（已經被加進去了），
              所以縮排＋壓低對比，不要讓人以為要再加一次。 */
        .stat-row.sub > div:first-child { padding-left: 12px; font-size: 12px; }
        .stat-row.sub > div:last-child { font-size: 12px; }
        .game-tooltip { position: fixed; z-index: 80; max-width: 320px; pointer-events: none; display: none; background: rgba(15,23,42,0.97); border: 1px solid #64748b; border-radius: 8px; padding: 10px 12px; font-size: 13px; line-height: 1.5; box-shadow: 0 8px 30px rgba(0,0,0,0.7); }
        .bar-bg { background: #334155; height: 20px; position: relative; border-radius: 999px; overflow: hidden; }
        .bar-fill { height: 100%; transition: width 0.2s ease-out; }
        .bar-text { position: absolute; top: 0; left: 0; width: 100%; text-align: center; font-size: 12px; line-height: 20px; font-weight: 700; text-shadow: 1px 1px 2px rgba(0,0,0,0.9); }
        .c-blessed { color: #facc15; } .c-ancient { color: #c084fc; } .c-rare { color: #fb923c; } .c-attr { color: #22d3ee; } .c-anc-bless { color: #e3a625; }
        /* 🔮 移植 02：增益另外三款與詛咒（顏色照上游：永恆紅／不朽綠／太初藍、詛咒紅） */
        .c-eternal { color: #f87171; } .c-immortal { color: #4ade80; } .c-primordial { color: #60a5fa; } .c-cursed { color: #ef4444; }
        /* 屬性詞綴 12 色：元素分色（火紅/水藍/風綠/地琥珀），階級越高越鮮明，靈系發光 */
        .c-attr-fire1  { color: #fca5a5; } .c-attr-fire3  { color: #ef4444; } .c-attr-fire5  { color: #dc2626; }
        .c-attr-water1 { color: #93c5fd; } .c-attr-water3 { color: #3b82f6; } .c-attr-water5 { color: #2563eb; }
        .c-attr-wind1  { color: #86efac; } .c-attr-wind3  { color: #22c55e; } .c-attr-wind5  { color: #16a34a; }
        .c-attr-earth1 { color: #fcd34d; } .c-attr-earth3 { color: #f59e0b; } .c-attr-earth5 { color: #d97706; }
        .c-attr-glow { text-shadow: 0 0 6px currentColor, 0 0 10px currentColor; }
        /* 🔨 移植 13：滿強化的裝備名稱前綴「淬鍊」（取代 +N）。
           用與強化值同一個語彙的琥珀色 ＋ 微光 —— 它要一眼看出「這件到頂了」，
           但不可以比屬性詞綴的五階色更搶眼（那才是稀有度）。 */
        .c-refined { color: #fbbf24; text-shadow: 0 0 5px rgba(251, 191, 36, 0.6); }
        /* 🏅 移植 13-ⓑ：傳說品（40 件）的琥珀金 ＋ 圖示呼吸光。
           ⚠️ 比「淬鍊」深一階（#d98a04 vs #fbbf24）—— 兩者可能同時出現在
              同一個名字上（淬鍊 前綴 ＋ 傳說 名稱），色階要分得開。 */
        .c-legend { color: #d98a04; font-weight: bold;
                    text-shadow: 0 0 4px rgba(217,138,4,0.65), 0 0 10px rgba(180,83,9,0.5); }
        .legend-glow { animation: legendGlow 2s ease-in-out infinite; }
        /* 🏺 移植 43：遺物的天藍（上游 .c-relic／.relic-glow，數字逐字照抄） */
        .c-relic { color: #38bdf8; font-weight: bold;
                   text-shadow: 0 0 4px rgba(56,189,248,0.7), 0 0 10px rgba(2,132,199,0.5); }
        .relic-glow { animation: relicGlow 2s ease-in-out infinite; }
        @keyframes relicGlow {
            0%, 100% { filter: drop-shadow(0 0 4px rgba(56,189,248,0.7)); }
            50%      { filter: drop-shadow(0 0 9px rgba(56,189,248,1)) drop-shadow(0 0 16px rgba(2,132,199,0.65)); }
        }
        @keyframes legendGlow {
            0%, 100% { filter: drop-shadow(0 0 4px rgba(217,138,4,0.7)); }
            50%      { filter: drop-shadow(0 0 9px rgba(217,138,4,1)) drop-shadow(0 0 16px rgba(180,83,9,0.65)); }
        }
        /* 📱 段 30：字級從 1.05rem（16.8px）降到 13px。
           ⚠️ 原本這是**全 app 最大的字** —— 手機版把每一樣東西都調小了
              （底部導覽 11px、分頁列 12.5px、連地圖名稱與角色名稱這兩個
              「標題」也只有 15px），唯獨這一條沒被掃到，於是一段流水帳的
              內文比每一個標題都醒目，畫面的輕重關係整個反過來。
              1.05rem 是桌機時代的值：那時日誌在 340px 的側欄裡，放大才讀得動。
           ⚠️ margin 跟著從 6px 收到 4px，是為了維持原本的行距節奏
              （間距 ÷ 行高 ≈ 0.24）—— 只縮字不縮間距的話，多出來的空間
              會被 margin 吃掉，密度幾乎沒有改善。
           ⚠️ 字級只寫在這裡一處。`#combat-log`／`#sys-log` 上的 `text-base`
              從來沒有生效過（每一行都是 .log-entry，rem 又是相對 root），
              留著只會讓人以為日誌是 16px —— 段 30 已一併從 index.html 移除。 */
        .log-entry { margin-bottom: 4px; line-height: 1.6; font-size: 0.8125rem; }
        @keyframes flash { 0% { opacity: 1; } 50% { opacity: 0.5; } 100% { opacity: 1; } }
        .anim-flash { animation: flash 1s infinite; }
        
        /* --- 屬性受擊動畫系統 (高速打擊版) --- */
        /* 1. 一般物理 */
        @keyframes hitNormal {
            0%, 100% { transform: translateX(0) scale(1); filter: brightness(1); }
            25%, 75% { transform: translateX(-4px) scale(0.95); filter: brightness(1.5) drop-shadow(0 0 10px rgba(239,68,68,0.8)); }
            50% { transform: translateX(4px) scale(0.95); filter: brightness(1.5) drop-shadow(0 0 10px rgba(239,68,68,0.8)); }
        }
        .anim-hit-normal { animation: hitNormal 0.1s ease-out; } /* 👈 改為 0.1s */

        /* 2. 無屬性魔法 */
        @keyframes hitMagic {
            0%, 100% { filter: brightness(1); transform: scale(1); }
            30% { filter: brightness(2) drop-shadow(0 0 20px #c084fc); transform: scale(1.05); }
            70% { filter: brightness(1.5) drop-shadow(0 0 10px #d8b4fe); transform: scale(0.98); }
        }
        .anim-hit-magic { animation: hitMagic 0.15s ease-out; } /* 👈 改為 0.15s */

        /* 3. 火屬性 */
        @keyframes hitFire {
            0%, 100% { transform: scale(1); filter: brightness(1); }
            25% { transform: scale(1.08); filter: brightness(2) drop-shadow(0 0 20px #ef4444); }
            75% { transform: scale(0.95); filter: brightness(1.5) drop-shadow(0 0 10px #f97316); }
        }
        .anim-hit-fire { animation: hitFire 0.15s ease-out; } /* 👈 改為 0.15s */

        /* 4. 水屬性 */
        @keyframes hitWater {
            0%, 100% { transform: translateY(0); filter: brightness(1); }
            25% { transform: translateY(-6px); filter: brightness(1.5) drop-shadow(0 0 15px #3b82f6); }
            75% { transform: translateY(4px); filter: brightness(1.2) drop-shadow(0 0 10px #60a5fa); }
        }
        .anim-hit-water { animation: hitWater 0.15s ease-out; } /* 👈 改為 0.15s */

        /* 5. 風屬性 */
        @keyframes hitWind {
            0%, 100% { transform: skewX(0deg) translateX(0); filter: brightness(1); }
            25% { transform: skewX(-15deg) translateX(-5px); filter: brightness(1.8) drop-shadow(0 0 15px #10b981); }
            75% { transform: skewX(15deg) translateX(5px); filter: brightness(1.3) drop-shadow(0 0 10px #34d399); }
        }
        .anim-hit-wind { animation: hitWind 0.15s ease-out; } /* 👈 改為 0.15s */

        /* 6. 地屬性 */
        @keyframes hitEarth {
            0%, 100% { transform: translateY(0) scaleY(1); filter: brightness(1); }
            30% { transform: translateY(8px) scaleY(0.85) scaleX(1.05); filter: brightness(0.8) drop-shadow(0 0 20px #a16207); }
            70% { transform: translateY(-2px) scaleY(1.02); filter: brightness(1.2) drop-shadow(0 0 10px #ca8a04); }
        }
        .anim-hit-earth { animation: hitEarth 0.15s ease-out; } /* 👈 改為 0.15s */
		
		/* 祝福物品的螢光邊緣呼吸特效 */
        @keyframes blessGlow {
            0%, 100% { filter: drop-shadow(0 0 4px rgba(250, 204, 21, 0.5)); }
            50% { filter: drop-shadow(0 0 10px rgba(250, 204, 21, 1)); }
        }
        .bless-glow { animation: blessGlow 2s infinite ease-in-out; }
        /* 📜 賦予祝福卷軸的紙改成琥珀金（2026-10-12，玩家「跟一般的武防卷做區別」，截圖對選定案）。
           ⚠️ 不動素材檔（`assets/` 不進版控、改了也不會跟著部署）—— 用濾鏡染色。
           ⚠️ 用圖片網址比對而不是加 class：這三張圖出現在背包、物品視窗、商店、煉製、市場、收集冊、抽獎……
              二十幾個地方，一個個加 class 一定漏；三種賦予卷（含祝福版，`img` 指回同一張）只用這三張圖
              （`loadtest.js` 守著「六張卷軸的圖示網址都命中這三個結尾」）。
           ⚠️ 祝福版的金光也是 `filter` 動畫，會把靜態濾鏡蓋掉 —— 所以另做一組「染色 ＋ 金光」的 keyframes。
              iOS 上光芒一律 `filter: none`（見下面 `html[data-ios]` 那段），所以那裡再補一次靜態染色。 */
        img:is([src$="/賦予武器祝福卷軸.png"], [src$="/賦予盔甲祝福卷軸.png"], [src$="/賦予飾品祝福卷軸.png"]) {
            filter: sepia(1) saturate(4.5) hue-rotate(-5deg) brightness(.98);
        }
        @keyframes blessGlowAmberPaper {
            0%, 100% { filter: sepia(1) saturate(4.5) hue-rotate(-5deg) brightness(.98) drop-shadow(0 0 4px rgba(250, 204, 21, 0.5)); }
            50% { filter: sepia(1) saturate(4.5) hue-rotate(-5deg) brightness(.98) drop-shadow(0 0 10px rgba(250, 204, 21, 1)); }
        }
        img:is([src$="/賦予武器祝福卷軸.png"], [src$="/賦予盔甲祝福卷軸.png"], [src$="/賦予飾品祝福卷軸.png"]).bless-glow {
            animation-name: blessGlowAmberPaper;
        }
        /* 🔮 詛咒：紅色呼吸光，含詛咒一律用它（蓋過其他詞綴的光） */
        @keyframes curseGlow {
            0%, 100% { filter: drop-shadow(0 0 4px rgba(239, 68, 68, 0.6)); }
            50% { filter: drop-shadow(0 0 10px rgba(239, 68, 68, 1)); }
        }
        .curse-glow { animation: curseGlow 1.6s infinite ease-in-out; }

        /* 遠古物品的紅色螢光呼吸特效（亮度比照祝福） */
        @keyframes ancientGlow {
            0%, 100% { filter: drop-shadow(0 0 4px rgba(192, 132, 252, 0.5)); }
            50% { filter: drop-shadow(0 0 10px rgba(168, 85, 247, 1)); }
        }
        .ancient-glow { animation: ancientGlow 2s infinite ease-in-out; }

        /* 遠古+祝福：紫↔金 交替螢光（維持顯眼度，雙層光暈 + 亮度/飽和提升 + 較快脈動） */
        @keyframes ancBlessGlow {
            0%, 100% {
                filter: drop-shadow(0 0 8px rgba(192, 132, 252, 1))
                        drop-shadow(0 0 20px rgba(168, 85, 247, 0.98))
                        drop-shadow(0 0 36px rgba(147, 51, 234, 0.72))
                        brightness(1.45) saturate(1.9);
            }
            50% {
                filter: drop-shadow(0 0 14px rgba(255, 226, 150, 1))
                        drop-shadow(0 0 30px rgba(243, 188, 72, 1))
                        drop-shadow(0 0 50px rgba(214, 150, 30, 0.9))
                        brightness(1.6) saturate(2.0);
            }
        }
        .anc-bless-glow { animation: ancBlessGlow 1.3s infinite ease-in-out; }

        /* 屬性+遠古：紫光（顏色同單遠古，顯眼度＝雙詞綴） */
        @keyframes ancientGlowStrong {
            0%, 100% {
                filter: drop-shadow(0 0 7px rgba(192, 132, 252, 1))
                        drop-shadow(0 0 18px rgba(168, 85, 247, 0.95))
                        drop-shadow(0 0 32px rgba(147, 51, 234, 0.65))
                        brightness(1.35) saturate(1.8);
            }
            50% {
                filter: drop-shadow(0 0 16px rgba(214, 170, 255, 1))
                        drop-shadow(0 0 34px rgba(184, 104, 250, 1))
                        drop-shadow(0 0 54px rgba(150, 60, 235, 0.9))
                        brightness(1.65) saturate(2.05);
            }
        }
        .ancient-glow-strong { animation: ancientGlowStrong 1.3s infinite ease-in-out; }

        /* 屬性+祝福：金光（顏色同單祝福，顯眼度＝雙詞綴） */
        @keyframes blessGlowStrong {
            0%, 100% {
                filter: drop-shadow(0 0 7px rgba(255, 224, 130, 1))
                        drop-shadow(0 0 18px rgba(250, 204, 21, 0.95))
                        drop-shadow(0 0 32px rgba(217, 160, 10, 0.65))
                        brightness(1.35) saturate(1.8);
            }
            50% {
                filter: drop-shadow(0 0 16px rgba(255, 240, 165, 1))
                        drop-shadow(0 0 34px rgba(253, 224, 71, 1))
                        drop-shadow(0 0 54px rgba(234, 180, 20, 0.9))
                        brightness(1.65) saturate(2.05);
            }
        }
        .bless-glow-strong { animation: blessGlowStrong 1.3s infinite ease-in-out; }

        /* 血盟敵人名稱：固定鮮紅 + 紅色脈動醒目特效 */
        @keyframes pledgeNameGlow {
            0%, 100% { text-shadow: 0 0 4px #ff0000, 0 0 8px #ff3b3b; }
            50%      { text-shadow: 0 0 10px #ff0000, 0 0 20px #ff2d2d, 0 0 32px #ff5555; }
        }
        .mob-name-pledge { color: #ff2d2d !important; animation: pledgeNameGlow 1s infinite ease-in-out; }

        /* 頭目名稱：保留等差顏色，疊加金色脈動專屬特效（與血盟不同） */
        @keyframes bossNameGlow {
            0%, 100% { text-shadow: 0 0 4px rgba(251,191,36,0.7), 0 0 8px rgba(245,158,11,0.5); letter-spacing: 0; }
            50%      { text-shadow: 0 0 12px rgba(251,191,36,1), 0 0 22px rgba(217,119,6,0.85); letter-spacing: 0.5px; }
        }
        .mob-name-boss { animation: bossNameGlow 1.6s infinite ease-in-out; }

        /* 三詞綴(屬性+遠古+祝福)：紅→藍→綠→琥珀 循環變換光（顏色不變，顯眼度最高，恆定強光+大光暈） */
        @keyframes triGlow {
            0%, 100% { filter: drop-shadow(0 0 11px rgba(239, 68, 68, 1))  drop-shadow(0 0 28px rgba(239, 68, 68, 0.85))  drop-shadow(0 0 52px rgba(239, 68, 68, 0.55))  brightness(1.75) saturate(2.2); }
            25%      { filter: drop-shadow(0 0 11px rgba(59, 130, 246, 1)) drop-shadow(0 0 28px rgba(59, 130, 246, 0.85)) drop-shadow(0 0 52px rgba(59, 130, 246, 0.55)) brightness(1.75) saturate(2.2); }
            50%      { filter: drop-shadow(0 0 11px rgba(34, 197, 94, 1))  drop-shadow(0 0 28px rgba(34, 197, 94, 0.85))  drop-shadow(0 0 52px rgba(34, 197, 94, 0.55))  brightness(1.75) saturate(2.2); }
            75%      { filter: drop-shadow(0 0 11px rgba(245, 158, 11, 1)) drop-shadow(0 0 28px rgba(245, 158, 11, 0.85)) drop-shadow(0 0 52px rgba(245, 158, 11, 0.55)) brightness(1.75) saturate(2.2); }
        }
        .tri-glow { animation: triGlow 2.4s infinite linear; }

        /* 🍎 2026-10-08（玩家回報「手機版點背包會跳回登入頁」，iPhone 14 Pro ＋ iOS 27.0.1）：iOS 的 WebKit 上（`<html data-ios>`，
           `src/ui/mobile.js` 的 `markIOS()` 蓋的）光芒**不播動畫、不用濾鏡**，改成圖示後面一圈靜態的放射漸層光暈。
           ⚠️ 原因：背包頁同時有十幾個會動的 `drop-shadow`（三詞綴是三層、半徑到 52px），iOS 27 的 WebKit 一畫就爆 —— 當機紀錄是
              遊戲分頁 1.7 GB `highwater`，錄影裡格子還在變色、半秒內分頁就被重新載入＝登出。圖示是透明底的 PNG，背景的漸層會從
              透明處透出來，看起來就是一圈光。
           ⚠️ 顏色照桌面那幾組 keyframes：單／雙／三詞綴照樣分得出來，三詞綴是多色光暈（不再四色循環）。
           ⚠️ 新增光芒 class（`getGlowClass()` 回的）要同時加進下面兩段 —— `loadtest.js` 從 `src/ui/colors.js` 推導清單比對。 */
        html[data-ios] :is(.bless-glow, .curse-glow, .ancient-glow, .anc-bless-glow, .ancient-glow-strong, .bless-glow-strong, .tri-glow, .legend-glow, .relic-glow) {
            animation: none; filter: none; border-radius: 50%;
        }
        html[data-ios] .bless-glow { background: radial-gradient(circle closest-side, rgba(250, 204, 21, .5), rgba(250, 204, 21, .16) 70%, transparent); }
        html[data-ios] .curse-glow { background: radial-gradient(circle closest-side, rgba(239, 68, 68, .55), rgba(239, 68, 68, .18) 70%, transparent); }
        html[data-ios] .ancient-glow { background: radial-gradient(circle closest-side, rgba(192, 132, 252, .5), rgba(168, 85, 247, .16) 70%, transparent); }
        html[data-ios] .ancient-glow-strong { background: radial-gradient(circle closest-side, rgba(192, 132, 252, .72), rgba(168, 85, 247, .3) 70%, transparent); }
        html[data-ios] .bless-glow-strong { background: radial-gradient(circle closest-side, rgba(255, 224, 130, .72), rgba(250, 204, 21, .3) 70%, transparent); }
        html[data-ios] .anc-bless-glow { background: radial-gradient(circle closest-side, rgba(255, 226, 150, .7), rgba(192, 132, 252, .5) 55%, rgba(147, 51, 234, .2) 80%, transparent); }
        html[data-ios] .tri-glow { background: radial-gradient(circle closest-side, rgba(239, 68, 68, .7), rgba(59, 130, 246, .5) 45%, rgba(34, 197, 94, .35) 70%, rgba(245, 158, 11, .15) 88%, transparent); }
        html[data-ios] .legend-glow { background: radial-gradient(circle closest-side, rgba(217, 138, 4, .55), rgba(180, 83, 9, .18) 70%, transparent); }
        html[data-ios] .relic-glow { background: radial-gradient(circle closest-side, rgba(56, 189, 248, .55), rgba(2, 132, 199, .18) 70%, transparent); }
        /* 📜 賦予卷的琥珀金紙：上面那條 `filter: none` 會連染色一起拿掉，這裡補回**靜態**的（不播動畫）。 */
        html[data-ios] img:is([src$="/賦予武器祝福卷軸.png"], [src$="/賦予盔甲祝福卷軸.png"], [src$="/賦予飾品祝福卷軸.png"]) {
            filter: sepia(1) saturate(4.5) hue-rotate(-5deg) brightness(.98);
        }

        /* ══════════════════════════════════════════════════════════════════
           📱 手機／平板版面（段 1，2026-08-27）
           設計與取捨見 docs/features/mobile-ui.md。

           ⚠️ 這一段**只新增，不改任何既有 selector**。桌機（≥1280px）一個像素
              都不能動 —— 而「順手把某個 class 改乾淨」正是最容易讓桌機跟著變的
              寫法。所以底下每一條不是 `@media` 內，就是只作用在新加的 id 上。

           ⚠️ 為什麼靠 `body[data-mpage]` 而不是搬 DOM：搬 DOM 會動到 46 處 inline
              onclick 的前提，而且 renderTabs() 每次重畫都要重新處理一次。
              CSS 決定誰顯示，DOM 原封不動 —— 所有既有的 render 函式都不必知道
              有手機版這回事。
           ══════════════════════════════════════════════════════════════════ */

        /* 底部導覽：桌機不存在。放在 @media 外面是為了「預設不顯示」，
           不是為了在桌機上排版 —— 桌機根本看不到它。 */
        #mobile-nav { display: none; }

        /* 🗑️ 平板專屬的斷點（768–1279px）移除：手機版現在是**唯一**的版面，
              所有寬度走同一條路，沒有「兩欄」這個中間態了。 */

        /* ── 手機與平板共用（<1280px）───────────────────────────────────
           ⚠️ 這一段是**平板那趟斷言逼出來的**：登入／創角的修法原本只寫在
              max-width:767px，但 900px 一樣塞不下 1000px 的面板（實測
              1000 vs 900、整頁 scrollWidth 950）。而觸控下限也不是手機專屬 ——
              768–1279px 涵蓋 iPad，那上面同樣是手指在按（實測加減鈕 30px）。 */
        /* 🔐 段 27：登入表單 ────────────────────────────────────────────────────
   ⚠️ 直式排列不是美觀偏好：先前兩個輸入框 ＋ 兩顆按鈕擠在一列，393px 下
      「註冊」被擠到下一行、而「記住帳號」那一句**橫向溢出卡片外**。
   ⚠️ 每一個可點的東西都 ≥44px（全 app 一致的觸控下限）。 */
.acc-form { display: flex; flex-direction: column; gap: 10px; }
.acc-in {
    width: 100%; min-height: 44px; font-size: 15px; padding: 0 12px;
    background: #0b1425; border: 1px solid #475569; border-radius: 8px;
    color: #e2e8f0; outline: none;
}
.acc-in:focus { border-color: #60a5fa; }
#acc-remember-row {
    display: flex; align-items: center; gap: 8px;
    min-height: 44px;                    /* 整列就是觸控目標，勾選框只有 16px */
    font-size: 14px; color: #cbd5e1; cursor: pointer;
}
#acc-remember-row input[type=checkbox] {
    width: 18px; height: 18px; flex: 0 0 auto; accent-color: #10b981;
}
.acc-btns { display: flex; gap: 10px; }
/* ⚠️ 段 54：按鈕換成 `.m-act` 之後這條也要跟著改 —— 它負責的是「撐滿整列」，
   而 `.m-act` 自己沒有 flex。忘了改的話兩顆按鈕會縮成內容寬度擠在左邊
   （第一版就是這樣，截圖才看出來）。 */
.acc-btns .m-act { flex: 1 1 0; }
#acc-hint { font-size: 11.5px; line-height: 1.6; color: #64748b; text-align: left; }
/* ⚠️ 訊息**靠左**，與 `#acc-hint` 一致 —— 帳號區塊本身是 `text-center`，
   而登入失敗那句會換行，置中的兩行讀起來像被切開的。
   ⚠️ `word-break: keep-all`：中文預設可以在**任何字之間**斷行，於是
   「註冊」會被拆成「註」／「冊」兩行（實測就是這樣）。keep-all 讓
   CJK 只在標點與空白處斷，那兩個字才會黏在一起。 */
#acc-msg { text-align: left; word-break: keep-all; line-height: 1.7; }

/* 👥 選角（多角色階段 2）────────────────────────────────────────────────
   ⚠️ 一張卡就是一顆按鈕（不是「卡片裡再放一顆進入鈕」）—— 整張都可以點，
      在手機上那是最大的觸控目標，而這一頁只有「挑一隻」這一件事可做。
   ⚠️ 高度給到 62px：三行字（名稱／等級職業／上次遊玩）＋ 觸控下限 44px。 */
#char-list { display: flex; flex-direction: column; gap: 8px; }
.charcard {
    display: flex; flex-direction: column; align-items: flex-start; gap: 1px;
    width: 100%; min-height: 62px; padding: 9px 13px; text-align: left;
    background: #16233c; border: 1px solid #475569; border-radius: 10px;
    color: #e2e8f0; cursor: pointer;
}
.charcard:hover { background: #1d2d4d; border-color: #64748b; }
/* 🟡 金框 ＝「這是伺服器上正在使用的那一隻」。與技能圖鑑同一個語彙。 */
.charcard.on { border-color: #eab308; background: #1e293b; }
.charcard.new { border-style: dashed; background: transparent; color: #94a3b8; }
.cc-name { font-size: 15px; font-weight: 700; }
.cc-sub  { font-size: 12px; color: #94a3b8; }
/* ⚠️ 絕對定位在右上角，不佔第三行 —— 佔的話卡片會再高一截，三張就多 30px。 */
.cc-when { font-size: 11px; color: #64748b; align-self: flex-end; margin-top: -14px; }
.cc-full { font-size: 12px; color: #64748b; text-align: center; padding: 8px 0; }

/* 🗑️ 刪角（階段 6）
   ⚠️ 刪除鈕是卡片的**兄弟**（`<button>` 不能巢狀），所以要一層 flex 把兩者排成
      一列，卡片吃掉剩下的寬度。
   ⚠️ 用**警示色而不是主色**，而且刻意放在最右邊、只有一個圖示 —— 它與「進入
      遊戲」在同一列上，份量要明顯比較輕（同段 10 把賣廢品做成警示色浮動鈕的判準）。*/
/* ⚠️ 刪除鈕的寬度與間距寫成變數：「新增角色」那張卡沒有刪除鈕，得自己空出
      同樣的一欄才對得齊（第一版它整整多出 52px，只有截圖看得出來）。
      寫死兩次的話改了一邊就會歪掉。 */
#char-list { --cc-del-w: 44px; --cc-gap: 8px; }
.cc-row { display: flex; gap: var(--cc-gap); align-items: stretch; }
.cc-row .charcard { flex: 1 1 auto; min-width: 0; }
/* ⚠️ `width: auto` 不可省：`.charcard` 是 `width: 100%`，而 100% ＋ margin-right
      的結果是**溢出**不是縮短 —— 第一版只加 margin，量到的右緣還是差 52px。
      這一欄是 `flex-direction: column`，auto 之後由 align-items: stretch 撐滿。 */
#char-list > .charcard.new {
    width: auto;
    margin-right: calc(var(--cc-del-w) + var(--cc-gap));
}
.cc-del {
    flex: 0 0 auto; width: var(--cc-del-w); min-height: 62px;   /* 44px＝全 app 的觸控下限 */
    background: transparent; border: 1px solid #475569; border-radius: 10px;
    color: #94a3b8; font-size: 16px; cursor: pointer;
}
.cc-del:hover { border-color: #b45309; color: #fca5a5; background: rgba(69, 26, 3, .5); }

/* 刪除確認：要打出角色名稱。⚠️ 它整個取代選角清單，所以不必處理層級與暗幕。 */
.cc-confirm { display: flex; flex-direction: column; gap: 9px; }
.cc-warn { font-size: 15px; font-weight: 700; color: #fca5a5; }
.cc-note { font-size: 12px; line-height: 1.7; color: #94a3b8; text-align: left; }
.cc-msg  { font-size: 12px; min-height: 1.1rem; color: #fca5a5; text-align: left; }
.cc-btns { display: flex; gap: 10px; }
.cc-btns .m-act { flex: 1 1 0; }

/* 🔐 段 27：被踢下線／登入過期的畫面 ────────────────────────────────────
   ⚠️ `position: fixed` 是相對**視窗**的，而版面置中在 500px —— 但這一層
      刻意鋪滿整個視窗（它要蓋掉一切，包含桌機上那一欄以外的背景）。
      這是本專案唯一一個**應該**滿版的 fixed 元件，其餘四個見 CLAUDE.md。 */
#session-ended {
    position: fixed; inset: 0; z-index: 9999;
    display: flex; align-items: center; justify-content: center;
    padding: 24px;
    background: rgba(2, 6, 16, .92);
    backdrop-filter: blur(3px);
}
#session-ended.hidden { display: none; }
#session-ended-card {
    width: 100%; max-width: 420px;
    background: #0f1a2e; border: 1px solid #f59e0b;
    border-radius: 14px; padding: 22px 20px;
    box-shadow: 0 18px 50px rgba(0, 0, 0, .7);
    text-align: center;
}
#session-ended-title {
    font-size: 18px; font-weight: 700; color: #fbbf24; margin-bottom: 10px;
}
#session-ended-body {
    font-size: 13.5px; line-height: 1.7; color: #cbd5e1; margin-bottom: 18px;
}
#session-ended-btn {
    width: 100%; min-height: 44px;      /* 觸控下限，同全 app */
    font-size: 14px; font-weight: 700; color: #06121f;
    background: #f59e0b; border: 0; border-radius: 10px; cursor: pointer;
}
#session-ended-btn:active { background: #d97706; }

@media all {   /* 段 5：手機版是唯一的版面 */
            /* `.btn` 是 padding .5rem 1rem ＋ 16px 字 ≈ 40px，差一點；
               創角的屬性加減鈕 `px-3 py-0.5` 更只有 30px，而那是新玩家
               第一個要按、還要連按很多下的東西。 */
            .btn { min-height: 44px; }

            /* 1000px 寫死 ＋ absolute 置中，在窄視窗會直接溢出。
               改成 static 之後它就是 body 那個 flex 容器的一般子項。 */
            #creation-screen {
                position: static;
                width: 100%;
                max-width: 100%;
                max-height: var(--app-h, 100dvh);
                overflow-y: auto;
                overflow-x: hidden;
            }
        }

        /* ── 手機：<768px ──────────────────────────────────────────────── */
        @media all {   /* 段 5：手機版是唯一的版面 */
            /* ── 📏 可視高度（2026-09-07，玩家回報「最下面的選單會被截掉」）──
               `100dvh` 本來就比 `100vh` 好（網址列會吃掉 vh），但**有些行動
               瀏覽器（in-app WebView）把 dvh 報成整個螢幕、沒扣掉自己的工具列** ——
               於是版面比看得到的區域高，最後一格（底部導覽）落在畫面外，
               而 `#game-screen` 是 `overflow: hidden`，捲也捲不到。
               所以真正的高度是 `src/ui/mobile.js` 的 `syncAppHeight()` **量**出來的。
               ⚠️ `var(--app-h, 100dvh)` 的退路正好是今天的行為 —— 那一行沒跑到
                  （或量不到）時，畫面仍然是對的。
               ⚠️ `--vp-cut` ＝ dvh 多報了多少。報得對的瀏覽器上它是 `0px`，
                  所以底下每一個 `bottom: var(--vp-cut)` 在那些機器上完全沒有作用。 */
            :root {
                --vp-cut: max(0px, calc(100dvh - var(--app-h, 100dvh)));
            }
            /* ⚠️ `!important` **不可省**，而且這才是玩家看到的那個 bug 的根：
                  `<body class="h-screen …">` 帶著 Tailwind 的 `.h-screen`
                  ＝ `height: 100vh`，而 class（0-1-0）**贏過**這裡的型別選擇器
                  （0-0-1）—— 也就是說上面那句「100dvh 而不是 100vh」在 body 上
                  **從來沒有生效過**。而 `100vh` 在行動瀏覽器上是「工具列收起來時」
                  的高度（大視窗），比看得到的區域高，底部導覽就落在畫面外。
                  ⚠️ 桌機上 `100vh`／`100dvh`／量到的 `innerHeight` 三個是同一個值，
                     所以這個 bug 在開發機上**永遠看不到** —— 只有真機會現形。 */
            html, body { height: var(--app-h, 100dvh) !important; }
            /* 🗑️ 2026-09-07：`body { padding: 0 }` 移除 —— 它是**死的**。
               body 上的 `.p-4`（0-1-0）一樣贏過型別選擇器，所以手機上 body
               一直都有 16px 內距（實測 `#game-screen` 寬 361 而不是 393）。
               ⚠️ 刻意**不改成 `!important` 把它變成真的** —— 段 5 之後那 16px
                  是版面的一部分（500px 那一欄的置中算式、`right: max(14px, …)`
                  都是在有它的前提下量出來的）。留著一條看起來有作用、其實沒有
                  的規則才是問題。 */

            #game-screen {
                flex-direction: column;
                gap: 0.5rem;
                max-width: none;
                height: var(--app-h, 100dvh);
                padding: 0.5rem 0.5rem 0;
                /* 底部導覽是 fixed，內容要讓出它的高度 ＋ iPhone 的 home indicator */
                padding-bottom: calc(58px + env(safe-area-inset-bottom));
                overflow: hidden;
            }

            /* 三欄全部改成全寬。⚠️ `w-[340px]` / `w-[380px]` 是 Tailwind 產的
               inline utility，權重與這裡相同而且排在前面，所以要 !important。 */
            #col-left, #col-center, #col-right {
                width: 100% !important;
                flex: 1 1 auto;
                min-height: 0;
                gap: 0.5rem;
            }

            /* 狀態面板永遠看得到（HP／MP／EXP／增益）——放置遊戲的玩家大半時間在看它。
               ⚠️ 它不可以跟著頁面被藏起來，所以下面每一個 data-mpage 都保留 #col-left，
                  只切換 #panel-auto。 */
            #col-left  { flex: 0 0 auto; }

            /* ── 依目前頁面決定誰顯示 ──
               冒險＝中欄；背包／角色／社群＝右欄；設定＝左欄的自動化面板。 */
            /* ⚠️ 一律寫成 `:not(...)`，不要寫成「是這一頁才藏」——
                  `data-mpage` 沒設上時（初始化沒跑到）前者仍有正確的預設，
                  後者會讓三欄同時顯示，那正是「手機版沒生效」的樣子。 */
            #panel-auto { display: none; }
            body[data-mpage="set"] #panel-auto { display: flex; flex: 1 1 auto; min-height: 0; }
            body:not([data-mpage="bag"]):not([data-mpage="char"]):not([data-mpage="soc"]) #col-right { display: none; }
            body[data-mpage="bag"]  #col-center,
            body[data-mpage="char"] #col-center,
            body[data-mpage="soc"]  #col-center,
            body[data-mpage="set"]  #col-center { display: none; }

            /* 分頁按鈕只留當前這一格的。10 個按鈕擠在 2 欄的 grid 上，
               手機一次只需要其中 2–4 個。 */
            #tabbar { grid-template-columns: repeat(2, minmax(0, 1fr)); }
            body[data-mpage="bag"]  #tabbar button:not([data-mgroup="bag"]),
            body[data-mpage="char"] #tabbar button:not([data-mgroup="char"]),
            body[data-mpage="soc"]  #tabbar button:not([data-mgroup="soc"]) { display: none; }
            /* 角色只有兩格（能力／技能），排成一列比較不浪費 */
            body[data-mpage="char"] #tabbar { grid-template-columns: repeat(2, minmax(0, 1fr)); }
            /* 🗑️ 段 10：#inv-tools 整列移除（見 index.html），這幾條沒有對象了 */

            /* 怪物列：三格並排在 393px 下每格只有 ~120px，圖與名稱都還放得下，
               但 185px 的最小高度會把日誌擠光。 */
            .mob-target { min-height: 120px; padding: 0.4rem; }
            #battle-view { padding: 0.5rem; }
            #mob-list { gap: 0.4rem; }

            /* 系統日誌的 h-1/3 在手機上只剩 5 行，等於沒有。
               ⚠️ 這裡先讓兩份日誌各自可捲、不再固定比例；合併成分段切換是段 2。 */
            #combat-log-panel, #sys-log-panel { flex: 1 1 auto; min-height: 0; height: auto; }


            /* ── 底部導覽 ── */
            #mobile-nav {
                display: flex;
                position: fixed;
                left: 0; right: 0; bottom: 0;
                z-index: 40;
                background: #0f172a;
                border-top: 1px solid #334155;
                padding-bottom: env(safe-area-inset-bottom);
            }
            #mobile-nav button {
                flex: 1;
                min-height: 58px;
                display: flex;
                flex-direction: column;
                align-items: center;
                justify-content: center;
                gap: 2px;
                background: none;
                border: 0;
                color: #A3AAB8;
                font-family: inherit;
                font-size: 11px;
                cursor: pointer;
                -webkit-tap-highlight-color: transparent;
            }
            /* 🎨 2026-09-27：圖示從 emoji 換成設計稿的線條圖示（sprite，吃 currentColor），
               顏色照設計師的建議：一般 #A3AAB8、選取 #F6B73C（原本 #94a3b8／#fbbf24，差很小）。
               🗑️ `#mobile-nav button span { font-size: 19px }` 拿掉 —— 那是給 emoji 的，現在沒有 span 了。 */
            #mobile-nav button .uic { width: 22px; height: 22px; }
            #mobile-nav button[aria-current="page"] { color: #F6B73C; }
        }

        /* ── 段 2：日誌分段切換 ＋ 抽屜的暗幕（2026-08-27）────────────────
           同樣只新增、不改既有 selector。
           🗑️ 2026-09-07：`#m-fab`／`#m-sheet`（快速換裝）已整套移除。 */
        #m-logtabs, #m-scrim { display: none; }

        @media all {   /* 段 5：手機版是唯一的版面 */
            /* ── 日誌分段切換 ── */
            /* 段 10：與 #tabbar 同一套語彙（demo 的 .segbar）—— 底線分頁，
               不是圓角填色按鈕。同一個畫面上兩種分頁樣式會讓人以為它們是
               不同層級的東西，而它們其實一樣。 */
            #m-logtabs {
                display: flex;
                flex: 0 0 auto;
                gap: 0;
                background: #0f172a;
                border-bottom: 1px solid #334155;
                overflow: hidden;
            }
            #m-logtabs button {
                position: relative;
                flex: 1;
                min-height: 44px;
                background: none;
                border: 0;
                border-bottom: 2px solid transparent;
                border-radius: 0;
                color: #94a3b8;
                font-family: inherit;
                font-size: 12.5px;
                font-weight: 700;
                cursor: pointer;
            }
            #m-logtabs button[aria-current="true"] { color: #fbbf24; border-bottom-color: #fbbf24; }

            /* ── 📜 移植 21-D：戰鬥日誌的來源過濾 ──
               ⚠️ 擠進分頁那一列（`flex: 0 0 auto`），所以**不多佔一個像素的高度** ——
                  自己一列的話日誌的可視行數會直接少掉三行多。
               ⚠️ 每一顆仍然是 **44×44**（全 app 的觸控下限）：393px 下四顆吃掉 176px，
                  兩個分頁還各有 108px，「⚔️ 戰鬥」四個字綽綽有餘。 */
            #log-filter { display: flex; flex: 0 0 auto; }
            /* 切到系統日誌時整塊收起來 —— 那一份沒有這四種來源，
               而且兩個分頁可以拿回全部寬度。 */
            body[data-mlog="s"] #log-filter { display: none; }
            #log-filter button {
                flex: 0 0 auto;
                min-width: 44px; min-height: 44px;
                padding: 0 2px;
                background: none; border: 0; border-bottom: 2px solid transparent;
                border-radius: 0;
                color: #64748b;                      /* 關掉時：暗 */
                font-family: inherit; font-size: 11px; font-weight: 700;
                cursor: pointer;
            }
            /* 開著（＝這一類看得到）：亮起來。⚠️ 用**亮／暗**而不是刪除線或 ✕ ——
               四顆裡通常四顆都開著，四個符號只是噪音（同段 41 未學會的不掛 ✕）。 */
            #log-filter button.on { color: #e2e8f0; border-bottom-color: #475569; }
            /* 被關掉的那一類：用 CSS 藏起來，**不是不寫進 DOM** ——
               重新點亮時那一段歷史還在（上游的做法）。 */
            #combat-log.cf-hide-player [data-src="player"],
            #combat-log.cf-hide-enemy  [data-src="enemy"],
            #combat-log.cf-hide-summon [data-src="summon"],
            #combat-log.cf-hide-pet    [data-src="pet"] { display: none; }
            /* 沒在看的那一側有新訊息時點一個紅點 */
            #m-logtabs i {
                position: absolute; top: 7px; right: 9px;
                width: 8px; height: 8px; border-radius: 999px; background: #ef4444;
                display: none;
            }
            #m-logtabs i.on { display: block; }

            /* 兩份日誌一次只顯示一份，而且吃滿剩下的空間。
               ⚠️ 分頁標題（「戰鬥日誌」／「系統與物品日誌」）在手機上是重複資訊 ——
                  上面的分段切換已經寫著同一件事，而那一列佔掉約 48px。 */
            #combat-log-panel > .panel-header,
            #sys-log-panel   > .panel-header { display: none; }
            #sys-log-panel { height: auto; }
            /* ⚠️ 寫成 `:not([data-mlog="s"])` 而不是 `[data-mlog="c"]`：
                  這樣**沒有那個 attribute 時也有正確的預設**（顯示戰鬥日誌）。
                  依賴 attribute 一定存在是危險的 —— 實測 smoketest 的 harness 會
                  覆蓋掉 boot.js 的 window.onload，於是 initMobileNav() 根本沒跑，
                  兩份日誌同時顯示、各佔一半（日誌只剩 62px）。真機上只要有任何
                  一條路徑沒跑到初始化，就會是同一個樣子，而且不會有錯誤訊息。 */
            body:not([data-mlog="s"]) #sys-log-panel,
            body[data-mlog="s"] #combat-log-panel { display: none; }
            body:not([data-mlog="s"]) #combat-log-panel,
            body[data-mlog="s"] #sys-log-panel { flex: 1 1 auto; min-height: 0; }
            #combat-log, #sys-log { padding: 0.75rem; }

            /* ⚠️ 在城鎮時 `renderMapView()` 會把戰鬥日誌**整塊藏起來**（既有行為，
                  桌機也一樣）。這時分段切換就是指著一個空的區域 —— 收起來，
                  並讓系統日誌吃滿。沒有這兩條的話，玩家在村莊點「⚔️ 戰鬥」
                  會看到一片空白，而那不是壞掉，是那份日誌本來就不存在。 */
            #col-center:has(#combat-log-panel.hidden) #m-logtabs { display: none; }
            #combat-log-panel.hidden + #sys-log-panel { display: flex; flex: 1 1 auto; min-height: 0; }

            /* 🗑️ 2026-09-07：兩顆浮動鈕（段 56 的快速換裝 🎒、這次的賣廢品 🗑）
               都移除了，所以段 40 的 `--m-fab-clear` **整個沒有對象**了 ——
               那個下方留白是「為了讓內容捲得過浮動鈕」才存在的。
               冒險頁的三個容器與背包頁的四個分頁**各拿回 72px**。
               ⚠️ 浮動鈕是 `position: fixed`、不佔版面也就不知道底下有什麼；
                  捲得過去不等於按得到。這是這個 app 不再用浮動鈕的理由。 */

            /* ⚠️ 段 12：`position: fixed` 是相對**視窗**，而段 5 之後版面置中在 500px ——
                  只寫 `inset: 0` 的話桌機上抽屜與暗幕會橫跨整個視窗，那一欄反而變成
                  一條浮在中間的東西（玩家截圖就是這樣）。FAB 早就處理過同一件事
                  （`right: max(14px, calc(50% - 236px))`），抽屜漏掉了。
                  ⚠️ `max(0px, …)` 不可省：窄螢幕時 50% - 250px 是負的，抽屜會超出畫面。 */
            #m-scrim {
                display: block;
                position: fixed; top: 0; bottom: var(--vp-cut); z-index: 46;
                left: max(0px, calc(50% - 250px)); right: max(0px, calc(50% - 250px));
                background: rgba(2,6,23,.6);
                opacity: 0; pointer-events: none; transition: opacity .22s;
            }
            body[data-msheet]:not([data-msheet="0"]) #m-scrim { opacity: 1; pointer-events: auto; }

            /* 🗑️ 2026-09-07：快速換裝的抽屜（`#m-sheet`／`#m-dps`／`.m-gear`／
               `.m-empty`）整套移除。⚠️ 拉柄／標題／關閉鈕那三條共用樣式**留著** ——
               切換地點與掛機收穫兩個 drawer 還用著它們（下面那幾條的 selector
               已經拿掉 `#m-sheet-*` 那一半）。 */
            #m-mapsheet-grip, #m-awaysheet-grip { width: 38px; height: 4px; border-radius: 999px; background: #475569; margin: 8px auto 4px; flex: 0 0 auto; }
            #m-mapsheet-head, #m-awaysheet-head {
                display: flex; align-items: center; justify-content: space-between;
                flex: 0 0 auto; padding: 6px 14px 10px; border-bottom: 1px solid #334155;
                font-size: 15px; font-weight: 700;
            }
            #m-mapsheet-head small, #m-awaysheet-head small { display: block; font-size: 11px; color: #94a3b8; font-weight: 400; margin-top: 2px; }
            #m-mapsheet-head button, #m-awaysheet-head button {
                background: none; border: 0; color: #94a3b8; font-size: 20px; cursor: pointer;
                width: 44px; height: 44px;
            }
        }

        /* ── 段 3：登入／創角畫面（2026-08-27）─────────────────────────────
           ⚠️ 這兩個畫面是 `#game-screen` 的**兄弟節點**，段 1／2 的 @media 一條都
              沒碰到 —— 而它們是玩家看到的**第一個**畫面。實測 393px 下
              登入畫面 1000px、創角面板 934px，整頁 scrollWidth 697。
              進不了遊戲的話，遊戲內做得再好都沒意義。

           ⚠️ 這裡用 `#id` 選擇器就夠了，不必 `!important`：
              Tailwind 的 `w-[1000px]` / `h-[480px]` 是 class（0,1,0），
              id 是（1,0,0），一定贏。 */
        @media all {   /* 段 5：手機版是唯一的版面 */
            /* 流體化與捲動在上面那個 <1280px 的區塊；這裡只做手機專屬的收斂 */
            #creation-screen { padding: 0.9rem; gap: 0.9rem; }
            #creation-screen h1 { font-size: 1.6rem; margin-bottom: 0.25rem; }
            #creation-screen > div:first-child { padding-bottom: 0.75rem; }
            /* my-12（3rem 上下）在手機上把登入表單推到摺線下 */
            #main-menu { margin-top: 1rem; margin-bottom: 1rem; gap: 0.9rem; }

            /* ── 段 48：還沒登入時，副標下面是「兩條線夾著一片空白」──────────
               `#btn-new`（新遊戲）要登入後才出現，所以第一個畫面上它是 hidden。
               於是 `#account-box` 的 `border-t` 隔開的是**標題那條 border-b** ——
               兩條一模一樣的分隔線，中間 38px 什麼都沒有（實測 274→312），
               看起來像畫面壞掉，而這是玩家看到的**第一個**畫面。
               ⚠️ 用**一般**兄弟選擇器（`~`）而不是相鄰（`+`）—— 多角色階段 2 在
                  兩者之間插了 `#char-box`，`+` 就再也選不到了。⚠️ 而症狀完全
                  不像「選擇器壞了」：畫面上只是那條線又出現、上面多了一片空白，
                  沒有任何錯誤訊息（`#char-box` 在未登入時是 `hidden`，
                  肉眼看那兩個仍然是相鄰的）。
               ⚠️ 登入之後按鈕會出現，這條就自動失效、分隔線回來 —— 那時它
                  真的隔開了兩個東西。 */
            #btn-new.hidden ~ #account-box {
                border-top: 0;
                margin-top: 0;
                padding-top: 0;
            }

            /* 創角：三欄（立繪｜選職業｜配點）改成單欄堆疊 */
            #creation-panel { flex-direction: column; align-items: stretch; gap: 0.9rem; }
            /* ── 段 48：立繪的框跟著圖的比例，不再是寫死的一條 ─────────────
               原本是 `height: 170px`：16 張立繪全是 **1920×1973（比例 0.97）**，
               塞進 330×170（比例 1.94）的框加 `object-cover`，只露得出 **50%**
               —— 騎士被切在腰部，劍看不到，而框上還有一圈厚重的金邊，
               整個畫面最該打動人的元素變成最沒份量的那個。

               ⚠️ 用 `aspect-ratio` 而不是再寫死一個 px 高度：框的寬度會隨視窗變
                  （393px 下 330、桌機那欄 500px 下 468），寫死高度的話**露出的比例
                  會跟著寬度變** —— 實測 280px 在 330 寬時露 83%，到 468 寬只剩 58%。
                  綁比例才會在每個寬度上都是同一個取景。
               ⚠️ 1.17 ≈ 1920 ÷ (1973 × 0.83)，也就是「露出圖的 83%」。
                  再高就開始擠掉「1. 選擇職業」，再矮劍尖就會被切掉。 */
            #create-portrait { width: 100%; height: auto; aspect-ratio: 1.17 / 1; }
            #create-right    { height: auto; }
            #create-cols     { flex-direction: column; height: auto; gap: 0.9rem; }
            #create-classbox { width: 100%; height: auto; }
            #stat-allocation { height: auto; }
            /* 職業說明在窄欄裡會變成很長一條，給它自己的高度上限 */
            #class-desc { max-height: 8.5rem; }

            /* ── 段 54：把登入／創角拉回 app 現在的元件系統 ────────────────
               ⚠️ 這兩個畫面是 `#game-screen` 的**兄弟節點**，段 30–53 的元件改造
                  （字級階層、`.m-act` 按鈕、琥珀選中色）一條都碰不到它們 ——
                  於是玩家看到的**第一、第二個畫面**與遊戲內是兩種視覺語言。

               ① 名稱輸入框先前只有 **32px**，而全 app 的觸控下限是 44px。
                  它還是**必填而且之後不能改**的欄位（`validatePlayerName()` 是
                  唯一的把關點），是創角流程裡最重要的一個輸入。 */
            #create-name { min-height: 44px; font-size: 14px; }

            /* ② 字級：創角畫面先前有 **48 處 16px**，而遊戲內的內文是 13px
                  （段 30 日誌、段 15 NPC 清單、段 43 數值）。這正是段 4e 記過的
                  「資訊密度」——「不溢位、觸控達標、比例達標」可以全部通過而
                  畫面仍然很糟。
               ⚠️ 用 `#id .class`（1,1,0）蓋掉 Tailwind 的 `.text-base`（0,1,0），
                  不用 `!important` —— 同段 3 那條「id 一定贏」的理由。
               ⚠️ 只調**字級**，不動任何間距／版面：那是另一件事，混在一起會分不清
                  是誰造成的。 */
            #creation-panel { font-size: 13px; }
            #creation-panel .text-base { font-size: 13.5px; }
            #creation-panel .text-lg   { font-size: 15px; }
            /* ⚠️ `剩餘: N` 也收到 15px。它值得強調（那是「開始冒險」亮不亮的原因），
               但強調靠的是粗體與顏色，不是比全 app 內文大一號 —— 而且段 48 已經
               把原因直接寫在按鈕上方了，這個數字不再是唯一的線索。 */
            #creation-panel .text-xl   { font-size: 15px; }
            #creation-panel .text-sm   { font-size: 12.5px; }
            /* 副標在**標題列**裡（兩個畫面共用），不在 #creation-panel 底下，
               所以要單獨點名。⚠️ 標題維持 25.6px —— 階層靠它與內文的落差建立，
               段 30 的教訓正好相反：內文比標題大才是問題。 */
            #creation-screen > div:first-child p { font-size: 13.5px; }

            /* ③ 選中的職業改用**琥珀**，不是藍色。
               ⚠️ 琥珀是全 app 的「選中」語彙（`.seg.active`、底部導覽、分頁底線、
                  怪物卡選中，styles.css 裡 31 處），而藍色在這個 app 裡沒有第二個
                  「選中」的用途。
               ⚠️ **只改創角這一處**，不動全域的 `.btn.active` —— 那個 class 同時被
                  `switchTab()` 的分頁列用著（`nav.js`），改全域會波及到它。 */
            #create-classbox .btn.active {
                background: #14213a; border-color: #fbbf24; color: #fbbf24;
            }
            /* 🗑️ 段 9 移除城鎮的留白微調：那幾條是在「桌機 DOM ＋ CSS 重塑」時期
                  把 p-6 收成 0.75rem。段 9 之後村莊是手機專屬的 DOM，尺寸直接
                  寫在下面 #town-view / .npcc / #town-interaction-container。 */
        }

        /* ── 段 49：主控制列與浮動傷害數字 ─────────────────────────────────
   ⚠️ 主控制列刻意夾在地圖標題與內容之間：它講的是「現在是什麼狀態」，
      而那正好是標題（在哪裡）與內容（發生什麼）之間的那一層。 */
@media all {
    #hunt-ctl {
        display: flex; align-items: center; justify-content: space-between; gap: 10px;
        padding: 7px 12px; border-bottom: 1px solid #22304a;
        background: rgba(15, 23, 42, .55);
    }
    #hunt-ctl-state { display: flex; align-items: center; gap: 8px; font-size: 13px; }
    #hunt-ctl .dot {
        width: 9px; height: 9px; border-radius: 50%; flex: 0 0 auto;
        background: #64748b; display: block;
    }
    /* 🟢 狩獵中：**會跳的那顆點就是「它在動」的證據** —— 動畫停了就代表這一格
       沒有再被重畫。文字寫「狩獵中」誰都會寫，會動的點是騙不了的。 */
    #hunt-ctl[data-state="live"] .dot { background: #34d399; animation: huntbeat 1.4s ease-in-out infinite; }
    #hunt-ctl[data-state="live"] #hunt-ctl-t { color: #6ee7b7; font-weight: 700; }
    #hunt-ctl[data-state="idle"] #hunt-ctl-t { color: #94a3b8; }
    #hunt-ctl[data-state="dead"] .dot { background: #f87171; }
    #hunt-ctl[data-state="dead"] #hunt-ctl-t { color: #fca5a5; font-weight: 700; }
    /* ⚠️ 斷線要**醒目**：畫面會停在最後一次同步的樣子，看起來卻像還在跑。 */
    #hunt-ctl[data-state="off"] { background: rgba(69, 10, 10, .55); border-bottom-color: #7f1d1d; }
    #hunt-ctl[data-state="off"] .dot { background: #ef4444; }
    #hunt-ctl[data-state="off"] #hunt-ctl-t { color: #fca5a5; font-weight: 700; }
    /* ⚠️ **不要**在這裡寫 min-height：`.m-act` 的 44px 是全 app 的觸控下限，
       而 `#hunt-ctl-btn` 是 id 選擇器（1,0,0）會直接蓋掉它 —— 第一版寫了 36px，
       等於在最主要的那顆按鈕上破了那條線。 */
    #hunt-ctl-btn { padding: 0 16px; font-size: 13.5px; }

    @keyframes huntbeat {
        0%, 100% { transform: scale(1);   opacity: 1; }
        50%      { transform: scale(1.6); opacity: .45; }
    }

    /* 💥 浮動傷害數字。⚠️ `position: fixed` ＋ 生成當下量到的座標 ——
       放進怪物卡片的話，renderAll() 的 replaceChild 會在最後一擊時把它刪掉。
       ⚠️ 浮層自己 `pointer-events: none`，不可以擋到底下的怪物卡片。 */
    #dmg-layer { position: fixed; inset: 0; pointer-events: none; z-index: 60; }
    .dmg-float {
        position: fixed; transform: translate(-50%, -50%);
        font-weight: 800; font-size: 17px; line-height: 1;
        text-shadow: 0 2px 4px rgba(0, 0, 0, .95), 0 0 2px rgba(0, 0, 0, .9);
        pointer-events: none; white-space: nowrap;
        animation: dmgfloat 900ms ease-out forwards;
    }
    /* 🖼️ 來源圖示（召喚術／迷魅術／項圈／法術各自的圖示，2026-09-06）。
       ⚠️ `vertical-align: -3px` 是為了跟數字的基線對齊 —— 圖示是方的、數字有
          下伸部，直接 baseline 對齊會看起來浮在上面。
       ⚠️ 不要用 `height: 1em`：`.crit` 的字級是 23px 而一般是 17px，圖示會跟著
          變大變小，同一場戰鬥裡看起來像兩種東西。 */
    /* 🐾 寵物分頁：名稱前那張寵物圖（怪物圖的 d6 幀，32px 素材放到 28px） */
    /* ⚠️ Tailwind 的 preflight 把 img 設成 block —— 不寫 inline-block 的話圖會自己占一行，名字掉到下面（實測踩到） */
    .pet-pic { display: inline-block; width: 28px; height: 28px; object-fit: contain; vertical-align: middle; margin-right: 4px; image-rendering: pixelated; }
    /* 🐾 寵物：裁到外框的小圖，畫大一點才認得出是誰；怪物圖多半是深色的，墊一塊淺底不然在深色的怪物卡上看不見（實測狼是一團黑）。
       名冊那張 `.pet-pic` 同一塊底。⚠️ `display: inline-block`：Tailwind 的 preflight 把 img 設成 block，圖會疊到數字**上方**（實測） */
    .dmg-float .dmg-icon.pet, .pet-pic { background: rgba(226, 232, 240, .92); border-radius: 6px; padding: 1px; box-sizing: border-box; }
    .dmg-float .dmg-icon.pet { width: 24px; height: 24px; vertical-align: -6px; }
    .dmg-float .dmg-icon {
        display: inline-block;
        width: 18px; height: 18px; vertical-align: -3px; margin-right: 3px;
        image-rendering: pixelated;   /* 圖示是 32px 的點陣素材，放大時平滑會糊 */
        filter: drop-shadow(0 1px 2px rgba(0, 0, 0, .9));
    }
    .dmg-float.out  { color: #fde68a; }
    .dmg-float.crit { color: #fb923c; font-size: 23px; }
    .dmg-float.in   { color: #f87171; }
    /* 💨 打空的（2026-09-09）：灰白、比傷害小一號。
       ⚠️ 刻意**比數字弱** —— 沒打中每個 tick 都可能發生，而傷害數字才是玩家在追的。
          用同樣的份量會讓真正有意義的那幾個被淹掉。
       ⚠️ 但仍然要看得清楚（`text-shadow` 沿用），不然等於沒做。 */
    .dmg-float.miss { color: #cbd5e1; font-size: 14px; font-weight: 700; opacity: .92; }

/* ✏️ 2026-09-28 寵物改名輸入框（玩家「輸入框做漂亮一點」）：深底＋細框（同血盟名稱框），聚焦時琥珀色框；
   `flex: 1 1 0` ＋ `min-width: 0` 讓它在窄視窗吃掉剩下的寬度而不把「確定／取消」擠出去。 */
.pet-rename { flex: 1 1 0; min-width: 0; background: #0f172a; border: 1px solid #475569; border-radius: 0.375rem;
              padding: 0.25rem 0.5rem; font-size: 0.8125rem; line-height: 1.25rem; color: #f1f5f9; outline: none; }
.pet-rename::placeholder { color: #64748b; }
.pet-rename:focus { border-color: #f59e0b; box-shadow: 0 0 0 2px rgba(245, 158, 11, 0.25); }

/* 🧪 2026-09-28 寵物喝藥門檻（玩家「外觀跟自動設定的選項一樣」）：照 `#panel-auto .slot`／`.ssel` 的形狀與數字抄 ——
   標題一列、整寬的下拉獨立一列（44px 深底細框圓角 8）、底下一行 10px 的說明。⚠️ 那幾條是 `#panel-auto` 作用域，這裡另寫一份，改一邊記得改另一邊。 */
.pet-pot { margin-top: 6px; padding-top: 8px; border-top: 1px solid #22304a; }
.pet-pot .st { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 7px; }
.pet-pot .st b { font-size: 12.5px; font-weight: 700; }
.pet-pot .ssel { display: block; width: 100%; min-height: 44px; font-size: 13px; padding: 0 10px;
                 background: #0b1425; border: 1px solid #475569; border-radius: 8px; color: #e2e8f0; outline: none; }
.pet-pot small { display: block; color: #7c8aa3; font-size: 10px; margin-top: 4px; }

    @keyframes dmgfloat {
        0%   { opacity: 0; transform: translate(-50%, -50%) scale(.7); }
        18%  { opacity: 1; transform: translate(-50%, -62%) scale(1.08); }
        100% { opacity: 0; transform: translate(-50%, -150%) scale(1); }
    }

    /* ⚠️ 數字本身是**資訊**，減少動態時仍然要看得到 —— 只拿掉飄移，不拿掉它。 */
    @media (prefers-reduced-motion: reduce) {
        #hunt-ctl[data-state="live"] .dot { animation: none; }
        .dmg-float { animation: dmgfade 900ms linear forwards; }
        @keyframes dmgfade { 0%, 70% { opacity: 1; } 100% { opacity: 0; } }
    }
}

/* ── 段 51：低血警示（03 的選項 D）──────────────────────────────────
   ⚠️ 為什麼是這個做法而不是「把 HP／MP 搬到戰鬥現場」：實測搬下來**不會省空間**
      （狀態面板的高度是頭像 84px 決定的，三條 bar 是白搭在上面的），自己一列
      淨 +23px、併進主控制列淨 +9px，而且要把頭像縮回 56px —— 那會部分推翻段 34–35。
      真正沒被滿足的需求是「快死了要看得到」，而那件事在段 51 之前**完全沒有做**。
   ⚠️ 警示同時出現在兩個地方：HP 那一列（你去找它時看得到）與主控制列
      （你**正在看**的地方，就在怪物列上方）。 */
@media all {
    /* 低血：血條描一圈紅邊，數字轉白加粗 */
    #st-hp-row[data-alert="low"] .bar-bg,
    #st-hp-row[data-alert="crit"] .bar-bg { box-shadow: inset 0 0 0 1.5px #f87171; }
    #st-hp-row[data-alert="low"] .bar-text,
    #st-hp-row[data-alert="crit"] .bar-text { color: #fff; font-weight: 800; }
    /* 危險：整列閃 */
    #st-hp-row[data-alert="crit"] { animation: hpcrit 1.05s ease-in-out infinite; }
    #st-hp-row[data-alert="crit"] .lb { color: #fca5a5 !important; }

    /* 主控制列：玩家正在看的地方。⚠️ 只在 `live`（狩獵中）時亮 —— 在村莊裡
       血量會自動回復，那時閃紅只是嚇人。 */
    body[data-hp-alert="low"]  #hunt-ctl[data-state="live"],
    body[data-hp-alert="crit"] #hunt-ctl[data-state="live"] {
        background: rgba(69, 10, 10, .5); border-bottom-color: #7f1d1d;
    }
    body[data-hp-alert="crit"] #hunt-ctl[data-state="live"] .dot {
        background: #f87171; animation-duration: .55s;
    }
    body[data-hp-alert="crit"] #hunt-ctl[data-state="live"] #hunt-ctl-t::after {
        content: "　⚠️ HP 危險"; color: #fca5a5;
    }
    body[data-hp-alert="low"] #hunt-ctl[data-state="live"] #hunt-ctl-t::after {
        content: "　· HP 偏低"; color: #fca5a5; font-weight: 400;
    }

    @keyframes hpcrit {
        0%, 100% { opacity: 1; }
        50%      { opacity: .45; }
    }
    /* ⚠️ 減少動態時仍然要看得見「危險」—— 只拿掉閃爍，紅邊與白字留著。 */
    @media (prefers-reduced-motion: reduce) {
        #st-hp-row[data-alert="crit"] { animation: none; }
        body[data-hp-alert="crit"] #hunt-ctl[data-state="live"] .dot { animation: none; }
    }
}

/* ── 段 3b：垂直空間的重新分配（2026-08-27，真機回報）────────────
           ⚠️ 段 1–3 只驗了「不橫向溢位」與「觸控目標」，**從來沒驗過垂直怎麼分**。
              實機（Pixel 系，393×852）上實測：狀態列 322px（38%）、怪物列 136px、
              戰鬥日誌只剩 **62px**（約 2 行）—— 版面沒有溢位，但完全不能用。
              「沒有溢位」不等於「能用」。smoketest 現在有三條斷言守這件事。

           狀態面板是「隨時要看得到」，不是「要佔一半」：
             等級／AC／MR／金幣 從四列改成兩欄格線、字級縮小
             HP／MP／EXP 三條由 24/24/16px 壓到 17/17/13px
             留白全面收斂 */
        @media all {   /* 段 5：手機版是唯一的版面 */
            /* space-y-3 產生的上邊距與第一列的分隔線在格線裡都是多餘的 */

            /* ⚠️ `!h-6` / `!h-4` 是 Tailwind 的 important utility，要蓋掉必須
                  同時用 !important **並且**選擇器更明確（id ＋ class 贏 class）。 */
            #st-buffs { padding: 0.4rem 0.7rem !important; font-size: 11.5px; line-height: 1.6; }

            /* 地圖列：`冒險地圖` 這四個字在 393px 下會被下拉選單擠成**直排**
               （實測，玩家截圖裡就是一個字一行）。標題本來就是多餘的 ——
               下拉選單自己就寫著現在在哪張地圖。 */
            #map-title { display: none; }
            #map-header { padding: 0.4rem 0.6rem; gap: 0.5rem; }
            #map-header > div { flex: 1; min-width: 0; }
            #map-select { width: 100%; padding: 0.5rem; font-size: 14px; }
            /* status-alerts（中毒／負重…）不能跟著標題一起消失 —— 它是狀態不是裝飾 */
            #status-alerts { display: block; }
            #map-header:has(#status-alerts:not(:empty)) #map-title { display: flex; }
        }


        /* ── 段 4c：手機的技能表（2026-08-27）────────────────────────────
           桌機一階就是一列、上下相鄰，哪一列是哪一階看得出來；手機一階會折成
           好幾列，沒有標籤就完全分不出來。排法照上游的魔法書表：左側標階、
           右側格狀。

           ⚠️ 這一段**只做版面**。設計文件裡「把學不到的也畫出來、四種狀態」那一版
              是把技能表變成 codex —— 而 `renderTabs()` 現在只列**已學會**的
              （`player.skills`），改成列全部會**同時改變桌機的行為**。
              那是功能不是版面，要獨立提案。 */
        .m-tier { display: none; }

        @media all {   /* 段 5：手機版是唯一的版面 */
            #tab-skill:not(.hidden) { display: block; padding: 8px; }
            /* 一階＝一列：左邊固定寬的階數標籤，右邊 4 欄格線。
               ⚠️ 段 14：標籤在 grid **外面**（`.m-skgrid` 才是 grid）。先前標籤與
                  技能同在一個 grid 裡，於是第二列的技能會自動排進**標籤正下方
                  那一格**（玩家截圖：傳送術跑到「1階」底下），整階對不齊。
                  ⚠️ 不能改用 `grid-row: 1 / -1` 讓標籤縱貫 —— `-1` 指的是**顯式**
                     格線，而這裡的列全是隱含的，那個寫法解析不出來。 */
            #tab-skill .m-sktier {
                display: flex !important;
                flex-wrap: nowrap !important;
                align-items: stretch;
                gap: 5px;
                padding: 5px !important;
                margin-bottom: 6px !important;
                background: #1e293b;
                border-radius: 6px;
            }
            #tab-skill .m-tier {
                flex: 0 0 34px;
                display: flex; align-items: center; justify-content: flex-end;
                font-size: 11px; font-weight: 700; color: #cbd5e1;
                padding-right: 3px;
            }
            #tab-skill .m-skgrid {
                flex: 1 1 auto; min-width: 0;
                display: grid;
                grid-template-columns: repeat(4, minmax(0, 1fr));
                gap: 5px;
            }
            /* 技能格：圖示在上、名稱在下。
               ⚠️ 名稱**一定要留**——手機沒有 hover，純圖示的表對還沒把圖背起來的人
                  就是一片看不懂的方塊。桌機是滑過去看，手機沒有那一招。 */
            #tab-skill .sk {
                position: relative;
                display: flex !important;
                flex-direction: column;
                align-items: center;
                justify-content: center;
                gap: 3px;
                min-height: 62px;
                padding: 6px 2px 4px !important;
                border: 1px solid #334155;
                border-radius: 8px;
                background: #16203a;
                white-space: normal !important;
                text-align: center;
                line-height: 1.25;
                font-size: 9.5px;
                color: #cbd5e1;
                font-family: inherit;
            }
            #tab-skill .sk .si { width: 20px !important; height: 20px !important; object-fit: contain; pointer-events: none; }
            #tab-skill .sk .sn { word-break: break-all; }
            /* 右上角的狀態記號 */
            #tab-skill .sk .mk {
                position: absolute; right: 3px; top: 2px;
                font-size: 9px; line-height: 1; pointer-events: none;
            }
            /* ⚠️ 段 16：四種狀態刻意用**不同的視覺語彙**，不是深淺不同的灰 ——
                  「已學會」與「還沒學會」對玩家是完全不同的資訊。 */
            #tab-skill .sk.own {
                border-color: #34d399;
                background: radial-gradient(circle at 50% 40%, rgba(16, 185, 129, .20), #16203a 70%);
            }
            #tab-skill .sk.own .mk { color: #34d399; }
            #tab-skill .sk.no { border-style: dashed; opacity: .45; cursor: default; }
            #tab-skill .sk.no .si { filter: grayscale(1); }
            #tab-skill .sk.no .mk { color: #64748b; }
            /* 金框 ＝ 點下去會發生事情（手動施放／自動施放正開著） */
            #tab-skill .sk.cast { border-color: #fbbf24; }
            #tab-skill .sk .cs {
                position: absolute; left: 3px; bottom: 2px;
                font-size: 8px; line-height: 1; color: #fbbf24; pointer-events: none;
            }
            #tab-skill .sk:active { transform: scale(.95); }

            /* 分組標題（通用魔法／職業魔法） */
            #tab-skill .m-skhd {
                display: flex; align-items: baseline; gap: 7px;
                padding: 12px 4px 6px; font-size: 12.5px; font-weight: 700; color: #fbbf24;
            }
            #tab-skill .m-skhd:first-of-type { padding-top: 4px; }
            #tab-skill .m-skhd:before {
                content: ""; width: 3px; height: 13px; background: #fbbf24; border-radius: 2px;
                align-self: center;
            }
            #tab-skill .m-skhd small { font-weight: 400; color: #7c8aa3; font-size: 10.5px; }
            /* 全部／我學到的 */
            #tab-skill .m-skfilter { display: flex; gap: 6px; padding: 2px 2px 6px; }
            #tab-skill .m-sklegend {
                display: flex; flex-wrap: wrap; gap: 4px 12px; padding: 0 4px 2px; font-size: 10px;
            }
            #tab-skill .m-sklegend .l-own { color: #34d399; }
            #tab-skill .m-sklegend .l-no { color: #64748b; }
            #tab-skill .m-sklegend .l-cast { color: #fbbf24; }
            #tab-skill .m-skempty { padding: 24px 12px; font-size: 12px; color: #7c8aa3; line-height: 1.7; text-align: center; }
        }

        /* ── 段 4d：手機的設定頁（2026-08-27）──────────────────────────────
           ⚠️ 實測 393px：自動化設定裡 **13/13 列都是 16px**（勾選框是 `w-4 h-4`），
              下拉與數字輸入最小 22px。全部遠低於 44px 的觸控下限，而這一頁是
              掛機遊戲玩家最常回來調的地方。

           作法：**整列**（包住勾選框的 `<label>`）變成觸控目標，勾選框本身只是
           指示器 —— 把 16px 的框放大到 44px 會讓版面整個走樣，而且沒有必要。 */
        @media all {   /* 段 5：手機版是唯一的版面 */
            /* ── 段 17：照 demo 分節（`.sgrp`），字級與其他分頁一致 ──
               ⚠️ 先前這一頁整份比別頁大一號（`text-base` 16px／`text-sm` 14px），
                  因為它從桌機沿用下來、段 6–16 每一次都跳過它。
               ⚠️ 勾選框仍然是真的 `<input type="checkbox">`（`settings.js` 讀寫
                  `el.checked`），只是整列變成觸控目標 —— 把 16px 的框放大到 44px
                  會讓版面整個走樣，而且沒有必要（`label` 本來就會把點擊轉給 input）。 */
            #panel-auto .panel-header { padding: 0.4rem 0.75rem; font-size: 13px; }
            #panel-auto .scroll-fill { padding: 0 0 12px; }

            /* 分節 */
            #panel-auto .sgrp {
                margin: 10px 8px 0;
                border: 1px solid #334155; border-radius: 10px;
                background: #16203a; overflow: hidden;
            }
            #panel-auto .sgrp > .gh {
                padding: 8px 11px; font-size: 11px; font-weight: 700; color: #fbbf24;
                background: #111c31; border-bottom: 1px solid #334155;
            }
            #panel-auto .sgrp > .gh small {
                display: block; font-weight: 400; color: #7c8aa3; font-size: 10px; margin-top: 2px;
            }

            /* 一般的一列：整列都是觸控目標 */
            #panel-auto .sr {
                display: flex; align-items: center; gap: 8px;
                padding: 8px 11px; min-height: 48px;
                border-bottom: 1px solid #22304a; cursor: pointer;
            }
            #panel-auto .sgrp > *:last-child { border-bottom: 0; }

            /* 🎛️ 分節裡的動作按鈕是**整列**（那一節只有這一個動作可做）。
               ⚠️ `<button>` 預設是 inline-block ＝ **內容寬度**，所以「切換角色」
                  一直是縮在左邊的一小顆 —— 只有它一個的時候看不太出來，
                  2026-09-01 旁邊多了「怪物掉落圖鑑」之後，兩顆不一樣寬就很明顯。
                  這與段 54 的 `.acc-btns` 是同一個坑：**斷言全綠，只有截圖看得出來**。
               ⚠️ 左右留 11px，與 `.sr` 那幾列的內距對齊 —— 貼著邊的話它會讀成
                  分節的一部分而不是一顆按鈕。 */
            #panel-auto .sgrp > .m-act {
                display: flex; align-items: center; justify-content: center;
                width: calc(100% - 22px); margin: 8px 11px;
            }
            #panel-auto .sr .lb { flex: 1; min-width: 0; font-size: 12.5px; }
            #panel-auto .sr .lb small { display: block; color: #7c8aa3; font-size: 10px; margin-top: 2px; }

            /* 主項 ＋ 附屬「自動購買」的一列 */
            #panel-auto .sr2 {
                display: flex; align-items: center; justify-content: space-between; gap: 8px;
                padding: 4px 11px; border-bottom: 1px solid #22304a;
            }
            /* ⚠️ `.hidden` 要蓋得過 `.sr2` 的 display —— 那三列（勇敢／慎重／精靈餅乾）
                  是靠 `updateClassPotionRows()` 加 `.hidden` 依職業收起來的。 */
            #panel-auto .sr2.hidden { display: none; }
            #panel-auto .sr2 .lbl {
                flex: 1; min-width: 0; display: flex; align-items: center; gap: 8px;
                min-height: 44px; font-size: 12.5px; cursor: pointer;
            }
            #panel-auto .sr2 .lbl small { color: #7c8aa3; font-size: 10px; margin-left: 5px; }
            #panel-auto .sr2 .buy {
                flex: 0 0 auto; display: flex; align-items: center; gap: 4px;
                min-height: 44px; padding-left: 6px;
                font-size: 10px; color: #7c8aa3; cursor: pointer;
            }
            #panel-auto .sr2 .buy.none { cursor: default; }

            /* 有下拉的一格（喝水／攻擊技能／治癒魔法）*/
            #panel-auto .slot { padding: 8px 11px; border-bottom: 1px solid #22304a; }
            #panel-auto .slot .st {
                display: flex; align-items: center; justify-content: space-between;
                gap: 8px; margin-bottom: 7px;
                /* ⚠️ 轉換魔法那一列有**兩組**門檻，而 `.cond` 是 nowrap ——
                   窄螢幕塞不下時要換行，不可以橫向溢位（見 CLAUDE.md 的
                   `.scroll-fill` 那一條：一軸溢位會把整份版面拉走）。 */
                flex-wrap: wrap;
            }
            #panel-auto .slot .st b { font-size: 12.5px; font-weight: 700; }
            #panel-auto .slot .cond {
                display: flex; align-items: center; gap: 5px;
                font-size: 11.5px; color: #94a3b8; white-space: nowrap;
            }

            /* 下拉與數字輸入：44px */
            #panel-auto .ssel {
                width: 100%; min-height: 44px; font-size: 13px; padding: 0 10px;
                background: #0b1425; border: 1px solid #475569; border-radius: 8px;
                outline: none;
            }
            #panel-auto .numin {
                width: 3.2rem; min-height: 44px; font-size: 13px; text-align: center;
                background: #0b1425; border: 1px solid #475569; border-radius: 7px;
                color: #e2e8f0; outline: none;
            }
            /* 原生勾選框（`settings.js` 讀寫 `el.checked`，不能換成 span），
               只用 `accent-color` 讓它不是刺眼的白 */
            #panel-auto input[type=checkbox] {
                width: 18px; height: 18px; flex: 0 0 auto; accent-color: #10b981;
            }
            #panel-auto .sr2 .buy input[type=checkbox] { width: 14px; height: 14px; }

            /* 📱 段 25：選項前面的圖示。⚠️ `flex: 0 0 auto` 不可省 —— 不然長名稱
                  會把圖示壓扁（flex 子項預設可以縮）。
               📱 段 41：20 → 28px。20px 的圖示旁邊全是 ≥44px 的控制項，
                  小到不像是給人看的 —— 它存在的理由是「一眼認出這個設定配哪個
                  道具」，太小就等於沒有做。⚠️ 那一列的最小高度是 44px，
                  28px 仍然放得下（實測列高沒有變）。 */
            #panel-auto img.si {
                flex: 0 0 auto; width: 28px; height: 28px; object-fit: contain;
                border-radius: 4px; background: rgba(11, 20, 37, .6);
            }

            /* 自動化技能的勾選清單。⚠️ 兩欄在 393px 下字會被切掉 —— 單欄。 */
            #auto-buff-skills.autosk { display: grid; grid-template-columns: minmax(0, 1fr); gap: 0; }
            #auto-buff-skills.autosk label {
                display: flex; align-items: center; gap: 8px;
                min-height: 44px; padding: 4px 11px; font-size: 12.5px; cursor: pointer;
                border-bottom: 1px solid #22304a;
            }
            #auto-buff-skills.autosk label:last-child { border-bottom: 0; }
            #auto-buff-skills:empty:after {
                content: "目前沒有學會任何可自動施放的技能。";
                display: block; padding: 14px 11px; font-size: 11px; color: #7c8aa3;
            }

            /* 存檔狀態 */
            /* ⚠️ `width: auto` **不可省**（2026-09-07 發現）：`save-status.js` 每次
               更新狀態時會整個重寫 `className`，而那串裡有 Tailwind 的 `w-full`
               —— `100%` ＋ 這裡左右各 8px 的 margin ＝ **一定溢出 16px**
               （實測容器 351px、它自己 351px ＋ 16px 邊距 → scrollWidth 367）。
               症狀是那一條往右偏 8px、右邊被 `.scroll-fill` 的 `overflow-x: hidden`
               裁掉。⚠️ 它**一直都在**，只是沒有人量得到：那條「設定頁不會橫向溢位」
               的斷言量的是 `#panel-auto`，而它是 `overflow: hidden` ——
               `scrollWidth` 永遠等於 `clientWidth`，那條斷言從來沒有驗到東西
               （CLAUDE.md 陷阱 ② 的另一種：**在 overflow:hidden 的元素上量溢位**）。
               ⚠️ 改 CSS 不改 `save-status.js`：那個檔案有好幾個狀態各寫一次
                  className，改字串要每一處都改，而漏掉不會有錯誤訊息。
                  id（1,0,0）本來就贏過 class（0,1,0）。 */
            #save-status {
                width: auto;
                margin: 12px 8px 0; padding: 8px 11px;
                border: 1px solid #334155; border-radius: 8px; background: rgba(30, 41, 59, .6);
                font-size: 11px; text-align: center; color: #94a3b8;
            }
        }

        /* ── 段 4e：資訊密度（2026-08-27，實機截圖 ＋ 對照參考版本）────────
           ⚠️ 段 1–4 一直用「數字」判斷版面（不溢位、觸控 44px、垂直比例），
              但那三項全綠的畫面**看起來仍然很糟**。真正的差距是**資訊密度**：
              每個容器都還帶著桌機的 padding／font-size／圓角，於是
                · 分頁按鈕是 2 欄大方塊，5 個佔掉 3 列約 300px
                · 一鍵排列／賣廢品又佔一整列 80px
                · 背包格線只分到畫面的一小截
              參考版本的做法是：分頁＝**一列細長 tab**、工具列收窄、狀態壓成一行。 */
        @media all {   /* 段 5：手機版是唯一的版面 */
            /* 1) 分頁按鈕：2 欄大方塊 → 一列橫排、可左右捲
               段 10：再從「圓角填色按鈕」改成 demo 的**扁平底線分頁**。
               ⚠️ 差別不只是好不好看：填色按鈕自己帶邊框＋圓角＋底色，一列就吃掉
                  約 80px（實測），而底線分頁是 44px。三個分頁（背包／角色／社群）
                  都掛著它，等於每一頁都白白少一截內容。 */
            #tabbar {
                display: flex !important;
                /* ⚠️ `flex-direction` 一定要明講 row —— `#tabbar` 帶著 `.panel`，
                      而 `.panel` 是 `flex-direction: column`。只寫 display:flex
                      的話 5 個分頁會變成**直排**，比原本的 2 欄還糟。 */
                flex-direction: row !important;
                flex-wrap: nowrap;
                overflow-x: auto;
                overflow-y: hidden;
                gap: 0 !important;
                padding: 0 !important;
                scrollbar-width: none;
                background: #0b1425 !important;
                border: 0;
                border-bottom: 1px solid #334155;
                border-radius: 0;
            }
            #tabbar::-webkit-scrollbar { display: none; }
            #tabbar button {
                flex: 1 0 auto;
                min-width: 64px;
                min-height: 44px !important;
                padding: 11px 8px !important;
                font-size: 12.5px !important;
                font-weight: 700;
                white-space: nowrap;
                color: #A3AAB8 !important;   /* 🎨 2026-09-27 設計稿的一般色（原 #94a3b8）*/
                background: none !important;
                border: 0 !important;
                border-bottom: 2px solid transparent !important;
                border-radius: 0 !important;
                box-shadow: none !important;
            }
            #tabbar button.active {
                color: #F6B73C !important;              /* 🎨 設計稿的選取色（原 #fbbf24）*/
                border-bottom-color: #F6B73C !important;
            }
            /* 🎨 社群分頁的圖示（原本是 emoji 接在文字前面）*/
            #tabbar button .uic { width: 16px; height: 16px; margin-right: 4px; vertical-align: -3px; }

            /* 🗑️ 段 10：一鍵排列／賣廢品那一列移除，改成「全部」分頁的 ⇅ ＋ 右下角浮動鈕 */

            /* 🗑️ 段 6：狀態面板改寫成專屬 DOM（#st-head），這裡壓扁四列的規則不再需要 */

            /* 4) 面板本身：桌機的圓角與陰影在手機上只是吃空間 */
            #col-left > .panel, #col-center > .panel, #col-right > .panel {
                border-radius: 8px;
            }
            #game-screen { gap: 5px; padding: 4px 5px 0; padding-bottom: calc(58px + env(safe-area-inset-bottom)); }
            #col-left, #col-center, #col-right { gap: 5px; }

            /* 5) 背包格子：讓格線看得出是格子 —— 邊框、間距、圖示都要對 */

            /* 6) 怪物卡片：桌機的字級在 393px 下三格並排會擠掉圖 */
            .mob-target { min-height: 108px; padding: 0.35rem 0.25rem; gap: 2px; }
            #mob-list .mob-target > div:first-child,
            #mob-list .mob-target .font-bold { font-size: 12px !important; line-height: 1.3; }
            /* ⚠️ 段 22：44 → 72px。段 4e 壓到 44 是為了「三格並排在 393px 下會擠掉圖」，
                  但那之後卡片變大了（段 10 加了 Lv 與 HP 數字、地圖列也收窄）——
                  實測卡片 107×146 而圖只有 44×44，上下都是空的。
                  原圖 111×78，72px 高會畫成 102×72，接近原生尺寸。 */
            /* ⚠️ 段 24：固定尺寸的**容器**，圖片本身的尺寸由 `mobImgFit()` 逐張算
                  （2026-09-25 起是原尺寸、只縮不放，見 src/ui/hud.js）。容器不可以跟著圖變 ——
                  那樣卡片高度又會忽高忽低，正是段 23 剛修好的東西。 */
            /* ⚠️ 段 31：改成 `position: relative` ＋ `overflow: hidden`，圖改由 JS
                  絕對定位。理由是縮放與置中對齊的是「圖裡有畫東西」的範圍 ——
                  留白多的 sprite 的 `<img>` 因此會比框還大（最大 279×225px），
                  要靠框把四周的**透明**留白裁掉。裁掉的一定是透明的：
                  bbox 依定義涵蓋所有不透明像素，而縮放已夾在 96×88 內。
               ⚠️ 置中不能再交給 flex —— 要對齊的是「內容的中心」而不是
                  「圖的中心」，那個位移只有 `mobImgFit()` 算得出來。 */
            /* 📐 2026-09-07（玩家回報「另一支手機最右邊的怪物圖被截掉」）：
               框的寬度是**上限**不是固定值。
               ⚠️ 病因：`width: 96px` 讓卡片的 min-content 變成 96 ＋ 8（內距）
                  ＋ 4（框線）＝ **108px**，而 flex 子項預設 `min-width: auto`
                  ＝「不可以縮到比 min-content 小」。三格 ＋ 兩個 6.4px 的間隙需要
                  **336.8px**，而 `#mob-list` 的寬是 `視窗 − 58` ——
                  也就是說**視窗要 395px 才夠**。實測 393 下就已經溢出 2px
                  （scrollWidth 337 對 clientWidth 335，第三張卡右緣 366 對 364），
                  360px 的機器則溢出 **35px**，第三隻整個被 `#map-panel` 的
                  `overflow: hidden` 裁掉。
               ⚠️ **不可以改成把 96 調小**：那會讓每一支手機的怪物圖都變小
                  （原尺寸之下大怪本來就是被這個框夾住的）。
               ⚠️ 框變窄時 `mobImgFit()` 會拿**實際的框寬**去算縮放（見
                  `src/ui/hud.js` 的 `mobImgScale()`）—— 不傳的話圖會照著 96
                  縮放然後偏一邊被裁，正是段 55（`max-width: none`）那個坑。
               ⚠️ 高度維持 88px：擠的是橫向，縱向沒有問題。 */
            #mob-list .mob-target { min-width: 0; }
            #mob-list .mob-target .mob-imgbox {
                width: 100%; max-width: 96px; height: 88px; margin: 0 auto 0.5rem;
                position: relative; overflow: hidden;
            }
            /* 🌑 灰白剪影怪（影魔／象牙塔影魔）→ 半透明黑影，照上游 v3.0.24。
               ⚠️ 掛在父層：`<img>` 上的受擊動畫會設自己的 filter，掛同一層會一被打就閃回灰白。
               名單與理由見 src/ui/hud.js 的 `MOB_SHADOW_TINT`。 */
            #mob-list .mob-target .mob-imgbox.mob-shadow-tint { filter: brightness(0); opacity: .62; }
            /* ⚠️ `max-width: none` **不可省**（2026-09-01，玩家回報「食人妖精很扁」）。
               Tailwind 的 preflight 有一條 `img { max-width: 100% }`，它會把
               `mobImgFit()` 算好的寬度夾回框寬（96px）—— 而**高度不會被夾**，
               於是圖被**橫向壓縮**。實測 134 隻裡有 **37 隻**中招，平均壓縮
               1.42 倍，最嚴重的希爾黛斯是 **2.91 倍**（279px 夾成 96px）。
               ⚠️ 段 31 的設計本來就預期「圖比框大（最大 279×210），靠
                  `overflow: hidden` 裁掉透明留白」—— preflight 讓那個前提在
                  寬度上悄悄不成立了。
               ⚠️ 連帶症狀：`left` 是按算出來的寬度算的，寬度被夾之後圖還會
                  **偏一邊**（食人妖精那張截圖裡它靠在左邊）。 */
            #mob-list .mob-target .mob-img {
                display: block; position: absolute; max-width: none;
            }
            #mob-list .bar-bg { height: 8px !important; }
            #mob-list .bar-text { display: none; }
        }

        /* ── 段 4f：真機回報的三件事（2026-08-27）────────────────────────
           1. 底部導覽會蓋住內容（而且要在**一般瀏覽器**下就正確，不能只靠 PWA）
           2. 自動化設定不能捲
           3. 道具格子太小、上方被三條橫列吃掉 */
        @media all {   /* 段 5：手機版是唯一的版面 */
            /* ── 1. 底部導覽改成「排版的一部分」，不再 position: fixed ──
               ⚠️ `fixed` ＋ `100dvh` 在**一般瀏覽器**上是不可靠的：Android Chrome 的
                  網址列會縮放，`dvh` 與 fixed 的更新時機不一致，導覽就會蓋到內容。
                  玩家要的是「不裝 PWA 也能正常顯示」，所以不要賭那個時機 ——
                  讓導覽真的佔一格，它就**永遠不可能**蓋住任何東西。 */
            #mobile-nav {
                position: static;
                flex: 0 0 auto;
                margin: 0 -5px;        /* 補回 #game-screen 的左右內距，維持滿版 */
            }
            /* 導覽進入排版之後，就不需要用 padding 幫它讓位了 */
            #game-screen { padding-bottom: env(safe-area-inset-bottom); }

            /* ── 2. 設定頁：#col-left 在設定頁必須**吃滿剩下的高度**，
                  不然它是 `flex: 0 0 auto`（內容決定高度），面板就整個撐出畫面外
                  —— 實測 #panel-auto 高 1088px 而視窗只有 916，看起來像「不能捲」，
                  其實是根本沒有捲動容器。 */
            body[data-mpage="set"] #col-left { flex: 1 1 auto; min-height: 0; }
            /* 🗑️ 段 17：內層容器改用 `.scroll-fill`（本來就是 flex:1 ＋ min-height:0
                  ＋ overflow），這裡不必再補一份。留這段記錄是因為原本的症狀很難聯想：
                  flex 子項的 `min-height` 預設是 auto（不可以縮到比內容小），所以
                  `flex-1` 約束不了它 —— 面板整個撐出畫面外，看起來像「不能捲」，
                  其實是**根本沒有捲動容器**。 */

            /* ── 3. 道具格子：把上方三條橫列讓出來的空間還給格線 ── */
            #game-screen { padding-left: 4px; padding-right: 4px; }
            #col-right > .panel:last-child { border-radius: 8px; }
            /* 分頁面板與內距再收一次 —— 每一層 4px 就是格子少 8px */
            /* 🗑️ 段 10 移除 `#tabbar` 的內距微調：底線分頁本來就沒有內距。 */
        }

        /* 📱 手機上左右貼齊螢幕、不露出背景圖（2026-09-26，玩家要求「手機版畫面切齊左邊跟右邊」）。
           原本每邊空 20px：`<body>` 的 `.p-4`（16px）＋ `#game-screen` 的 4px —— 背景圖從兩側露出來。
           ⚠️ 只在窄螢幕：桌機是 500px 一欄置中，兩側本來就是背景，那是刻意的。
              540 ＝ 500 那一欄 ＋ 兩側各 20px 舊空隙；再寬就是「一欄置中」的情況。
           ⚠️ `body.p-4` 而不是 `body`：Tailwind 的 `.p-4`（0-1-0）贏過型別選擇器 —— 2026-09-07 那條
              `body { padding: 0 }` 就是這樣死掉的（見上面 🗑️ 那段）。只改左右，上下照舊。
           ⚠️ 三件事要一起做，少一件就還看得到背景：
              ① 兩層內距歸零 ② 底部導覽原本用 `margin: 0 -5px` 抵掉 #game-screen 的內距 —— 內距沒了它要歸零，
              否則會凸出畫面 5px ③ 最外層面板拿掉左右框線與圓角 —— 貼邊的圓角會在四個角露出一小塊背景。
           ⚠️ ③ 的選擇器一定要寫成 `#col-* > .panel`：上面「4) 面板本身」那條是同一個寫法（1-1-0），
              單寫 `#status-panel`（1-0-0）會輸給它 —— 第一版就是這樣，框線拿掉了、圓角還是 8px
              （smoketest 手機那一趟抓到的，截圖上看不出來）。
           ⚠️ 面板之間上下那 8px 的間隙照舊（玩家只要求左右）。 */
        @media (max-width: 540px) {
            body.p-4 { padding-left: 0; padding-right: 0; }
            #game-screen { padding-left: 0; padding-right: 0; }
            #mobile-nav { margin: 0; }
            #col-left > .panel, #col-center > .panel, #col-right > .panel,
            #col-right > .panel:last-child {   /* ← 這一條另有一句 8px（1-2-0），要逐字蓋過 */
                border-left-width: 0; border-right-width: 0; border-radius: 0;
            }
        }

        /* ══════════════════════════════════════════════════════════════════
           段 5：手機版介面成為**唯一**的版面（2026-08-27）

           先前的做法是「桌機三欄 ＋ 一堆 @media 把它重塑成手機」。做了六個階段之後
           結論很清楚：**那條路一直在跟桌機的 Tailwind utility 打 !important 戰**，
           而且每次都是「斷言的數字對、畫面不對」。物品 dialog 就是必然的結果 ——
           它是 `min-w-[360px]` ＋ 比較面板 `min-w-[280px]`，在手機上必然是 660px。

           所以改成：**所有寬度走同一條路**，桌機只是把那一欄置中。
           這是**移除**複雜度不是增加 —— 沒有「桌機不能動」這個限制之後，
           要改版面可以直接改，不用再想「這樣會不會弄壞桌機」。
           ⚠️ 代價：桌機不再是三欄並排。那是刻意的，玩家明確要求的。
           ══════════════════════════════════════════════════════════════════ */
        @media all {
            /* 一欄置中。500px 是手機的上限寬度 —— 再寬格子就會變得稀疏，
               而所有的尺寸（格子 65px、卡片 101px、觸控 44px）都是照手機調的。 */
            #game-screen {
                max-width: 500px !important;
                margin: 0 auto;
            }
            /* 創角／登入畫面同樣收在同一個寬度，不然開場與遊戲中會是兩種比例 */
            #creation-screen { max-width: 500px !important; }

            /* ── 段 18：物品 dialog 是**手機專屬的 DOM**，不是被 CSS 疊起來的桌機面板 ──
               先前是 `absolute top-1/2 left-1/2 -translate-1/2` ＋ 兩個並排的面板
               （`min-w-[280px]` ＋ `min-w-[360px]` ＝ 至少 660px），段 5 用
               `flex-direction: column-reverse` 把它壓成貼底的兩層 —— 那是重塑。
               現在它就是一個 bottom sheet：拉柄 ＋ 標題 ＋ 可捲內容 ＋ 釘在底部的動作列。
               ⚠️ 動作列**不跟著內容捲**：物品說明可以很長（詞綴、套裝、比對），
                  捲在一起的話「裝備」會被推到看不見的地方。 */
            #item-modal {
                display: flex; flex-direction: column;
                position: fixed; bottom: var(--vp-cut); z-index: 50;
                left: max(0px, calc(50% - 250px)); right: max(0px, calc(50% - 250px));
                top: auto; width: auto; max-width: none; transform: none;
                max-height: calc(0.86 * var(--app-h, 100dvh));
                background: #1e293b;
                border-top: 1px solid #475569;
                border-radius: 14px 14px 0 0;
                box-shadow: 0 -12px 34px rgba(0, 0, 0, .6);
                padding-bottom: env(safe-area-inset-bottom);
            }
            #item-modal.hidden { display: none; }
            #modal-scrim {
                position: fixed; top: 0; bottom: var(--vp-cut); z-index: 49;
                left: max(0px, calc(50% - 250px)); right: max(0px, calc(50% - 250px));
                background: rgba(2, 6, 23, .6);
            }
            #modal-scrim.hidden { display: none; }
            #modal-grip {
                flex: 0 0 auto; width: 38px; height: 4px; margin: 8px auto 4px;
                border-radius: 999px; background: #475569;
            }
            #modal-head {
                flex: 0 0 auto; display: flex; align-items: center; gap: 8px;
                padding: 4px 12px 10px; border-bottom: 1px solid #334155;
            }
            #modal-item-name {
                flex: 1; min-width: 0; display: flex; align-items: center; gap: 8px;
                font-size: 14px; font-weight: 700; line-height: 1.3;
            }
            #modal-item-name .mi { width: 30px; height: 30px; object-fit: contain; flex: 0 0 auto; }
            #modal-item-name .mn { min-width: 0; }
            #modal-x {
                flex: 0 0 auto; width: 44px; height: 44px; margin: -6px -8px 0 0;
                background: none; border: 0; color: #94a3b8; font-size: 18px; cursor: pointer;
            }
            #modal-x:active { color: #e2e8f0; }

            #modal-body {
                flex: 1 1 auto; min-height: 0; overflow-y: auto; overflow-x: hidden;
                padding: 10px 12px;
            }
            #modal-item-desc {
                font-size: 12.5px; line-height: 1.7; color: #cbd5e1; min-height: 0;
            }
            /* 比對：在內容裡面，用一條虛線分隔 —— 不是疊在上方的另一個框 */
            #modal-compare {
                margin-top: 12px; padding-top: 10px;
                border-top: 1px dashed #475569; font-size: 12px; line-height: 1.6;
            }
            #modal-compare.hidden { display: none; }
            #modal-compare .cmp-hd {
                font-size: 10.5px; font-weight: 700; color: #fbbf24; margin-bottom: 6px;
            }
            #modal-compare .cmp-sep { margin: 10px 0; border-top: 1px dashed #334155; }

            /* 動作列：兩欄，`span2` 佔滿一列 */
            #modal-actions {
                flex: 0 0 auto;
                display: grid; grid-template-columns: 1fr 1fr; gap: 6px;
                padding: 8px 12px 12px; border-top: 1px solid #334155;
            }
            #modal-actions:empty { display: none; }
            /* 🎛️ 段 44：`.m-act` 從「物品 modal 專用」升格成**全 app 的動作按鈕**。
               ⚠️ 先前這幾條是寫成 `#modal-actions .m-act` 的後代選擇器 ——
                  所以其他介面就算加上 `m-act` 這個 class 也**完全不會生效**，
                  只能各自用桌機時代的 `.btn`。那就是「五種按鈕並存」的成因：
                  不是沒有系統，是系統被鎖在一個容器裡。
               ⚠️ `#modal-actions` 只留版面（grid、間距），外觀全部搬到這裡。
               語意：預設＝次要；`.pri` 主要／確定；`.warn` 不可逆（賣出、解散）；
                     `.mag` 特殊（強化）；`.tog.on` 切換中。 */
            .m-act {
                min-height: 44px; padding: 0 10px;
                border: 1px solid #475569; border-radius: 8px;
                background: #0f1a2e; color: #cbd5e1;
                font-family: inherit; font-size: 12.5px; font-weight: 700; cursor: pointer;
            }
            #modal-actions .m-act.span2 { grid-column: 1 / -1; }
            /* ⚠️ `.m-act` 原本只用在 <button> 上，所以沒有設 `display` —— 而
               **非替換型 inline 元素的 `min-height` 是不生效的**，直接套在 <a> 上
               會得到一條沒有高度、文字也不置中的連結（觸控目標整個消失，
               而且畫面上看起來只是「有點怪」）。加這一條讓它變成真的動作按鈕。 */
            a.m-act {
                display: flex; align-items: center; justify-content: center;
                text-decoration: none;
            }
            .m-act.pri { background: #065f46; border-color: #10b981; color: #d1fae5; }
            .m-act.warn { background: #7c2d12; border-color: #ea580c; color: #ffedd5; }
            .m-act.mag { background: #4c1d95; border-color: #8b5cf6; color: #ede9fe; }
            /* 切換型（鎖定／廢品）：開著才上色 —— 不然看不出目前是哪一邊 */
            .m-act.tog.on { background: #78350f; border-color: #f59e0b; color: #fef3c7; }
            .m-act:disabled { opacity: .45; cursor: default; }
            .m-act:active { transform: scale(.98); }
        }

        /* ── 段 6：狀態 header 是**手機專屬的 DOM**，不是被 CSS 壓扁的桌機版 ──
           先前是「標題列 ＋ 四列數值（等級／AC／MR／金幣）＋ 三條粗條」，
           我用 `display:flex; flex-wrap` 把那四列壓成一行 —— 那是 CSS 妥協，
           而且每加一個數值就要再調一次。段 6 直接把 DOM 改成它該有的樣子。

           ⚠️ **每一個 id 都保留**（`#st-class`／`#st-classname`／`#st-lv`／
              `#st-ac`／`#st-mr`／`#st-gold`／`#bar-*`／`#txt-*`），所以
              `src/ui/hud.js` 的 `updateUI()` 一行都不用改。
           ⚠️ `#st-class` 是**角色名稱**，`#st-classname` 才是職業 —— 命名很誤導。 */
        /* 📱 段 35：左邊整欄給頭像，右欄裝「身分／數值 ＋ 三條 bar」。
           ⚠️ 內距改由 #st-main 統一負責 —— 原本 #st-head 與 #st-bars 各自帶
              左右 9px，分欄之後那兩份會在右欄內部再縮一次，bar 就對不齊頭像。 */
        #st-main {
            display: flex;
            align-items: stretch;
            gap: 10px;
            padding: 7px 9px;
        }
        #st-right {
            flex: 1 1 auto;
            min-width: 0;
            display: flex;
            flex-direction: column;
            justify-content: center;
            gap: 5px;
        }
        #st-head {
            display: flex;
            align-items: flex-start;
            justify-content: space-between;
            gap: 8px;
            padding: 0;
        }
        /* 📱 段 34：圓形頭像。取代原本「整片立繪當面板背景」的做法。
           ⚠️ 立繪是**全身**圖（`assets/character/男騎士.png` 等），所以要
              `object-fit: cover` ＋ 把取景點拉到上緣附近才會是臉，不是肚子。
           ⚠️ 40px 不會讓面板變高：右邊那兩行字（15px ＋ 11.5px 加行距）本來
              就約 40px —— 這是**用既有的空白**換到一個身分識別，不是加一列。
           ⚠️ 琥珀環刻意壓到 55% 不透明：琥珀在這個 app 是「選中／可互動」的
              語彙（分頁底線、選中的怪、金幣），頭像不是控制項，全彩會搶戲。
           ⚠️ 圖載不到時由 `updateUI()` 收掉（`display:none`），不要留一個破圖框
              —— 舊角色的 `player.avatar` 可能是空的，那時退路是職業名，
              而 `assets/character/` 裡**沒有**「騎士.png」這種檔案（只有男/女騎士）。 */
        /* 📱 段 35：40 → 84px（玩家回報「太小、看不出是誰」）。面積是原本的 4.4 倍，
           而面板高度不變 —— 它吃的是右欄本來就用不到的寬度，不是新的高度。 */
        #st-avatar {
            flex: 0 0 auto;
            align-self: center;
            display: block;
            width: 84px; height: 84px;
            border-radius: 50%;
            overflow: hidden;
            background: #0b1425;
            border: 2px solid rgba(251, 191, 36, .55);
            box-shadow: 0 1px 4px rgba(0, 0, 0, .5);
        }
        /* ⚠️ 一定要自己寫這一條：上面的 `display: block` 會蓋掉瀏覽器預設給
              `[hidden]` 的 `display: none`，少了它「載不到就收掉」等於沒作用。 */
        #st-avatar[hidden] { display: none; }
        /* ⚠️ 立繪是 512×512 的方形插畫，框也是方的 —— 比例相同，所以
              `object-fit: cover` **不會裁掉任何東西**，`object-position` 也完全
              沒有作用：整張圖被縮成 40px，臉只佔其中一小塊（第一版就是這樣，
              看起來像縮圖不是頭像）。要取臉只能真的放大再位移。
           ⚠️ 也**不要**用 `transform: scale()` ＋ `transform-origin` —— 那個的
              可見範圍不是以原點為中心，而是偏一邊的（實測放大 1.8 倍、原點
              57% 時，實際看到的是 25%～81%），六張裡有三張的臉因此被切掉。
              改成把圖放大成 260% 再用 left／top 明確位移，框到哪一塊是算得出來的。
           ⚠️ `max-width: none` 不可省：Tailwind 的 base 會給 img `max-width:100%`，
              那會讓 260% 的寬度失效、又縮回整張圖。
           ⚠️ 位移值由 `avatarOffset()` 逐張算（見 src/ui/hud.js）。 */
        #st-avatar { position: relative; }
        #st-avatar img {
            display: block;
            position: absolute;
            width: 260%; height: 260%; max-width: none;
        }
        #st-head .who { min-width: 0; line-height: 1.35; }
        #st-head .who > b {
            display: block;
            font-size: 15px;
            font-weight: 700;
            color: #f8fafc;
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
        }
        #st-head .who > span { font-size: 11.5px; color: #94a3b8; }
        /* ⚖️ 性向的圖示（單一羽翼）。18px 是量出來的 —— 雙翼那一版要 22px 才勉強
           讀得出來，單翼在同樣寬度下每一片拿到兩倍的像素，18px 就夠。
           ⚠️ `vertical-align` 讓它壓在基線上，不然那一行的行高會被撐開，
              而撐開就等於面板長高（那 22px 手機上是從戰鬥日誌扣的）。 */
        #st-align-ic { display: inline-block; line-height: 0; margin: 0 3px 0 5px; }
        #st-align-ic .align-ic { width: 18px; height: auto; display: block; }
        #st-head .who > span #st-align-ic { vertical-align: -3px; }
        #st-head .who > span b { color: #f8fafc; font-weight: 700; }
        #st-head .nums {
            flex: 0 0 auto;
            text-align: right;
            font-size: 11.5px;
            color: #94a3b8;
            line-height: 1.45;
        }
        #st-head .nums > span { display: block; white-space: nowrap; }
        #st-head .nums .g b { color: #fbbf24; font-weight: 700; font-variant-numeric: tabular-nums; }
        /* 🎨 金幣圖示固定琥珀色：它不是可以點的東西，不會被誤會成「選取中」；灰色的金幣不像錢。 */
        #st-head .nums .g .uic { color: #F6B73C; width: 13px; height: 13px; margin-right: 3px; vertical-align: -2px; }
        #st-head .nums b { color: #f8fafc; font-weight: 700; font-variant-numeric: tabular-nums; }

        #st-bars { display: flex; flex-direction: column; gap: 3px; padding: 0; }
        #st-bars > div { display: flex; align-items: center; gap: 6px; }
        #st-bars .lb { flex: 0 0 26px; text-align: right; font-size: 10px; font-weight: 700; }
        #st-bars .lb.hp { color: #f87171; }
        #st-bars .lb.mp { color: #60a5fa; }
        #st-bars .lb.xp { color: #eab308; font-size: 9px; }
        #st-bars .bar-bg { height: 15px; }
        #st-bars .bar-bg.xp { height: 10px; }
        #st-bars .bar-text { font-size: 10px; line-height: 15px; color: #fff; }
        #st-bars .bar-bg.xp + .bar-text, #st-bars .xp .bar-text { font-size: 9px; line-height: 10px; }
        #st-buffs { padding: 4px 9px !important; font-size: 11px; line-height: 1.6; }
        /* 📱 段 21：左側那條是**純圖示**（玩家指定）。
           ⚠️ 沒有圖示可用的（夥伴、誘捕、異常狀態）退回文字 —— 全部塞圖示會讓
              那幾項憑空消失，而中毒／暈眩正是最需要看見的。 */
        #st-buffs .bi-row { display: flex; flex-wrap: wrap; align-items: center; gap: 4px; }
        #st-buffs .bi {
            width: 22px; height: 22px; object-fit: contain;
            border-radius: 4px; background: rgba(15, 23, 42, .6);
            /* 外框沿用該 buff 的顏色語彙？—— 不行，圖示是 <img> 拿不到文字色。
               改用一層很淡的邊，只為了讓圖示彼此分得開。 */
            box-shadow: inset 0 0 0 1px rgba(148, 163, 184, .25);
        }
        /* 退回文字的那幾項：做成小標籤，才不會跟圖示擠成一團 */
        /* 🐾 狀態列寵物那一格：腳印圖與名字之間留 4px（玩家「太近很醜」）；`.bi-t` 之外的文字清單那一份也一樣 */
        #st-buffs .uic { margin-right: 4px; vertical-align: -0.2em; }
        #st-buffs .bi-t {
            display: inline-flex; align-items: center; height: 22px; padding: 0 6px;
            border-radius: 4px; background: rgba(15, 23, 42, .6);
            box-shadow: inset 0 0 0 1px rgba(148, 163, 184, .25);
            font-size: 10.5px; font-weight: 700; line-height: 1;
        }

        /* ── 段 7：背包格子是**手機專屬的 DOM**（`.itile`）──────────────────
           先前是「桌機清單列 ＋ CSS 把名稱藏起來、壓成方形、四個角落絕對定位」。
           段 5 之後手機版是唯一的版面，那個桌機形狀已經沒有任何用途 ——
           `src/ui/tabs.js` 現在直接產生格子，這裡就只剩「格子長什麼樣」。

           ⚠️ 這一段是**淨刪除**：拿掉的重塑規則比新加的多。 */
        #tab-items:not(.hidden), #tab-weapons:not(.hidden),
        #tab-armors:not(.hidden), #tab-all:not(.hidden) {
            display: grid;
            /* ⚠️ 固定 5 欄，不隨寬度變 —— 欄數一變，同一件東西在不同手機上的位置
                  就不一樣，肌肉記憶會失效，而那正是格狀比清單快的原因。

               ⚠️ 段 12：`grid-auto-rows: max-content` **不可省**，這一條不是風格問題。
                  隱含列的預設是 `auto`，而 `auto` 取的是格子的**自動最小尺寸**——
                  它算不出 `aspect-ratio: 1/1` 推導的高度（欄寬要等 grid 演算法跑完
                  才確定，是循環相依），於是退回用內在寬度（圖示約 48px）。
                  實測：格子 64×64，而它的列只有 **47.8px** —— 每格往下溢出 16px、
                  一路疊加，最後一列被父層的 `overflow: hidden` 裁掉；更糟的是
                  `scrollHeight` 只算 row track，所以整份清單**只捲得動 12px**
                  （玩家回報的「全部分頁還是被截掉」）。
                  `max-content` 讓列去解析那個比例：列高 63.8px、可捲 207px。
                  ⚠️ 四個格狀分頁（道具／武器／防具／全部）都靠這一條。 */
            grid-template-columns: repeat(5, minmax(0, 1fr));
            grid-auto-rows: max-content;
            gap: 4px;
            align-content: start;
            padding: 4px;
            /* 📦 段 47：空白處的淡格線。週期由 `syncBagLattice()` 量出來寫進
               `--bag-cell`（見 src/ui/tabs.js 的說明：格子是 1fr ＋ aspect-ratio，
               寫死 px 會讓線穿過格子）。⚠️ 沒有量到時 fallback 到 0，
               `background-size: 0` 等於不畫 —— 寧可不畫也不要畫歪。 */
            background-image:
                linear-gradient(to right, rgba(148, 163, 184, .055) 1px, transparent 1px),
                linear-gradient(to bottom, rgba(148, 163, 184, .055) 1px, transparent 1px);
            background-size: var(--bag-cell, 0) var(--bag-cell, 0);
            background-position: 4px 4px;
        }
        .itile {
            position: relative;
            aspect-ratio: 1 / 1;
            border: 1px solid #334155;
            border-radius: 9px;
            background: #16203a;
            display: flex;
            align-items: center;
            justify-content: center;
            overflow: hidden;
            cursor: pointer;
        }
        .itile:active { transform: scale(.95); }
        .itile .ico { width: 52px; height: 52px; object-fit: contain; pointer-events: none; }
        /* 狀態用底色表達 —— 文字標籤在 65px 的格子裡放不下 */
        .itile.bad  { background: rgba(69, 10, 10, .55); border-color: #7f1d1d; }
        .itile.done { opacity: .45; }
        .itile.lk   { border-color: #7f1d1d; }
        /* 四個角落 */
        .itile > span {
            position: absolute;
            font-size: 11px;
            font-weight: 700;
            line-height: 1;
            text-shadow: 0 1px 2px #000;
            font-variant-numeric: tabular-nums;
            pointer-events: none;
        }
        .itile .en { left: 4px;  top: 3px;    color: #34d399; }
        .itile .lk { right: 3px; top: 2px;    font-size: 9px; }
        .itile .ct { right: 4px; bottom: 3px; color: #60a5fa; }
        .itile .jk { left: 4px;  bottom: 3px; color: #fbbf24; font-size: 10px; }


        /* ── 段 8：裝備欄卡片是**手機專屬的 DOM**（`.eqc`）────────────────
           先前是「`<span>欄位名</span>` ＋ `<div>全名 ＋ 圖示</div>`」，再用
           `column-reverse` 把圖示挪到名稱上方、把欄位名絕對定位到卡片上緣。
           段 5 之後手機版是唯一的版面，那個桌機形狀沒有用途了 ——
           `src/ui/tabs.js` 直接產生卡片，這裡只剩「卡片長什麼樣」。

           ⚠️ 一格代表**一個欄位**不是一件物品，所以空的欄位也要畫。
              只列有穿的，玩家就看不出「我還有一格沒穿」—— 那是掛機遊戲裡
              最常見的、白白少一截戰力的原因。
           ⚠️ 13 格（含箭矢）在 3 欄下最後一列只剩一格。手機版的設計原型當初
              為了整齊把箭矢拿掉，但真的遊戲裡那一欄是有用的（弓要吃它，而且有
              「耗盡自動購買 1000 銀箭」的設定在跑）—— 為了排版藏掉一個真實的
              欄位不划算。留著，接受最後一列只有一格。 */
        #tab-equip:not(.hidden) {
            display: grid;
            grid-template-columns: repeat(3, minmax(0, 1fr));
            gap: 5px;
            align-content: start;
            padding: 5px;
        }
        .eqc {
            position: relative;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: flex-start;
            gap: 4px;
            min-height: 96px;
            padding: 18px 4px 6px;      /* 上方留給欄位名 */
            border: 1px solid #334155;
            border-radius: 9px;
            background: #16203a;
            cursor: pointer;
            overflow: hidden;
        }
        /* 🦴 移植 18-C：寵物裝備那一格吃滿整列（見 `tabs.js` 的說明）。
           ⚠️ `1 / -1` 在這裡是安全的 —— `#tab-equip` 的三欄是**顯式**格線
              （`grid-template-columns`）；段 14 那個「-1 解析不出來」的坑
              講的是**隱含**的列。 */
        .eqc.wide {
            grid-column: 1 / -1;
            flex-direction: row; justify-content: center; gap: 8px;
            min-height: 62px; padding: 18px 8px 6px;
        }
        .eqc.wide .nm { width: auto; }
        .eqc:active { transform: scale(.97); }
        /* 欄位名：釘在卡片上緣置中 */
        .eqc .sl {
            position: absolute; left: 0; right: 0; top: 4px;
            text-align: center; font-size: 10.5px; color: #77869f;
            letter-spacing: .04em;
        }
        .eqc .ico { width: 30px; height: 30px; object-fit: contain; pointer-events: none; }
        .eqc .nm {
            font-size: 10px; line-height: 1.35; text-align: center;
            word-break: break-all; width: 100%;
        }
        /* 空欄位：虛線框 ＋ 淡化，一眼看得出「這格沒穿」 */
        .eqc.empty { border-style: dashed; border-color: #475569; opacity: .55; cursor: default; }
        .eqc.empty .mt { margin-top: 18px; font-size: 11px; color: #64748b; }
        /* 套裝生效：沿用桌機的翠綠環（isSetActive） */
        .eqc.set {
            border-color: #34d399;
            box-shadow: 0 0 10px rgba(16,185,129,.45), inset 0 0 0 1px rgba(52,211,153,.35);
        }
        /* 角落 */
        .eqc .en, .eqc .lk {
            position: absolute; font-size: 11px; font-weight: 700; line-height: 1;
            text-shadow: 0 1px 2px #000; pointer-events: none;
        }
        .eqc .en { left: 4px; top: 3px; color: #34d399; }
        .eqc .lk { right: 3px; top: 2px; font-size: 9px; }


        /* ── 段 9：村莊（2026-08-27，玩家對照設計稿回報）─────────────────
           段 3 判斷「城鎮零 CSS 改動就全綠」，那是對的 —— 但**只對「不壞掉」而言**。
           玩家要的是設計稿那個樣子：緊湊的標題列、圖示卡片、bottom sheet。

           ⚠️ 段 3 曾寫下「改成 bottom sheet 這件事取消，它要解決的問題不存在」。
              那句話的前提是「互動面板與清單各佔一半」—— 那個問題確實不存在。
              段 9 做 sheet 是**另一個**理由：讓 NPC 清單留在後面當脈絡，
              並把村名／副標／切換收成一列（省掉約 90px）。兩者不衝突。 */
        @media all {
            /* 標題列：名稱＋副標在左，切換在右。
               段 10：城鎮與狩獵區用**同一個**形狀 —— 原本只有城鎮長這樣，狩獵區是
               一條整寬的原生 select，兩者看起來像不同的 app。 */
            #town-head { display: block; flex: 1 1 auto; min-width: 0; }
            #town-head b {
                display: block; font-size: 15px; font-weight: 800; color: #e2e8f0; line-height: 1.25;
                white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
            }
            #town-head span { display: block; font-size: 11px; color: #7c8aa3; line-height: 1.3; }
            /* 「冒險地圖」四個字是多餘的（#town-head 已經寫著現在在哪），但同一個
               span 裡的 #status-alerts 是**狀態**，要留 —— 所以藏字不藏整個 span。 */
            #map-title-t { display: none; }

            /* 「切換 ▾」：標籤是給眼睛看的，被點到的是疊在上面 opacity:0 的 select */
            /* 切換靠右：外層那個 div 是 flex:1（段 3b 給的），不指定的話
               flex:0 的 #map-switch 會黏在**左邊**，右半留一塊空白。 */
            #map-header > div { justify-content: flex-end; flex: 0 0 auto !important; }
            #map-switch {
                display: block; position: relative; flex: 0 0 auto;
                min-width: 84px; min-height: 44px;
            }
            #map-switch > i {
                display: flex; align-items: center; justify-content: flex-end; height: 100%;
                font-style: normal; font-size: 13px; font-weight: 700; color: #d6a94a;
            }
            #map-select {
                position: absolute; inset: 0; width: 100%; height: 100%;
                opacity: 0; padding: 0; border: 0;
            }

            /* ⚠️ 在城鎮裡「村莊」才是主角，但實測是**系統日誌佔 454px、村莊只剩 215px**
                  （5 個 NPC 的第 3 列直接被裁掉）。原因是上面那條
                  `#combat-log-panel.hidden + #sys-log-panel` 給了日誌 `flex: 1 1 auto`
                  ——basis 依內容算——而地圖面板拿的是 Tailwind 的 `flex-1`（basis **0**）。
                  兩者相爭時 basis 大的先分到，主角就被配角擠掉了。
                  這裡直接給定比例，不再讓「誰的內容長」決定誰是主角。
                  ⚠️ 選擇器要 `:first-child`：#col-center 底下有三個 .panel
                     （地圖／戰鬥日誌／系統日誌）。 */
            body[data-mtown] #col-center > .panel:first-child { min-height: 0; }
            /* 🗑️ 段 14：62/38 與 sheet 打開時的 88/12 都不需要了 —— 村莊裡日誌
                  整個收起來（玩家要求），村莊面板是那一欄唯一的東西。 */

            /* 村莊本體 */
            #town-view { position: relative; padding: 0; gap: 0; }
            /* 🏪 2026-09-07（玩家要求「NPC 販賣彈窗拉到狀態欄下面」）：
               sheet 打開時 `#town-view` **讓出「定位祖先」的身分**。
               它的兩個絕對定位子元素（`#town-scrim` 與 `#town-interaction-container`）
               於是改相對 `#map-panel` 定位 —— 而 `#map-panel` 的上緣就在狀態面板
               正下方，所以 sheet 的頂端一口氣往上長了一整個 `#map-header`。
               實測 393×852：sheet 295..787（492px）→ 177..787（**611px**），
               而 `#interaction-content` 的可視區 416 → 535px（茉莉那 10 列從
               看得到 5 列變成 7 列）。
               ⚠️ 用「拿掉 position」而不是搬 DOM：`#town-view` 帶著 `.hidden` 離開
                  村莊時，這兩個子元素是它的**後代**才會一起消失。搬成 `#map-panel`
                  的直接子元素之後就得自己記得關 —— 而漏掉的症狀是「進了狩獵區，
                  NPC 面板還蓋在怪物列上」。
               ⚠️ `#town-view` 底下沒有別的絕對定位元素（清單是 grid、卡片是 static），
                  所以拿掉它不會影響任何其他東西。
               ⚠️ 56px 的手指點擊留白**照舊**（`max-height: calc(100% - 56px)`）——
                  只是現在那 56px 露出來的是地圖列而不是 NPC 清單的第一排。 */
            #town-view.sheet { position: static; }
            #town-npc-container {
                display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
                /* ⚠️ `align-content: start` 不可以少 —— grid 的預設是 stretch，
                      NPC 只有兩位時那兩張卡會被拉成整格那麼高（實測象牙塔上
                      一張卡約 800px）。原本靠 HTML 的 `content-start`，
                      段 9 把 class 收乾淨時漏了補回來。 */
                align-content: start;
                gap: 7px; padding: 8px; background: transparent;
            }
            .npcc {
                display: flex; flex-direction: column; align-items: center; justify-content: center;
                gap: 3px; min-height: 92px; padding: 10px 6px;
                border: 1px solid #334155; border-radius: 10px; background: #16203a;
                cursor: pointer; transition: background .15s, border-color .15s;
            }
            .npcc:active { background: #1e2b4d; border-color: #475569; }
            .npcc .ic { font-size: 26px; line-height: 1.1; color: #A3AAB8; }   /* 🎨 線條圖示，一般色 */
            .npcc .ic .uic { width: 28px; height: 28px; }
            .npcc .nm { font-size: 15px; font-weight: 700; color: #e2e8f0; line-height: 1.2; }
            .npcc .ti { font-size: 11.5px; color: #7c8aa3; line-height: 1.2; text-align: center; }

            /* 暗幕：只蓋住村莊那一塊，不是整頁 —— 狀態列與底部導覽仍然可用 */
            #town-scrim { display: none; position: absolute; inset: 0; z-index: 5; background: rgba(2, 6, 23, .62); }
            #town-view.sheet #town-scrim { display: block; }

            /* ⚠️ sheet 是 position:absolute（相對 #town-view），**不是 fixed**。
                  段 4f 學到的：fixed ＋ dvh 在一般瀏覽器上更新時機不可靠，
                  而底部導覽是排版的一部分 —— 用 absolute 就永遠不可能蓋到它。 */
            #town-interaction-container {
                position: absolute; left: 0; right: 0; bottom: 0; z-index: 6;
                /* ⚠️ 段 14：從 66% 放寬。村莊裡日誌收起來之後 #town-view 吃滿整欄，
                      66% 只剩商店 2 列看得到。留 56px 給手指點空白處關掉 —— 那是
                      bottom sheet 的既有預期，不留就只剩右上角那顆 ✕。 */
                max-height: calc(100% - 56px); min-height: 0;
                background: #131c31; border-top: 1px solid #334155;
                border-radius: 16px 16px 0 0; box-shadow: 0 -10px 26px rgba(0, 0, 0, .5);
            }
            #town-sheet-grab {
                flex: 0 0 auto; width: 44px; height: 4px; margin: 8px auto 2px;
                border-radius: 999px; background: #475569;
            }
            #town-sheet-head {
                flex: 0 0 auto; display: flex; align-items: flex-start; justify-content: space-between;
                gap: 8px; padding: 6px 12px 10px; border-bottom: 1px solid #24304a;
            }
            #town-sheet-head .ttl { display: flex; align-items: baseline; gap: 6px; min-width: 0; }
            #interaction-npc-name { font-size: 17px; font-weight: 800; color: #e2e8f0; }
            #interaction-npc-title { font-size: 13.5px; color: #cbd5e1; }
            #interaction-npc-title::before { content: '·'; margin-right: 6px; color: #475569; }
            #interaction-npc-desc { font-size: 11.5px; color: #7c8aa3; line-height: 1.4; margin-top: 2px; }
            /* ✕ 要 44px —— 它是這個 sheet 唯一的關閉鈕（暗幕是第二條路） */
            #town-sheet-x {
                flex: 0 0 auto; width: 44px; height: 44px; margin: -8px -6px 0 0;
                font-size: 18px; color: #94a3b8; background: none; border: 0; cursor: pointer;
            }
            #town-sheet-x:active { color: #e2e8f0; }
            #interaction-content { padding: 8px 10px 10px; }

            /* ── 段 15：NPC 面板裡的清單列（商店／製作／任務都用同一個形狀）──
               ⚠️ 這幾條是**桌機尺寸從來沒被改過**的最後一處：品名 `text-lg`（18px）、
                  價格 `text-base`（16px）、圖示 48px、列高 **74px** —— 在 393px 寬的
                  sheet 裡一次只看得到 7 列。demo 的規格是 12.5px／48px。
               ⚠️ 只改 `#interaction-content` 底下的 —— `.list-item` 還被市場／血盟／
                  PvP 的清單用著（在 `#col-right`），那些不在這次的範圍。
               ⚠️ 按鈕**不跟著縮到 demo 的 34px**：44px 是這份專案一路守著的觸控下限，
                  而玩家回報的是「字太大」不是「按鈕太大」。 */
            #interaction-content .list-item {
                padding: 6px 8px !important;
                margin-bottom: 6px !important;
                gap: 8px;
            }
            /* 圖示框 48 → 36 */
            #interaction-content .list-item .w-12 { width: 36px !important; height: 36px !important; }
            #interaction-content .list-item .w-10 { width: 30px !important; height: 30px !important; }
            /* 名稱 18 → 13、價格 16 → 11.5、圖與文字的間距 16 → 8 */
            #interaction-content .list-item .text-lg { font-size: 13px !important; }
            #interaction-content .list-item .text-base { font-size: 11.5px !important; }
            #interaction-content .list-item .text-xs { font-size: 10px !important; }
            #interaction-content .list-item .gap-4 { gap: 8px !important; }
            #interaction-content .list-item .gap-1\.5 { gap: 2px !important; }
            /* 按鈕維持 ≥44px，但收窄左右內距 —— 393px 下 px-6 會把名稱擠掉 */
            #interaction-content .list-item button {
                min-height: 44px; padding: 0 14px !important; font-size: 12.5px !important;
            }
            #interaction-content .list-item input[type=number] {
                min-height: 44px; width: 3.2rem !important; font-size: 12.5px !important;
            }

            /* ── 🔨 製作列（2026-09-06，玩家回報「一次只有兩三個項目、不好滑動」）──
               實測：列高 58–113px、容器 284px ＝ **一次 2.5 列**。
               病因是需求那一段跟「數量框 ＋ 兩顆按鈕」搶同一行的寬度 ——
               左邊只剩約 110–184px，而需求字串的中位數就要 230px。
               ⚠️ 「擠成一行」不是選項：材料最多 **8 項**、最長估算 862px，
                  就算吃滿整列（349px）也只有 76% 放得下。
               所以是**兩行**：第一行圖示／名稱／動作，第二行讓需求吃滿整列。 */
            #interaction-content .craft-row {
                display: flex; flex-direction: column; gap: 2px;
                padding: 5px 8px !important; margin-bottom: 5px !important;
                background: #1e293b; border: 1px solid #334155; border-radius: 8px;
                /* ⚠️ `.list-item` 的基底是 `justify-content: space-between` ＋
                      `align-items: center`，那是給**橫排**用的。改成直排之後
                      `align-items: center` 會把兩行都**水平置中** —— 實測名稱
                      左邊留了約 85px 空白，看起來像版面壞了（截圖才看得出來，
                      斷言全綠）。這兩條把它扳回來。 */
                align-items: stretch; justify-content: flex-start;
                border-bottom: 1px solid #334155;   /* 蓋掉 .list-item 的整條底線 */
            }
            #interaction-content .craft-row:hover { background: #24334d; }
            .craft-main { display: flex; align-items: center; gap: 8px; }
            .craft-ic {
                flex: 0 0 auto; width: 34px; height: 34px; border-radius: 6px;
                background: #0f172a; border: 1px solid #334155;
                display: flex; align-items: center; justify-content: center;
            }
            .craft-ic img { width: 28px; height: 28px; object-fit: contain; pointer-events: none; }
            /* ⚠️ `min-width: 0` 不可省 —— flex 子項的預設是「不可以縮到比內容小」，
                  少了它長名稱會把動作那一組推出列外（同 `.scroll-fill` 的道理）。 */
            .craft-nm {
                flex: 1 1 auto; min-width: 0; font-size: 13px; font-weight: 700;
                overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
            }
            .craft-y { color: #facc15; font-size: 11.5px; margin-left: 4px; }
            .craft-act { flex: 0 0 auto; display: flex; align-items: center; gap: 6px; }
            /* 需求：吃滿整列的寬度，字級與商店的價格一致（11.5px） */
            .craft-req { font-size: 11.5px; line-height: 1.35; color: #94a3b8; }
            .craft-req .craft-q { white-space: nowrap; }
            .craft-req .ok { color: #4ade80; font-weight: 700; }
            .craft-req .no { color: #f87171; font-weight: 700; }
            .craft-plus { color: #475569; margin: 0 4px; }
            .craft-empty { font-size: 12.5px; color: #94a3b8; padding: 10px 2px; }
        }


        /* ── 段 10：逐頁對照 demo（2026-08-27）─────────────────────────────
           玩家把原型與實作並排看，結論是「還是不一樣」。逐頁比對之後，差異
           **不是**某一頁沒做完，而是幾個貫穿全部四頁的形狀。 */
        @media all {
            /* 1) 「全部」分頁上的排序指示器（demo 的 .si）。
                  已經在那一頁時再按一次＝一鍵排列，見 mobileBagAll()。 */
            #tabbar .si { margin-left: 5px; font-size: 12.5px; opacity: .45; font-weight: 400; }
            #tabbar button.active .si { opacity: 1; }

            /* 🗑️ 2026-09-07：賣出廢品的浮動鈕（`#m-fab-sell`）也移除了（玩家要求）——
               **整個 app 現在沒有任何浮動鈕**。它的工作被兩件事接手：
                 ①進狩獵區時自動賣（規則在 `src/engine/travel.js`）
                 ②設定 → 物品自動刪除的「立即賣出廢品」
               浮動鈕是 `position: fixed`、**不佔版面**也就不知道底下有什麼 ——
               那正是段 56 把快速換裝那顆拿掉的理由，這顆是同一個問題。 */

            /* 3) 怪物卡：加上等級與剩餘 HP 數字，並把「正在打誰」改成琥珀色。
                  ⚠️ 原本目標是**紅色**框 —— 而紅色在這個畫面裡到處都是（血條、
                     受傷、危險），等於沒有標。琥珀色是全 app 的「選中」語彙
                     （底部導覽、分頁底線都是它）。 */
            .mob-lv { text-align: center; font-size: 9.5px; color: #94a3b8; margin: -4px 0 4px; }
            .mob-hpn { text-align: center; font-size: 9px; color: #94a3b8; margin-top: 3px; }
            .mob-target.active { border-color: #fbbf24 !important; background: #0f172a !important;
                box-shadow: 0 0 12px rgba(251, 191, 36, .18); }

            /* 4) 頁內篩選（排行榜的 等級／財富、市場的 市場／我的寄售／信箱）。
                  原本是 `.btn` 的**藍色填色大按鈕**，44px 一列 —— 與上方的分頁列
                  長得像兩套系統。改成 demo 的膠囊篩選：38px、選中才上琥珀色。 */
            /* 📱 段 32：選中從「整塊填滿琥珀」改成「淡底 ＋ 琥珀邊 ＋ 琥珀字」。
               ⚠️ 形狀**維持**膠囊 —— 段 10 讓它與上方的底線分頁不同形狀是對的
                  （長得一樣會讓人以為按了會換頁）。改的是**份量**不是形狀。
               ⚠️ 問題在階層被壓反了：每個分頁畫面都是「頁層級的底線分頁」正上方
                  壓著這排篩選，而實心琥珀在暗底上是整個畫面最亮的東西，
                  頁層級卻只有一條 2px 底線 —— 掃過去眼睛先被**從屬**的那排抓走。
               ⚠️ 琥珀仍然是「選中」的語彙（底部導覽、分頁底線、選中的怪都是它），
                  只是改用**邊框與文字**承載，把實心填色留給真正的主要動作按鈕。
               ⚠️ 未選中的邊框也從 #475569 收到 #334155（＝分頁列底線的顏色）：
                  三顆等重的描邊膠囊並排本身就吵，選中的那顆才浮得出來。 */
            .seg {
                flex: 1; min-width: 0; height: 38px; cursor: pointer;
                background: #0f1a2e; border: 1px solid #334155; color: #94a3b8;
                border-radius: 7px; font-family: inherit; font-size: 11.5px; font-weight: 700;
                white-space: nowrap; overflow: hidden; text-overflow: ellipsis; padding: 0 6px;
            }
            .seg.active { background: #14213a; border-color: #fbbf24; color: #fbbf24; }

            /* 🗑️ 設定頁的兩個分頁（2026-09-07）。它取代了原本的 `panel-header`，
               所以左右內距要對齊底下的 `.sgrp`，不然看起來像浮在外面的一條。 */
            #set-tabs { display: flex; gap: 6px; flex: 0 0 auto; padding: 8px 10px; }

            /* 「物品自動刪除」的一列。與掛機收穫的 `.awrow`／確認畫面的 `.ckrow`
               同尺寸 —— 同一種清單長一樣才對。 */
            .jklist { padding: 0; }
            .jkrow {
                display: flex; align-items: center; gap: 10px; min-height: 44px;
                padding: 4px 2px; border-bottom: 1px solid #263449;
            }
            .jkrow:last-child { border-bottom: 0; }
            .jkrow img { width: 28px; height: 28px; object-fit: contain; flex: 0 0 auto; }
            .jkrow .jkn { flex: 1; min-width: 0; font-size: 13px;
                          overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
            .jkrow .jkc { flex: 0 0 auto; font-size: 11px; color: #94a3b8;
                          font-variant-numeric: tabular-nums; }
            /* ⚠️ 44px 是這份專案一路守著的觸控下限（段 15） */
            .jkrow .m-act { flex: 0 0 auto; min-height: 36px; height: 36px; padding: 0 14px; font-size: 12.5px; }
            .jkempty { font-size: 12.5px; color: #94a3b8; line-height: 1.8; padding: 6px 2px; }

            /* 5) 日誌分段本來往內縮 10px，與底下的日誌面板對不齊，看起來像兩個
                  不相干的框。demo 是同一個框的上緣 —— 左右先對齊。 */
            #m-logtabs { border-radius: 0; }

            /* 6) 能力值：桌機的 text-lg ＋ p-5 ＋ gap-5 在 393px 下一列就 80px，
                  六個主屬性就吃掉半個畫面（實測要捲三頁才看得完）。
                  demo 的 .statgrid 是 12.5px 兩欄。 */
            #tab-stats { padding: 10px 12px !important; gap: 10px !important; }
            #tab-stats > div { font-size: 12.5px !important; gap: 4px 12px !important; }

            /* ── 段 14：切換地點的 drawer ── */
            #m-mapsheet {
                display: none; flex-direction: column;
                position: fixed; bottom: var(--vp-cut); z-index: 47;
                left: max(0px, calc(50% - 250px)); right: max(0px, calc(50% - 250px));
                /* 📱 段 50：74% → 幾乎全高。實測 74% 時可視 552px、一次只看得到
                   12 列，而清單有 68 列（內容 3,203px ＝ 要捲 5.8 個畫面）。
                   ⚠️ 保留 56px 是給手指點空白處關掉用的（同城鎮 sheet 的既有預期），
                      不留就只剩右上角那顆 ✕。 */
                max-height: calc(100% - 56px);
                background: #1e293b; border-top: 1px solid #475569;
                border-radius: 14px 14px 0 0;
                box-shadow: 0 -12px 34px rgba(0, 0, 0, .6);
                transform: translateY(102%);
                transition: transform .22s ease;
                padding-bottom: env(safe-area-inset-bottom);
            }
            /* ⚠️ 用 `display: flex` ＋ transform，不是只切 display —— 只切 display
                  的話滑入動畫不會播（元素是從 none 直接出現的）。 */
            body[data-msheet="map"] #m-mapsheet { display: flex; transform: translateY(0); }

            /* ── 📦 掛機收穫的 drawer（2026-09-01）──
               形狀與切換地點那個一致（同一套拉柄／標題／可捲內容的樣式）。
               ⚠️ `max-height` 同樣留 56px 給手指點空白處關掉。 */
            #m-awaysheet {
                display: none; flex-direction: column;
                position: fixed; bottom: var(--vp-cut); z-index: 47;
                left: max(0px, calc(50% - 250px)); right: max(0px, calc(50% - 250px));
                max-height: calc(100% - 56px);
                background: #1e293b; border-top: 1px solid #475569;
                border-radius: 14px 14px 0 0;
                box-shadow: 0 -12px 34px rgba(0, 0, 0, .6);
                transform: translateY(102%);
                transition: transform .22s ease;
                padding-bottom: env(safe-area-inset-bottom);
            }
            body[data-msheet="away"] #m-awaysheet { display: flex; transform: translateY(0); }
            /* 🩹 段 42：**可捲的那一層要自己有不透明底色** —— 它是
               `overflow-y: auto`，而外層帶著 transform ＋ transition（滑入動畫），
               兩層各自重繪時中間幾個影格會透出底下的暗幕與場景圖。
               色碼要與外層一致（都是 #1e293b），否則捲動時會看到色差。 */
            #m-awaysheet-body {
                flex: 1 1 auto; min-height: 0; overflow-y: auto; overflow-x: hidden;
                background: #1e293b; padding: 4px 12px 12px;
            }
            /* 一列＝一種物品。⚠️ 圖示固定尺寸，否則每一列高度會跟著圖跳。 */
            .awrow {
                display: flex; align-items: center; gap: 10px;
                min-height: 44px;                      /* 全 app 的觸控下限（這裡不可點，但視覺一致） */
                padding: 4px 2px; border-bottom: 1px solid #263449;
                background: #1e293b;
            }
            .awrow:last-child { border-bottom: 0; }
            .awrow img { width: 30px; height: 30px; flex: 0 0 auto; object-fit: contain; }
            .awrow .awn { flex: 1 1 auto; min-width: 0; font-size: 13px;
                          overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
            .awrow .awc { flex: 0 0 auto; font-size: 13px; font-weight: 700;
                          font-variant-numeric: tabular-nums; color: #e2e8f0; }
            /* 金幣那一列釘在最上面：它是掛機收穫裡唯一「一定有」的東西 */
            .awrow.gold .awn, .awrow.gold .awc { color: #facc15; }
            #m-awaysheet-body .awempty { font-size: 12.5px; color: #94a3b8; padding: 10px 0; }

            /* ── 🎁 世界王討伐結果的 drawer（段 4b，2026-09-09）── */
            #m-wbsheet {
                display: none; flex-direction: column;
                position: fixed; bottom: var(--vp-cut); z-index: 47;
                left: max(0px, calc(50% - 250px)); right: max(0px, calc(50% - 250px));
                max-height: calc(100% - 56px);
                background: #1e293b; border-top: 1px solid #475569;
                border-radius: 14px 14px 0 0;
                box-shadow: 0 -12px 34px rgba(0, 0, 0, .6);
                transform: translateY(102%);
                transition: transform .22s ease;
                padding-bottom: env(safe-area-inset-bottom);
            }
            body[data-msheet="wb"] #m-wbsheet { display: flex; transform: translateY(0); }
            /* 🩹 同段 42：可捲的那一層要自己有不透明底色 */
            #m-wbsheet-body {
                flex: 1 1 auto; min-height: 0; overflow-y: auto; overflow-x: hidden;
                background: #1e293b; padding: 4px 12px 12px;
            }
            #m-wbsheet-grip { width: 38px; height: 4px; border-radius: 999px; background: #475569; margin: 8px auto 4px; flex: 0 0 auto; }
            #m-wbsheet-head {
                display: flex; align-items: center; justify-content: space-between;
                gap: 8px; padding: 2px 12px 8px; font-size: 15px; font-weight: 700;
                color: #f8fafc; border-bottom: 1px solid #24304a; flex: 0 0 auto;
            }
            #m-wbsheet-head small { display: block; font-size: 11px; color: #94a3b8; font-weight: 400; margin-top: 2px; }
            #m-wbsheet-head button {
                flex: 0 0 auto; min-width: 44px; min-height: 44px;
                background: none; border: 0; color: #94a3b8; font-size: 18px; cursor: pointer;
            }
            /* 分組標題（「我的收穫」／「全部分配」）*/
            #m-wbsheet-body .wbgh {
                font-size: 11.5px; color: #94a3b8; font-weight: 700;
                padding: 10px 2px 4px; position: sticky; top: 0; background: #1e293b; z-index: 2;
            }
            /* 一位參與者。⚠️ 直排：拿到的東西可能有好幾樣，擠成一行會被截掉 */
            .wbrow {
                display: flex; flex-direction: column; align-items: stretch; gap: 2px;
                padding: 6px 2px; border-bottom: 1px solid #263449; background: #1e293b;
            }
            .wbrow:last-child { border-bottom: 0; }
            .wbrow.me { background: #1c2b3f; }
            .wbrow .wbtop { display: flex; align-items: baseline; gap: 8px; }
            .wbrow .wbn { flex: 1 1 auto; min-width: 0; font-size: 13px; font-weight: 700; color: #e2e8f0;
                          overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
            .wbrow.me .wbn { color: #4ade80; }
            .wbrow .wbd { flex: 0 0 auto; font-size: 11.5px; color: #94a3b8; font-variant-numeric: tabular-nums; }
            .wbrow .wbg { font-size: 12.5px; color: #cbd5e1; line-height: 1.55; }
            .wbrow .wbg .none { color: #64748b; }

            /* ── 🧩 一次做完的確認 drawer（2026-09-06）──
               形狀沿用掛機收穫那一個，只多一條**釘在底部**的動作列。 */
            /* ── 🎓 技能說明卡的 drawer（移植 20）──
               形狀與「一次做完」那個一致（拉柄／標題／可捲內容／**釘底動作列**）。
               ⚠️ 上游那一份是 hover tooltip，而我們沒有 hover —— 段 5 之後
                  手機版是唯一的版面。 */
            #m-skillsheet {
                display: none; flex-direction: column;
                position: fixed; bottom: var(--vp-cut); z-index: 47;
                left: max(0px, calc(50% - 250px)); right: max(0px, calc(50% - 250px));
                max-height: calc(100% - 56px);
                background: #1e293b; border-top: 1px solid #475569;
                border-radius: 14px 14px 0 0;
                box-shadow: 0 -12px 34px rgba(0, 0, 0, .6);
                transform: translateY(102%);
                transition: transform .22s ease;
                padding-bottom: env(safe-area-inset-bottom);
            }
            body[data-msheet="skill"] #m-skillsheet { display: flex; transform: translateY(0); }
            #m-skillsheet-grip { width: 38px; height: 4px; border-radius: 999px;
                                 background: #475569; margin: 8px auto 4px; flex: 0 0 auto; }
            #m-skillsheet-head {
                display: flex; align-items: center; justify-content: space-between;
                gap: 8px; padding: 2px 12px 8px; border-bottom: 1px solid #334155;
                font-size: 15px; font-weight: 700; flex: 0 0 auto;
            }
            #m-skillsheet-head button {
                flex: 0 0 auto; min-width: 44px; min-height: 44px; margin: -8px -8px 0 0;
                background: none; border: 0; color: #94a3b8; font-size: 18px;
            }
            /* 🩹 段 42：可捲的那一層要自己有不透明底色 */
            #m-skillsheet-body {
                flex: 1 1 auto; min-height: 0; overflow-y: auto; overflow-x: hidden;
                background: #1e293b; padding: 10px 12px 12px;
            }
            #m-skillsheet-actions {
                flex: 0 0 auto; display: grid; grid-template-columns: 1fr 1fr; gap: 8px;
                padding: 10px 12px; border-top: 1px solid #334155; background: #1e293b;
            }
            #m-skillsheet-actions:empty { display: none; }
            /* 只有一顆「知道了」時佔滿整列（同 `#m-craftsheet-actions .m-act.span2`）*/
            #m-skillsheet-actions .m-act:only-child { grid-column: 1 / -1; }
            .sktip-top { display: flex; align-items: flex-start; gap: 10px; }
            .sktip-ico { width: 40px; height: 40px; flex: 0 0 auto; object-fit: contain; }
            .sktip-top > div { flex: 1 1 auto; min-width: 0; }
            .sktip-kind { font-size: 11px; color: #94a3b8; margin-bottom: 4px; }
            .sktip-meta { font-size: 12.5px; color: #cbd5e1; line-height: 1.6; }
            /* 效果用暖色：它是「這個技能到底做什麼」，是整張卡唯一要一眼抓到的東西 */
            .sktip-eff { font-size: 12.5px; color: #fda4af; line-height: 1.7; margin-top: 4px; }
            .sktip-desc { font-size: 12px; color: #cbd5e1; margin-top: 6px; line-height: 1.5; }
            .sktip-msg { font-size: 11.5px; color: #94a3b8; margin-top: 6px; font-style: italic; }
            .sktip-note { font-size: 11.5px; color: #fbbf24; margin-top: 6px; }

            #m-craftsheet {
                display: none; flex-direction: column;
                position: fixed; bottom: var(--vp-cut); z-index: 47;
                left: max(0px, calc(50% - 250px)); right: max(0px, calc(50% - 250px));
                max-height: calc(100% - 56px);
                background: #1e293b; border-top: 1px solid #475569;
                border-radius: 14px 14px 0 0;
                box-shadow: 0 -12px 34px rgba(0, 0, 0, .6);
                transform: translateY(102%);
                transition: transform .22s ease;
                padding-bottom: env(safe-area-inset-bottom);
            }
            body[data-msheet="craft"] #m-craftsheet { display: flex; transform: translateY(0); }
            #m-craftsheet-grip { width: 38px; height: 4px; border-radius: 999px;
                                 background: #475569; margin: 8px auto 4px; flex: 0 0 auto; }
            #m-craftsheet-head {
                display: flex; align-items: flex-start; justify-content: space-between;
                gap: 8px; padding: 2px 12px 8px; border-bottom: 1px solid #334155;
                font-size: 15px; font-weight: 700; flex: 0 0 auto;
            }
            #m-craftsheet-head small { display: block; font-size: 11px; color: #94a3b8;
                                       font-weight: 400; margin-top: 2px; }
            #m-craftsheet-head button {
                flex: 0 0 auto; min-width: 44px; min-height: 44px; margin: -8px -8px 0 0;
                background: none; border: 0; color: #94a3b8; font-size: 18px;
            }
            /* 🩹 段 42：可捲的那一層要**自己**有不透明底色（見 #m-mapsheet-body）。 */
            #m-craftsheet-body {
                flex: 1 1 auto; min-height: 0; overflow-y: auto; overflow-x: hidden;
                background: #1e293b; padding: 4px 12px 12px;
            }
            /* 分節標題：「將製作」「將消耗」「缺少」 */
            #m-craftsheet-body .ckh {
                font-size: 11.5px; color: #94a3b8; font-weight: 700;
                margin: 10px 0 2px; letter-spacing: .04em;
            }
            #m-craftsheet-body .ckh:first-child { margin-top: 2px; }
            /* 🔴 「你要 18 個，這次只做得到 7 個」的說明（2026-09-07，玩家回報）。
               ⚠️ 用**琥珀**不用紅：沒有任何東西失敗，只是做得比要求的少 ——
                  紅框會讓人以為按下去會出事（同段 28「壞消息才上紅框」的判準）。
               ⚠️ 它在 `.ckh:first-child` 前面，所以那條 `margin-top: 2px` 不再命中
                  第一個 `.ckh` —— 補一條讓間距維持原樣。 */
            #m-craftsheet-body .ckcut {
                font-size: 12px; line-height: 1.55; color: #fde68a;
                background: rgba(251, 191, 36, .10);
                border: 1px solid rgba(251, 191, 36, .45);
                border-radius: 8px; padding: 8px 10px; margin: 2px 0 4px;
            }
            #m-craftsheet-body .ckcut + .ckh { margin-top: 6px; }
            /* 一列＝一種物品。與掛機收穫的 .awrow 同尺寸（同一種清單，長一樣才對） */
            .ckrow {
                display: flex; align-items: center; gap: 10px; min-height: 40px;
                padding: 3px 2px; border-bottom: 1px solid #263449; background: #1e293b;
            }
            .ckrow:last-child { border-bottom: 0; }
            .ckrow img { width: 28px; height: 28px; flex: 0 0 auto; object-fit: contain; }
            .ckrow .ckn { flex: 1 1 auto; min-width: 0; font-size: 13px;
                          overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
            .ckrow .ckc { flex: 0 0 auto; font-size: 13px; font-weight: 700;
                          font-variant-numeric: tabular-nums; color: #e2e8f0; }
            .ckrow.gold .ckn, .ckrow.gold .ckc { color: #facc15; }
            .ckrow.lack .ckn, .ckrow.lack .ckc { color: #f87171; }
            /* 最後的成品標一下 —— 那一列才是玩家真正要的東西 */
            .ckrow.goal { background: #202f47; }
            .ckrow.goal .ckc { color: #fbbf24; }
            /* ⚠️ 動作列**不跟著內容捲**（同段 18 物品 dialog）：清單可以很長
               （法丘 21 道工序），捲在一起的話「開始製作」會被推到看不見的地方。 */
            #m-craftsheet-actions {
                flex: 0 0 auto; display: grid; grid-template-columns: 1fr 1fr; gap: 8px;
                padding: 10px 12px; border-top: 1px solid #334155; background: #1e293b;
            }
            #m-craftsheet-actions:empty { display: none; }
            /* 只有一顆按鈕時佔滿整列（同 `#modal-actions .m-act.span2`）。
               ⚠️ 這條**不可以**省成 `:has(:only-child)` —— 那是新語法，
                  而退路是「一顆按鈕縮在左半邊」，看起來像版面壞了。 */
            #m-craftsheet-actions .m-act.span2 { grid-column: 1 / -1; }
            /* 🩹 段 42：**可捲的那一層要自己有不透明底色。**
               玩家回報「捲動切換地區的 drawer 時會閃一下看起來像背景圖的東西」。
               機制：
                 · `#m-mapsheet-body` 與 `.mrow` 原本都是**完全透明**的
                   （`rgba(0,0,0,0)`），底色靠外層的 `#m-mapsheet` 畫。
                 · 但它是 `overflow-y: auto` 又內含 `position: sticky` 的
                   分組標題（`.gh`）—— 這種組合在 Chromium 會被提升成獨立的
                   合成層，而外層的 `#m-mapsheet` 自己也帶著 `transform` ＋
                   `transition`（滑入動畫），同樣是一層。
                 · 兩層各自重繪，中間就會有幾個影格「誰都還沒畫」，
                   於是看到的是**再底下那一層**：暗幕只有 60% 不透明
                   （`rgba(2,6,23,.6)`），所以透出來的正是地圖的場景圖。
               ⚠️ 段 37 之前底下是一塊純色，就算漏也看不出來 —— 是**場景背景**
                  讓這個一直存在的透明破口變得看得見。
               ⚠️ 色碼要與 `#m-mapsheet` 一致，否則捲動時會看到色差。
               ⚠️ 快速換裝那個 sheet 是同一個結構，一起補（它同樣浮在場景上）。 */
            #m-mapsheet-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; overflow-x: hidden;
                               padding: 4px 0 10px; background: #1e293b; }
            #m-mapsheet-body .gh {
                padding: 9px 14px 5px; font-size: 11px; font-weight: 700; color: #fbbf24;
                position: sticky; top: 0; background: #1e293b;
            }
            #m-mapsheet-body .mrow {
                display: flex; align-items: center; justify-content: space-between; gap: 8px;
                width: 100%; min-height: 46px; padding: 10px 14px;
                background: none; border: 0; border-bottom: 1px solid #24304a;
                color: #e2e8f0; font-family: inherit; font-size: 13px; text-align: left; cursor: pointer;
            }
            #m-mapsheet-body .mrow:active { background: #223050; }

            /* ── 段 50：篩選 ／ 收藏 ／「★ 適合」──────────────────────────
               ⚠️ 用**既有的 `.seg`**（排行榜與市場的頁內篩選就是它），不另外發明
                  一套膠囊：段 10 的判斷是「不同層級的東西不要長得一樣」，
                  反過來說同一個層級的東西就該長得一樣。 */
            #m-mapsheet-body .mchips {
                /* 🗼 移植 15-③：`flex-wrap: wrap` —— 第 6 個膠囊（傲慢之塔）放不下時
                   **折行**而不是橫向溢位。⚠️ 先前是 `display: flex` 沒有 wrap，
                   所以任何人加第 6 個都會把這一列撐破，而症狀是「版面壞掉」
                   不是「有捲軸」（段 50 刻意不讓這一列橫向捲）。 */
                display: flex; flex-wrap: wrap; gap: 7px; padding: 9px 12px 11px;
                position: sticky; top: 0; z-index: 3; background: #1e293b;
                border-bottom: 1px solid #24304a;
            }
            /* 🗺️ 2026-09-15：「狩獵」的子分頁（野外／地監／特殊）。整寬折成第二行，
               用一條細線與上一層隔開 —— 兩層長得一模一樣的話看不出誰管誰。 */
            #m-mapsheet-body .mchips .msub {
                flex: 1 1 100%; display: flex; flex-wrap: wrap; gap: 7px;
                padding-top: 8px; border-top: 1px dashed #334155;
            }
            #m-mapsheet-body .mempty {
                padding: 26px 18px; text-align: center; color: #7c8aa3; font-size: 12.5px;
                line-height: 1.8;
            }
            #m-mapsheet-body .gh i { font-style: normal; font-weight: 400; opacity: .6; }

            /* ⚠️ 收藏鈕是**另一顆按鈕**（`<button>` 不可以巢狀），所以每一列外面
               包一層 flex；`.mrow` 讓出右邊 44px 給它。 */
            #m-mapsheet-body .mrow-wrap {
                display: flex; align-items: stretch;
                border-bottom: 1px solid #24304a;
            }
            #m-mapsheet-body .mrow-wrap .mrow { border-bottom: 0; flex: 1 1 auto; min-width: 0; }
            #m-mapsheet-body .mfav {
                flex: 0 0 auto; width: 44px; min-height: 46px;
                background: none; border: 0; border-left: 1px solid #1c2740;
                color: #475569; font-size: 17px; line-height: 1; cursor: pointer;
            }
            #m-mapsheet-body .mfav.on { color: #fbbf24; }
            #m-mapsheet-body .mfav:active { background: #223050; }
            /* ★ 適合：跟在名稱後面，因為它講的是**這張圖**適不適合，不是右邊那個數字 */
            #m-mapsheet-body .mrow .fit {
                margin-left: 7px; font-size: 10.5px; color: #fbbf24; font-weight: 700;
                white-space: nowrap;
            }
            /* 明顯打不動：整列變暗，但**不隱藏** —— 玩家有權去送死，只是要先看到。
               ⚠️ 只用「變暗」一個訊號，**不要**再把建議等級染紅：門檻是
                  `mid > lv + 8`，於是 Lv.9 與 Lv.10 兩列會一個正常一個紅色，
                  差一級卻看起來像出了錯 —— 而紅色在這個 app 裡是錯誤色，
                  「這張圖比你高」不是錯誤。 */
            #m-mapsheet-body .mrow.dim .nm,
            #m-mapsheet-body .mrow.dim .rt { opacity: .42; }
            /* 📱 段 36：目前所在改用**左邊一條琥珀色標記 ＋ 淡底**，不再把整列
               的文字染成琥珀。
               ⚠️ 原本 `.mrow.on` 是 `color:#fbbf24; font-weight:700` —— 與群組標題
                  `.gh` 的顏色與字重**一模一樣**，於是「妖精森林」看起來像第三個
                  群組標題而不是「我在這裡」，清單的階層整個被打斷。
               ⚠️ 名稱維持一般字色：一列裡只該有一個地方在喊，而那是右邊的「目前」。 */
            #m-mapsheet-body .mrow.on {
                background: #16233d;
                box-shadow: inset 3px 0 0 #fbbf24;
            }
            /* 右側：建議等級 ＋「目前」。⚠️ `flex: 0 0 auto` 不可省 ——
               地圖名稱長短差很多（「新兵修練場」vs「傲慢之塔91~100樓」），
               不固定的話等級會跟著名稱浮動，整排對不齊。 */
            #m-mapsheet-body .mrow .rt {
                flex: 0 0 auto; display: flex; align-items: center; gap: 8px;
            }
            /* 🌍 段 39：地圖人數。跟在地圖名稱後面（不是右側）——
               右側那一格是「建議等級／目前」，那是**選圖的判斷依據**；
               人數是氛圍，混進去會讓右側變成兩件事。 */
            #m-mapsheet-body .mrow .ppl {
                margin-left: 7px; font-size: 11px; color: #7c8aa5;
                font-variant-numeric: tabular-nums;
            }
            #m-mapsheet-body .mrow.on .ppl { color: #94a3b8; }
            /* 🐲 段 2：純 BOSS 房的狀態。活著＝琥珀（全 app 的「可以」語彙）；還沒出現＝灰。 */
            #m-mapsheet-body .mrow .wb {
                margin-left: 7px; font-size: 11px; font-variant-numeric: tabular-nums;
                border-radius: 4px; padding: 1px 5px; white-space: nowrap;
            }
            #m-mapsheet-body .mrow .wb.on  { color: #0f172a; background: #fbbf24; font-weight: 700; }
            #m-mapsheet-body .mrow .wb.off { color: #94a3b8; background: #1e293b; border: 1px solid #334155; }
            #m-mapsheet-body .mrow .lv {
                font-size: 11.5px; color: #94a3b8; font-variant-numeric: tabular-nums;
                white-space: nowrap;
            }
            #m-mapsheet-body .mrow.on .lv { color: #cbd5e1; }
            #m-mapsheet-body .mrow .cur {
                flex: 0 0 auto; font-size: 10px; font-weight: 700; color: #0f172a;
                background: #fbbf24; border-radius: 4px; padding: 2px 6px; white-space: nowrap;
            }
            /* 「切換 ▾」現在是一顆真的按鈕，select 只留著當資料來源與換圖入口 */
            #map-select { display: none !important; }
            #map-switch { cursor: pointer; }

            /* 📱 段 37：地圖的場景背景。
               ⚠️ 圖由 `applyMapScene()` 設在 inline style 上，這裡只負責「怎麼鋪」。
               ⚠️ 一定要壓暗：場景是 1920×1080 的插畫，亮度與細節都遠高於 UI，
                  不壓的話怪物卡、血條、NPC 名稱全部會糊在裡面。用 `::before`
                  疊一層而不是把漸層混進 background-image —— 那樣 `background-size:
                  cover` 會同時套到漸層上，換圖時漸層也跟著重繪、會閃一下。
               ⚠️ `z-index` 要讓內容浮在遮罩上，否則整個面板會被蓋住看不見。 */
            #map-panel {
                position: relative;
                background-size: cover;
                background-position: center center;
                background-repeat: no-repeat;
            }
            #map-panel::before {
                content: '';
                position: absolute; inset: 0;
                background: linear-gradient(180deg, rgba(9,15,28,.38) 0%, rgba(9,15,28,.66) 100%);
                border-radius: inherit;
                pointer-events: none;
                z-index: 0;
            }

            /* 📱 段 38：村莊的場景帶 ＋ 連續漸層。
               ⚠️ 為什麼村莊要另一套：實測 12 個 NPC 的妖精森林，卡片蓋掉可見區的
                  **84%**，而且露出來的是 12 條互不相連的縫 —— 場景等於白載。
                  狩獵區同樣是 84%，但那是一排卡片配連續的環繞留白，讀得出是什麼
                  地方，所以**狩獵區維持原本的遮罩、一個像素都不動**。
               ⚠️ 也**不能**把狩獵區一起放亮：實測沙漠那種亮圖放亮之後，狼幾乎
                  融進沙地裡、「搜尋中…」也糊掉。怪物卡只有 62% 不透明
                  （NPC 卡是 78%），對遮罩敏感得多。 */
            #map-scene-band { display: none; }
            body[data-mtown] #map-scene-band { display: block; height: 132px; flex: 0 0 auto; }
            /* 🔨 2026-09-06：**NPC 面板打開時把場景帶收起來**（+132px 給清單）。
               段 38 留這 132px 是為了「NPC 卡片蓋掉 84% 的可見區，要有一塊純場景
               看得到」—— 而 sheet 打開時場景整個被 sheet ＋ 暗幕蓋住，那塊留白
               一個像素都沒有在做事，卻佔掉村莊可用高度的三分之一。
               實測：`#interaction-content` 從 **284 → 416px**。
               ⚠️ 寫成「有 attribute 才收起來」是安全的方向：`initMobileNav()`
                  沒跑到或哪條路徑忘了設，退回的是**今天的樣子**（段 3b 的判準是
                  「沒有那個 attribute 時也要有正確的預設」，這裡正好成立）。 */
            body[data-mnpc] #map-scene-band { display: none; }

            /* ⚠️ px 停止點是從**面板頂端**算的：地圖列約 58px，場景帶 132px，
                  所以第一列 NPC 卡片大約落在 190px。漸層必須在 190px **之前**
                  就變暗 —— 不然第一列卡片會坐在很亮的場景上，副標的灰字讀不到。
                  於是深化發生在場景帶自己的下半段（120→190），視覺上是場景由上
                  往下自然沒入清單，沒有任何一條切線。 */
            body[data-mtown] #map-panel::before {
                background: linear-gradient(180deg,
                    rgba(9,15,28,.10) 0px,
                    rgba(9,15,28,.12) 120px,
                    rgba(9,15,28,.70) 190px,
                    rgba(9,15,28,.86) 100%);
            }
            /* ⚠️ `position: relative` 不可省 —— 沒有它這幾層還是會被 ::before 蓋掉 */
            #map-panel > * { position: relative; z-index: 1; }

            /* ⚠️ 卡片原本是不透明的（`.npcc` #16203a、`.mob-target` #0f172a），
                  鋪上場景之後它們會把場景整片蓋掉 —— 村莊裡實測只剩卡片之間的
                  細縫看得到圖，等於白載了 500KB。改成半透明 ＋ 背景模糊：
                  場景透出來，而字仍然壓在一層夠暗的底上讀得到。
               ⚠️ 只在**有場景時**才半透明（`[data-scene]` 且不是空字串）——
                  沒有場景圖時半透明會讓卡片浮在純色上，邊界糊掉。 */
            #map-panel[data-scene]:not([data-scene=""]) .npcc {
                background: rgba(22, 32, 58, .78);
                backdrop-filter: blur(2px);
            }
            #map-panel[data-scene]:not([data-scene=""]) .npcc:active {
                background: rgba(30, 43, 77, .88);
            }
            #map-panel[data-scene]:not([data-scene=""]) .mob-target {
                background: rgba(15, 23, 42, .62);
                backdrop-filter: blur(2px);
            }
            #map-panel[data-scene]:not([data-scene=""]) .mob-target.active {
                background: rgba(69, 10, 10, .72);
            }
            /* ⚠️ 空格子帶著 `opacity-50`，連背景一起被減半（實測有效底色只剩約
                  31%）—— 於是「搜尋中…」直接糊進樹葉裡。用 `[data-sig]` 精準
                  補回來，不要去碰那個 Tailwind 的 `!border-dashed` class。 */
            #map-panel[data-scene]:not([data-scene=""]) .mob-target[data-sig="empty"] {
                background: rgba(15, 23, 42, .9);
            }

            /* ── 段 14：村莊裡不顯示日誌 ── */
            /* ⚠️ 段 9 曾刻意留兩行「讓買完東西的訊息看得到」，玩家明確要求拿掉：
                  村莊要看的是 NPC，戰鬥日誌在那裡本來就是空的。
                  切回狩獵區時日誌自己會回來（`renderMapView()` 拿掉 .hidden）。 */
            body[data-mtown] #sys-log-panel { display: none !important; }
            body[data-mtown] #col-center > .panel:first-child { flex: 1 1 auto; }

            /* 7) 段 13：裝備中的東西也會出現在武器／防具／全部的格線裡 ——
                  金框 ＋ 左下角 ★。⚠️ 用**外框**而不是底色：底色已經被
                  「不能裝備」（暗紅）與「已學會」（變暗）用掉了，再疊一層
                  就分不出哪個是哪個。 */
            .itile.eqd {
                border-color: #fbbf24;
                box-shadow: inset 0 0 0 1px rgba(251, 191, 36, .55);
            }
            .itile .st {
                position: absolute; left: 4px; bottom: 2px;
                font-size: 11px; line-height: 1; color: #fbbf24;
                text-shadow: 0 1px 2px #000; pointer-events: none;
            }
        }


        /* ── 段 22：Toast 通知 ────────────────────────────────────────────
           動作回饋／錯誤改走這裡，不再寫進系統日誌（手機一次只看得到一份日誌，
           預設是「戰鬥」；村莊裡連日誌都收起來 —— 寫進去等於沒有）。
           ⚠️ 位置在**底部導覽的上方**：那是視線離開手指最近的地方，而且不會
              蓋住玩家正要按的東西。 */
        /* 📣 2026-10-08：伺服器公告的跑馬燈（src/ui/notice.js）。畫面最上方一條、文字由右往左跑 10 秒；沒在播時整條藏起來 */
        #notice-bar {
            position: fixed; z-index: 61; left: 0; right: 0; top: env(safe-area-inset-top);
            display: none; overflow: hidden; white-space: nowrap;
            background: rgba(69, 26, 3, .94); border-bottom: 1px solid #b45309;
            color: #fde68a; font-size: 13px; font-weight: 700; line-height: 30px; height: 30px;
            pointer-events: none;
        }
        #notice-bar.on { display: block; }
        #notice-bar .notice-text { display: inline-block; padding-left: 100%; animation: notice-run 10s linear forwards; }
        @keyframes notice-run { from { transform: translateX(0); } to { transform: translateX(-100%); } }
        #toast-root {
            position: fixed; z-index: 60;
            left: max(10px, calc(50% - 240px)); right: max(10px, calc(50% - 240px));
            bottom: calc(66px + env(safe-area-inset-bottom) + var(--vp-cut));
            display: flex; flex-direction: column; align-items: center; gap: 6px;
            pointer-events: none;   /* ⚠️ 不可省 —— 它橫跨整欄，會擋住底下的按鈕 */
        }
        #toast-root .toast {
            max-width: 100%;
            padding: 9px 13px;
            border-radius: 9px;
            background: rgba(15, 23, 42, .96);
            border: 1px solid #475569;
            box-shadow: 0 6px 20px rgba(0, 0, 0, .55);
            color: #e2e8f0; font-size: 12.5px; line-height: 1.5; text-align: center;
            animation: toast-in .18s ease;
        }
        #toast-root .toast.err { border-color: #b45309; background: rgba(69, 26, 3, .96); }
        #toast-root .toast.out { opacity: 0; transform: translateY(6px); transition: .2s ease; }
        @keyframes toast-in { from { opacity: 0; transform: translateY(8px); } }
        /* ⚠️ 關掉動畫時仍然要看得到 —— `showToast()` 用 setTimeout 而不是
              `animationend` 收尾，正是因為這裡可能整個沒有動畫。 */
        @media (prefers-reduced-motion: reduce) {
            #toast-root .toast { animation: none; }
        }

        /* ── 🔒 日誌捲動鎖定（移植 08 第二批，上游 2026-06-14 的 `initSysLogLock`）──
           往上捲想看剛剛那一則時，下一則訊息一來就把你拉回底部 —— 狩獵區每 250 ms
           一次回應，實務上等於**根本讀不到上面的東西**。
           ⚠️ 上游只做了系統日誌，我們**兩份都做**：手機一次只看得到一份，而預設
              顯示的是戰鬥日誌 —— 只做系統日誌等於對多數玩家沒有作用。
           ⚠️ 按鈕是 `position: absolute`，所以外層要有一個 `position: relative`
              的容器（`.logwrap`）；直接放在 `#combat-log` 裡面的話它會**跟著內容捲走**。 */
        .logwrap { position: relative; flex: 1 1 0%; min-height: 0; display: flex; flex-direction: column; }
        .logunlock {
            position: absolute; left: 50%; transform: translateX(-50%);
            bottom: 10px; z-index: 5;
            padding: 5px 12px; border-radius: 999px;
            font-size: 12px; font-weight: 700; line-height: 1.2;
            color: #fef3c7; background: rgba(120, 53, 15, .92);
            border: 1px solid #f59e0b; box-shadow: 0 2px 8px rgba(0, 0, 0, .5);
            cursor: pointer;
        }
        .logunlock:hover { background: rgba(146, 64, 14, .96); }

/* 🏰 App 啟動畫面（2026-10-01；src/ui/pwa.js 的 hideSplash()）。
   ⚠️ 預設 display:none，**只在從桌面開啟的 App** 才顯示 —— 判斷放 CSS 而不是 JS，
      是為了在第一次繪製就蓋上（styles.css 在 <head>）。一般瀏覽器分頁永遠看不到它。
   ⚠️ url() 相對於**這個 CSS 檔**（src/），所以是 pwa/splash.jpg，不是 src/pwa/…
   直式（手機）：cover 鋪滿、以人物那一帶為準裁左右；橫式（桌機 App、平板橫放）：整張 contain 置中，
   兩側用同一張圖模糊鋪底，不會裁掉人物。 */
#app-splash { display: none; }
@media (display-mode: standalone), (display-mode: fullscreen) {
    #app-splash {
        display: block; position: fixed; inset: 0; z-index: 2147483000; overflow: hidden;
        background: #1e293b;
        transition: opacity 0.4s ease;
        cursor: pointer;
    }
    #app-splash::before {
        content: ""; position: absolute; inset: -40px;
        background: url("pwa/splash.jpg") center / cover no-repeat;
        filter: blur(24px) brightness(0.7);
    }
    #app-splash img {
        position: absolute; inset: 0; width: 100%; height: 100%;
        object-fit: cover; object-position: 50% 62%;
    }
    #app-splash.splash-out { opacity: 0; pointer-events: none; }
}
@media (display-mode: standalone) and (min-aspect-ratio: 3/4), (display-mode: fullscreen) and (min-aspect-ratio: 3/4) {
    #app-splash img { object-fit: contain; object-position: 50% 50%; }
}
