/* ─────────────────────────────────────────────────────────────
   theme.css — 這支檔案是「美術可以自由改」的地方。
   所有顏色、字體、尺寸、圓角、陰影都集中在這裡的變數。
   ⚠ 契約：HTML 結構與 class 名稱是程式定位用的，請不要更名或刪除；
     只改樣式與變數值。改壞了最多是難看，不會弄壞遊戲規則。
   ───────────────────────────────────────────────────────────── */
:root {
  /* 木頭質感版（2026-09-05）。方向：暖色木盤、輕陰影、字乾淨；
     立體感全部靠陰影與漸層做，不用 3D、不加圖檔（多一個檔就多一次載入）。 */
  --bg: #efe6d4;
  --panel-bg: #fffcf4;
  --panel-line: #e3d7bd;
  --ink: #241f18;
  --ink-soft: #756c5c;
  /* 盤上的字（棋子、盤邊對照表）永遠是「木頭上的墨」，跟介面文字分開兩套變數。
     這樣之後要把介面改成深色，盤上的字也不會跟著變白看不見。 */
  --board-ink: #1d1710;
  --board-ink-soft: #6b5a41;
  --line-road: #b39c76;
  --line-rail: #33291d;
  /* 棋盤木紋：三層疊出來——上方受光、細木紋、底色 */
  --wood-top: #e2c69c;
  --wood-bottom: #d3b586;
  --rail-width: 5px;
  --road-width: 1.6px;

  --seat-0: #2f5fd0;   /* 下家　藍 */
  --seat-1: #17864a;   /* 右家　綠 */
  /* v232：上家、左家照 B 款稿改成紅、黃（原本是橘 #d15a12、紫 #7c3fc4），跟大廳的紅椅、黃椅一致。
     黃色選芥末黃而不是亮黃：棋子上的字一律是深色墨（--board-ink）印在米白底上，對比 13:1；
     暗棋本身沒有字，但亮黃跟木頭盤面太接近，所以暗棋的黃邊另外壓深（見下方 .piece--seat3）。 */
  --seat-2: #b8382a;   /* 上家　紅（取稿上紅椅、紅棋的暖紅） */
  --seat-3: #c99419;   /* 左家　芥末黃 */

  --node-fill: #fffdf6;
  --camp-stroke: #c22a1f;
  --hq-fill: #fdf0cf;
  /* 暗棋不用統一的灰色，直接填該家的隊色，才分得出誰是誰 */
  --hidden-ink: #ffffff;
  --select-ring: #f59e0b;
  --move-dot: #10b981;

  --radius: 10px;
  --font: "PingFang TC", "Noto Sans TC", system-ui, sans-serif;
  --piece-font-size: 16.5px;

  /* 軍階符號顏色（CYC 風格：將官星、校官梅花、尉官槓） */
  --piece-fill: #f2f6f4;
  /* 三個階層必須是三個顏色。原本 --rank-star 與 --rank-bar 是同一個色碼 #e8b923，
     等於將官和尉官同色；--rank-plum 的橘褐跟黃只差約 30° 色相，小尺寸分不開。
     改成金／藍／灰：色相拉到最遠，而且明度本身就在講強弱（金最亮 → 灰最暗）。
     規格見 docs/piece-symbols-spec.md。 */
  --rank-star: #C77800;   /* 將官　金 */
  --rank-plum: #1D4ED8;   /* 校官　藍 */
  --rank-bar:  #5B6B7F;   /* 尉官　灰 */
  --rank-special: #B91C1C; /* 地雷、炸彈　深紅 */
  --rank-tool: #5B6B7F;   /* 工兵　灰（與尉官同層） */
  --rank-mine: #B91C1C;   /* 地雷　深紅 */
  --rank-bomb: #B91C1C;   /* 炸彈　深紅 */
  --rank-flag: #C77800;   /* 軍旗　金 */

  /* ── B 款暖木棋館（v231，照 GPT 視覺稿 四國軍棋B款_其餘畫面_1015）──
     全站按鈕只剩兩種：
     主按鈕＝暖金漸層實心、深棕字（稿上「換你囉」「坐下等人」）；
     次按鈕＝米白底＋深綠細框＋深綠字（稿上「我準備好了」「先跟電腦下一局」）。
     原本的藍色實心／藍框藍字按鈕全部取消——藍色留給「藍家」這個隊色，不再兼任按鈕色。 */
  --btn-primary-top: #f8dc7e;
  --btn-primary-bottom: #dba63a;
  --btn-primary-edge: #b98728;     /* 主按鈕的外框與底緣，做出一點厚度 */
  --btn-primary-ink: #3a2810;
  --btn-secondary-bg: #fffaf0;
  --btn-secondary-hover: #f6efdd;
  --btn-secondary-ink: #2d5a3a;
  --btn-secondary-line: #2d5a3a;
  /* 頁面底色：暖米色，上方受光（純 CSS 漸層，不畫任何插圖） */
  --bg-glow: rgba(255, 248, 230, .85);
  /* 棋盤外圈的木桌邊：比盤面深的胡桃木色 */
  --table-rim-top: #a8743f;
  --table-rim-bottom: #77491f;
  --table-rim-dark: #5a3616;
}

/* ── 夜間版（實驗中）─────────────────────────────────────────
   深色的桌面會讓木頭棋盤浮出來，那正是 Lynch 看上的那個象棋站的作法。
   只要在 <body> 加 data-skin="night" 就生效，盤上的字用的是 --board-ink，
   不受影響。想預設打開就把這一段的選擇器換成 :root。
   ui/style-lab.html 右上角有切換鈕，可以直接比較。 */
body[data-skin="night"] {
  --bg: #17130f;
  --panel-bg: #221c16;
  --panel-line: #382e24;
  --ink: #f2e9d9;
  --ink-soft: #a3947e;
  --line-road: #a68d68;
  /* 按鈕在深色底上：金色主按鈕本來就夠亮，只壓暗一點；
     次按鈕的深綠字在深底上會看不見，改成淺綠字＋中綠框。 */
  --btn-primary-top: #ecc865;
  --btn-primary-bottom: #c48d2a;
  --btn-primary-edge: #8a6420;
  --btn-primary-ink: #2a1c08;
  --btn-secondary-bg: #2b241c;
  --btn-secondary-hover: #362d23;
  --btn-secondary-ink: #acd6ab;
  --btn-secondary-line: #6f9f72;
  --bg-glow: rgba(120, 86, 40, .22);
  --table-rim-top: #6e4a28;
  --table-rim-bottom: #4a2f17;
  --table-rim-dark: #2e1c0c;
}
body[data-skin="night"] .panel { box-shadow: 0 1px 2px rgba(0,0,0,.4), 0 10px 24px rgba(0,0,0,.35); }
body[data-skin="night"] .board { box-shadow: 0 18px 44px rgba(0,0,0,.55), inset 0 0 0 1px rgba(255,255,255,.12); }
body[data-skin="night"] .log li { border-bottom-color: rgba(255,255,255,.07); }
body[data-skin="night"] .setupbar { border-color: var(--panel-line); }

* { box-sizing: border-box; }
/* ⚠️ 手機下拉會觸發瀏覽器的「拉一下重新整理」，一整局就沒了（Lynch 實測回報）。
   contain 只擋住捲動溢位傳給瀏覽器外框，頁面內部照樣捲得動。
   放 html 與 body 兩層是因為各家瀏覽器認的層級不一樣。 */
html, body { overscroll-behavior-y: contain; }
body {
  margin: 0; color: var(--ink);
  /* 暖米色的房間：上方一團柔光往下淡出（B 款）。只用漸層，不放圖、不畫插圖。 */
  background: radial-gradient(120% 60% at 50% 0%, var(--bg-glow), rgba(255,255,255,0) 70%), var(--bg);
  font-family: var(--font);
  /* 全站禁止假造粗體／斜體：沒有那個字面時寧可用最接近的真字面，也不要描兩次 */
  font-synthesis: none; -webkit-font-synthesis: none;
  /* 棋盤是拿來拖棋子的，不是拿來選字的。不擋的話拖曳會變成框選文字，點擊判定跟著壞掉。 */
  -webkit-user-select: none; user-select: none;
}
.log, .modal input { -webkit-user-select: text; user-select: text; }
.app {
  display: grid; grid-template-columns: 1fr 280px; grid-template-rows: auto 1fr;
  gap: 12px; padding: 12px; height: 100vh; height: 100dvh;
}
.topbar { grid-column: 1 / -1; display: flex; align-items: center; gap: 16px; }
.title { font-size: 18px; margin: 0; }
/* 標題＝回首頁。做成按鈕但長得像標題，滑過去才提示可以按。 */
.title-home {
  font: inherit; color: inherit; background: none; border: 0; padding: 0; margin: 0;
  cursor: pointer; letter-spacing: inherit;
}
.title-home:hover { color: var(--btn-secondary-ink); }
.turn { font-weight: 700; padding: 4px 12px; border-radius: 999px; border: 1px solid transparent; }
/* 輪到自己：標題那行整個亮起來，還有一顆會呼吸的點（只有桌機看得到；手機 .turn 是隱藏的）。
   padding 與邊框「平常就留著」，所以亮起來的時候版面不會跳。
   v231：從藍色改成跟「換你囉」同一套暖金。 */
.turn.is-my-turn {
  background: linear-gradient(180deg, var(--btn-primary-top), var(--btn-primary-bottom));
  color: var(--btn-primary-ink); border-color: var(--btn-primary-edge);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--btn-primary-bottom) 22%, transparent);
}
.turn.is-my-turn::before {
  content: ''; display: inline-block; width: 8px; height: 8px; border-radius: 50%;
  background: var(--btn-primary-ink); margin-right: 8px; vertical-align: 1px;
  animation: turn-pulse 1.4s ease-in-out infinite;
}
@keyframes turn-pulse { 0%,100% { opacity: 1 } 50% { opacity: .25 } }
/* 棋盤本身也發光（inset，不改變任何尺寸）。v231：藍光改成暖金，跟木桌、金色橫幅同一調。 */
.board.is-my-turn {
  box-shadow: inset 0 0 0 1px rgba(60,35,12,.35), inset 0 0 0 4px var(--btn-primary-bottom),
              inset 0 0 26px color-mix(in srgb, var(--btn-primary-top) 45%, transparent);
}
.controls { margin-left: auto; display: flex; align-items: center; gap: 12px; }
.toggle { font-size: 13px; color: var(--ink-soft); }
/* 次按鈕（預設）：米白底、深綠細框、深綠字、膠囊圓角。
   ⚠ 只准用 transform／顏色做按下的回饋，不准改 padding、邊框寬度這類會動到尺寸的東西——
     手機上點一下就讓版面跳，是 Lynch 回報過的「畫面晃得很嚴重」。 */
.btn {
  font: inherit; padding: 6px 14px; border-radius: 999px;
  border: 1px solid var(--btn-secondary-line); background: var(--btn-secondary-bg); cursor: pointer;
  color: var(--btn-secondary-ink); font-weight: 600;
  box-shadow: 0 1px 0 rgba(90,64,28,.10), 0 2px 6px rgba(90,64,28,.08);
  transition: background .12s ease, transform .06s ease;
}
.btn:hover { background: var(--btn-secondary-hover); }
.btn:active { transform: translateY(1px); }
.btn:disabled { opacity: .45; cursor: default; }

/* 木桌邊（B 款）：棋盤放在一張厚實的木桌上。
   ⚠ 桌邊做在 .stage（外層）而不是 .board（svg）上：board.js 用 svg 的
     getBoundingClientRect 換算平移與縮放，在 svg 上加邊框或 padding 會讓手指位置對不準。
   全部是 CSS 漸層與陰影，不是插圖。 */
.stage {
  display: grid; place-items: center; min-height: 0;
  padding: 12px; border-radius: 22px;
  background:
    repeating-linear-gradient(92deg, rgba(60,32,10,.07) 0 2px, rgba(60,32,10,0) 2px 9px),
    linear-gradient(180deg, var(--table-rim-top), var(--table-rim-bottom));
  box-shadow:
    0 3px 0 var(--table-rim-dark),                 /* 桌板的厚度 */
    0 16px 34px rgba(70,44,18,.32),                /* 落在地上的影子 */
    inset 0 1px 0 rgba(255,232,196,.45),           /* 桌邊上緣受光 */
    inset 0 -2px 0 rgba(40,22,8,.35);
}
.board {
  width: 100%; height: 100%; max-height: 100%;
  /* 木頭桌面：受光的高光 + 細木紋 + 底色漸層。全部是 CSS 漸層，沒有圖檔要載。 */
  background:
    /* 一層很淡的木紋（原本兩層條紋太明顯，Lynch：「漸層色我覺得沒有很好看」），
       加一點點四周壓暗，讓中間自然亮起來——不用刺眼的白色高光。 */
    radial-gradient(105% 78% at 50% 42%, rgba(255,255,255,.18), rgba(120,84,40,.10) 78%, rgba(90,60,25,.16) 100%),
    repeating-linear-gradient(91deg, rgba(104,70,30,.035) 0 3px, rgba(104,70,30,0) 3px 11px),
    linear-gradient(178deg, var(--wood-top), var(--wood-bottom));
  /* 盤面嵌在木桌邊裡：外陰影交給 .stage，這裡只留一圈嵌入的內陰影 */
  border-radius: 12px;
  box-shadow: inset 0 0 0 1px rgba(60,35,12,.35), inset 0 2px 6px rgba(60,35,12,.22),
              inset 0 -18px 30px rgba(90,60,25,.10);
  /* 單指平移、雙指縮放都交還給瀏覽器（manipulation 只關掉「連點兩下放大」的延遲）。
     放大之後要能拖著看棋盤的別的地方——這需要單指平移，所以佈陣的拖曳互換
     必須拿掉，改成純點選（Lynch 的解法）。兩者不能並存。 */
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent; }

.side { display: flex; flex-direction: column; gap: 12px; min-height: 0; }
/* ⚠ 只有「戰報」那一塊可以被壓縮，座位表不行。
   座位表被壓到的時候會**少顯示一家**——Lynch 實機：斷線回來之後
   「胖球小可愛可以玩，但我的介面看不到他」，其實是第四列被切掉了。 */
.side-sheet { display: contents; }                 /* 桌機：不存在，面板照常排在側欄 */
.side-toggle { display: none; }
.side > .turnbanner, .side .panel { flex: 0 0 auto; }
.side .panel:has(.log) { flex: 1 1 auto; min-height: 120px; }
.panel {
  background: var(--panel-bg); border-radius: var(--radius);
  padding: 10px 12px; border: 1px solid var(--panel-line); min-height: 0;
  display: flex; flex-direction: column;
  box-shadow: 0 1px 2px rgba(90,64,28,.06), 0 6px 16px rgba(90,64,28,.05);
}
.panel-title { font-size: 12px; margin: 0 0 8px; color: var(--ink-soft); font-weight: 600;
  letter-spacing: .12em; text-transform: none; }
.seats { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; }
.seat { display: flex; align-items: center; gap: 8px; font-size: 14px; }
.seat-dot { width: 10px; height: 10px; border-radius: 50%; }
.seat.is-turn { font-weight: 700; }
.seat.is-out { opacity: .45; text-decoration: line-through; }
.seat-note { margin-left: auto; font-size: 12px; color: var(--ink-soft); }
.log { list-style: none; margin: 0; padding: 0; overflow-y: auto; font-size: 13px;
  display: grid; gap: 0; font-variant-numeric: tabular-nums;
  /* 底部淡出：讓「還有更多」看得出來，而不是被硬切一刀 */
  -webkit-mask-image: linear-gradient(180deg, #000 calc(100% - 18px), transparent);
  mask-image: linear-gradient(180deg, #000 calc(100% - 18px), transparent);
}
.log li { color: var(--ink-soft); padding: 4px 2px; line-height: 1.45;
  border-bottom: 1px solid rgba(120,95,55,.10); }
.log li:last-child { border-bottom: 0; }
.log li.is-big { color: var(--ink); font-weight: 600; }

/* 棋盤元素 */
.rail { stroke: var(--line-rail); stroke-width: var(--rail-width); fill: none;
  stroke-linecap: round; stroke-linejoin: round; }
.road { stroke: var(--line-road); stroke-width: var(--road-width); }
/* 點位：木頭上的象牙色格子，下緣一道極輕的陰影就有浮起來的感覺 */
.node { fill: var(--node-fill); stroke-width: 2.5; }
.layer-nodes { filter: drop-shadow(0 1px 0.5px rgba(80,55,25,.28)); }
.node--camp { stroke: var(--camp-stroke); stroke-width: 4; }
.node--hq { fill: var(--hq-fill); stroke-width: 3; }
.node--center { stroke: #6b5a41; }
.piece { cursor: default; }
.piece--mine { cursor: pointer; }
.piece-label { font-size: var(--piece-font-size); text-anchor: middle; dominant-baseline: central; pointer-events: none; }

.is-selected .piece-box { stroke: var(--select-ring); stroke-width: 4; }
.move-dot { fill: var(--indicator, var(--move-dot)); stroke: #fff; stroke-width: 2; cursor: pointer; }
.move-dot--capture { fill: none; stroke: var(--indicator, var(--move-dot)); stroke-width: 4;
  paint-order: stroke; filter: drop-shadow(0 0 2px #fff); }
.move-target { cursor: pointer; }
.flag-mark { fill: var(--camp-stroke); font-size: 11px; text-anchor: middle; }

/* 佈陣列 */
/* [hidden] 必須明確蓋過 display:flex，否則開局後佈陣列不會消失 */
.setupbar[hidden] { display: none; }
.setupbar {
  grid-column: 1 / -1; display: flex; align-items: center; gap: 12px;
  background: var(--panel-bg); border: 1px solid #e7dfcc; border-radius: var(--radius);
  padding: 8px 12px; font-size: 14px;
}
.setup-who { font-weight: 700; }
.setup-timer { font-variant-numeric: tabular-nums; font-weight: 700; }
.setup-timer.is-urgent { color: var(--camp-stroke); }
.setup-hint { color: var(--ink-soft); font-size: 13px; }
.setup-hint.is-error { color: var(--camp-stroke); }
.setup-actions { margin-left: auto; display: flex; gap: 8px; }
/* 主按鈕：暖金漸層實心、深棕字、膠囊、輕微立體（上緣亮邊＋下緣深邊＋落影）。
   邊框寬度跟次按鈕一樣是 1px，兩種按鈕並排時高度一致。 */
.btn--primary {
  background: linear-gradient(180deg, var(--btn-primary-top), var(--btn-primary-bottom));
  color: var(--btn-primary-ink); border-color: var(--btn-primary-edge); font-weight: 700;
  box-shadow: inset 0 1px 0 rgba(255,250,225,.75), inset 0 -2px 0 rgba(120,80,15,.22),
              0 2px 0 var(--btn-primary-edge), 0 4px 10px rgba(120,80,15,.22);
}
.btn--primary:hover {
  filter: brightness(1.04);
  background: linear-gradient(180deg, var(--btn-primary-top), var(--btn-primary-bottom));
}
.seat.is-ready .seat-note { color: var(--move-dot); }

/* 棋子與軍階符號 */
/* 看得到身分的棋子：底色帶一點該家的顏色，跟米色棋盤拉開對比。
   原本一律用近乎白色的 #f2f6f4，Lynch 實機回報「我方顏色有點淺，對比不足」。
   ⚠ 暗棋（piece--hidden）的整塊實色規則在下面，選擇器更精確，不會被這條蓋掉。 */
/* 棋子的立體感。
   ⚠ 原本把 filter 掛在整層（.layer-pieces）上，結果**文字被一起送進濾鏡點陣化，
     小字就糊掉了**（Lynch 實機：「我方棋子我覺得字很糊」）。
   現在只給方框本身一道**零模糊**的實心陰影：文字不在濾鏡裡，所以維持向量的銳利度，
   而且零模糊的陰影比高斯模糊便宜得多，一盤上百顆也不掉幀。 */
.piece-box { filter: drop-shadow(0 1.5px 0 rgba(74,48,18,.42)); }
.piece-box { fill: url(#faceWood); stroke-width: 3; }
/* 看得到身分的棋子：木頭面帶一點該家的顏色（漸層定義在 index.html 的 #paint-defs）。
   Lynch 實機回報過「我方顏色有點淺，對比不足」，所以下緣的顏色壓得比上緣重。 */
.piece--seat0 .piece-box { fill: url(#face0); }
.piece--seat1 .piece-box { fill: url(#face1); }
.piece--seat2 .piece-box { fill: url(#face2); }
.piece--seat3 .piece-box { fill: url(#face3); }
/* ⚠ 字重不要開到 800。蘋方（PingFang TC）沒有 800 這個字面，
   瀏覽器就會**自己假造粗體**——做法是把同一個字略微位移再描一次，
   看起來就是「重疊」與模糊（Lynch 一眼看出來：「會不會是有重疊造成？」）。
   600（Semibold）是蘋方真的有的字面，配上 font-synthesis: none 之後，
   任何系統都只會用真的字面，不會再假造。 */
.piece-label { font-weight: 600; fill: var(--board-ink); }
/* 暗棋填該家的隊色（上淺下深＝有厚度），一眼分得出是哪一家 */
.piece--hidden.piece--seat0 .piece-box { fill: url(#hide0); }
.piece--hidden.piece--seat1 .piece-box { fill: url(#hide1); }
.piece--hidden.piece--seat2 .piece-box { fill: url(#hide2); }
.piece--hidden.piece--seat3 .piece-box { fill: url(#hide3); }
/* v232 黃家：芥末黃跟木頭盤面的明度很接近（對比只有約 1.2～2.2），
   邊框壓成深芥末，暗棋才不會融進盤面；選取框原本是琥珀色，落在黃棋上看不出來，黃家改用深棕。
   ⚠ 只改 stroke 顏色，不改線寬：選中時棋子不能變大（大小在這遊戲裡是階級訊號）。 */
.piece--hidden.piece--seat3 .piece-box { stroke: #8a6410; }
.piece--seat3.is-selected .piece-box { stroke: #4a2c0c; }

.piece-label { font-size: var(--piece-font-size); text-anchor: middle; dominant-baseline: central;
  pointer-events: none; fill: var(--board-ink); }
/* ⚠ 棋子上的東西**一律不加濾鏡**。
   Lynch 連續兩次回報字不夠銳利，最後把軍階符號那道亮邊也拿掉才乾淨——
   濾鏡會把整個群組（含文字）先點陣化再貼回去，小字一定糊。
   立體感只保留在方框的零模糊陰影上（那是形狀，不是文字）。 */
.badge { filter: none; }
.piece-label { filter: none; text-rendering: optimizeLegibility; }
.ins--star { fill: var(--rank-star); }
.ins--plum circle { fill: var(--rank-plum); }
.ins--bar { fill: var(--rank-bar); }
.ins--tool { fill: var(--rank-tool); }
.ins--tool-handle { stroke: var(--rank-tool); stroke-width: 2.2; stroke-linecap: round; }
.ins--mine { fill: var(--rank-mine); }
.ins--mine-spikes { stroke: var(--rank-mine); stroke-width: 1.6; }
.ins--bomb { fill: var(--rank-bomb); }
.ins--bomb-fuse { stroke: var(--rank-bomb); stroke-width: 1.6; fill: none; stroke-linecap: round; }
.ins--flag { fill: var(--rank-flag); }
.ins--flag-pole { stroke: var(--ink); stroke-width: 1.6; }

/* 換手鈕：一機兩人才會出現。**不要浮在棋盤上**——Lynch 在手機上被它擋住整個中央
   （「太礙眼，拿掉，或是放別地方不要擋到畫面」）。改成排在棋盤下方的一般元素，
   棋盤本身不會被推動，只有下面的面板往下挪。 */
.handoff { display: block; margin: 10px auto 0; padding: 14px 28px; font-size: 16px;
  box-shadow: 0 6px 20px rgba(0,0,0,.18); }
.stage { position: relative; }

/* 結局畫面 */
.overlay[hidden] { display: none; }
.overlay {
  position: fixed; inset: 0; display: grid; place-items: center; z-index: 20;
  padding: 12px; overflow-y: auto;
  background: rgba(24, 20, 14, .55); backdrop-filter: blur(3px);
  animation: overlay-in .28s ease-out;
}
@keyframes overlay-in { from { opacity: 0 } to { opacity: 1 } }
.overlay-code {
  margin: 2px 0 14px; font-size: 13px; letter-spacing: .08em;
  color: var(--muted, #8a8172); font-variant-numeric: tabular-nums;
}
.overlay-card {
  background: var(--panel-bg); border-radius: 20px; padding: 36px 52px; text-align: center;
  box-shadow: 0 24px 60px rgba(0,0,0,.35); animation: card-in .34s cubic-bezier(.2,1.2,.4,1);
  /* 結算的統計比一句「獲勝」長很多，手機上一定超出螢幕。
     沒有高度上限的話會被切掉而且捲不動（Lynch 手機實測：「最後的戰績無法下拉」）。 */
  min-width: 320px; max-width: 100%; max-height: 92vh; max-height: 92dvh; overflow-y: auto;
}
@keyframes card-in { from { transform: translateY(18px) scale(.94); opacity: 0 } to { transform: none; opacity: 1 } }
.overlay-emblem { font-size: 56px; line-height: 1; margin-bottom: 10px; }
.overlay-title { margin: 0 0 6px; font-size: 30px; letter-spacing: .04em; }
.overlay-title.is-win { color: var(--rank-star); }
.overlay-title.is-lose { color: var(--ink-soft); }
.overlay-sub { margin: 0 0 22px; color: var(--ink-soft); font-size: 14px; }
.overlay-btn { padding: 12px 28px; font-size: 16px; }

/* 上一步的痕跡 */
.last-path { fill: none; stroke-width: 5; opacity: .38; stroke-linejoin: round; stroke-linecap: round;
  stroke-dasharray: 6 7; }
.last-from { fill: none; stroke-width: 3; opacity: .55; stroke-dasharray: 4 4; }
/* 上一步的落點：跟棋子同尺寸的虛線外框。不能用「比較大」來標記——
   這個遊戲的玩家整局都在比大小，體積上的差異會被讀成階級差異。 */
.last-to { fill: none; stroke-width: 3; opacity: .95; stroke-dasharray: 7 3; }

.piece-hit { cursor: inherit; }
.piece--mine { cursor: grab; }
.piece--mine.is-dragging { cursor: grabbing; opacity: .55; }

/* 對話框（陣型管理） */
.modal[hidden] { display: none; }
.modal { position: fixed; inset: 0; z-index: 30; display: grid; place-items: center;
  background: rgba(24,20,14,.45); backdrop-filter: blur(2px); }
/* 視窗永遠不能寬過螢幕：min-width 固定 340px 時，窄手機上會整個被推出畫面 */
.modal-card { background: var(--panel-bg); border-radius: 16px; padding: 22px 24px;
  min-width: min(340px, 100%); max-width: min(460px, calc(100vw - 24px));
  box-shadow: 0 18px 46px rgba(0,0,0,.3); }
.modal-title { margin: 0 0 14px; font-size: 17px; }
/* 46vh 太矮：多人連線視窗有三個開房選項＋輸入房號那一段，會被切在半空中。
   （Lynch 加了「加入遊戲」之後就頂到了。）拉到 62vh，還是會捲，只是不會一開就被切。 */
/* grid 的欄位一定要 minmax(0,1fr)：預設 min-width:auto 會讓長網址那一列
   把整個內容區撐寬，視窗裡就變成左右都要捲（實測：複製鈕與右邊那個座位被切掉）。 */
.modal-body { display: grid; grid-template-columns: minmax(0, 1fr); gap: 8px;
  max-height: 62vh; max-height: 62dvh; overflow-y: auto; overflow-x: hidden; }
.modal-actions { display: flex; gap: 8px; justify-content: flex-end; margin-top: 18px; }
.modal input[type="text"] { font: inherit; padding: 9px 12px; border-radius: 10px;
  border: 1px solid var(--line-road); width: 100%; background: #fff; color: var(--ink); }
.saved-row { display: flex; align-items: center; gap: 10px; padding: 9px 12px; border-radius: 10px;
  border: 1px solid #e7dfcc; background: #fff; }
.saved-name { font-weight: 600; }
.saved-date { color: var(--ink-soft); font-size: 12px; }
.saved-row .btn { padding: 4px 10px; font-size: 13px; }
.saved-row .spacer { margin-left: auto; }
.modal-empty { color: var(--ink-soft); font-size: 14px; padding: 8px 2px; }
.modal-note.is-warn { color: var(--camp-stroke); font-weight: 600; }
.report-row { display: flex; gap: 8px; margin: 8px 0; flex-wrap: wrap; }
.terms-body ol { padding-left: 1.4em; } .terms-body li { margin: 6px 0; }
.btn--danger { color: var(--camp-stroke); border-color: var(--camp-stroke); }

/* 走法提示的顏色＝看棋的人自己的隊色 */
.ind-seat0 { --indicator: var(--seat-0); }
.ind-seat1 { --indicator: var(--seat-1); }
.ind-seat2 { --indicator: var(--seat-2); }
.ind-seat3 { --indicator: var(--seat-3); }

/* 轉角圓弧：畫粗一點，讓它明顯是「可以順著滑過去的鐵路圓角」 */
.rail--arc { stroke-width: calc(var(--rail-width) + 1px); stroke-linecap: round; }

/* 對局代號：要在截圖裡看得清楚，但不能搶走棋盤的注意力。
   用等寬字，字母和數字才不會因為字寬不同而看錯。 */
.gamecode {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 14px;
  font-weight: 600;
  letter-spacing: .1em;
  padding: 3px 9px;
  border-radius: 6px;
  border: 1px solid var(--line);
  color: var(--ink);            /* 截圖縮小後還要看得清楚，不能用淡色 */
  background: var(--surface);
  white-space: nowrap;
  cursor: pointer;               /* 按一下就複製 */
  transition: background .15s, color .15s, border-color .15s;
}
.gamecode:hover { border-color: var(--ink-dim); }
.gamecode.is-copied {
  background: var(--ok, #1a7f5a);
  border-color: transparent;
  color: #fff;
}

/* 新手指南 */
/* 指南要夠寬才讀得順。modal-card 預設 max-width: 460px 會把內容夾到要左右捲，
   所以這裡把含有 .guide 的那張卡片放寬。 */
/* 高度一律再寫一次 dvh：LINE 這類 App 內建瀏覽器的 100vh 不含它自己的工具列，
   算出來比實際看得到的還高，底部按鈕列就會壓到說明文字（Lynch 用 LINE 開實測）。
   dvh 是實際可視高度；不支援的舊瀏覽器會忽略這一行，退回上面的 vh。 */
.modal-card:has(.guide) { max-width: min(92vw, 820px); max-height: 90vh; max-height: 90dvh; overflow: hidden;
  display: flex; flex-direction: column; }
/* 指南要自己管捲動：.modal-body 預設 max-height: 46vh 會把整份指南夾扁，
   動畫棋盤的最後一排（軍旗）就被切掉，那整段就白講了。 */
.modal-card:has(.guide) .modal-body { min-height: 0; display: flex; max-height: none; overflow: visible; }
.guide { display: flex; flex-direction: column; width: min(86vw, 760px); max-width: 100%; }
.guide-body, .guide-body * { overflow-wrap: anywhere; }
/* 分頁要釘在最上面：往下捲內文時，切換的按鈕不能跟著捲不見。 */
.guide-tabs {
  display: flex; gap: 6px;
  position: sticky; top: 0; z-index: 2;
  background: var(--panel-bg);
  padding: 2px 0 10px;
}
/* 用較高的選擇器特異度，否則 .btn 自己的 color 會蓋過來，選中的分頁會變成一塊黑色 */
/* v231：選中的分頁＝次按鈕反白（深綠底、米白字），跟全站按鈕同一套顏色 */
.guide-tabs .guide-tab.is-on {
  background: var(--btn-secondary-ink);
  color: var(--btn-secondary-bg);   /* 不是 --surface：那個變數不存在，字色會回退成跟背景同色 */
  border-color: var(--btn-secondary-ink);
}
/* 捲動發生在這一層（不是整張卡片），分頁才釘得住 */
.guide { max-height: 82vh; max-height: 82dvh; overflow: hidden; }
.guide-body { flex: 1; min-height: 0; overflow-y: auto; line-height: 1.75; font-size: 16px; }
.guide-body h3 { font-size: 17px; margin: 18px 0 6px; }
.guide-body h3:first-child { margin-top: 0; }
.guide-body p { margin: 6px 0; }
.guide-body ul, .guide-body ol { margin: 6px 0; padding-left: 22px; }
.guide-body li { margin: 3px 0; }
.guide-body b { color: var(--ink); }
.guide-body { color: var(--ink); }
.g-rank { font-weight: 600; letter-spacing: .04em; }

/* 新手指南的圖示與動畫 */
/* display: contents 會蓋過 [hidden] 的 display:none，一定要明寫這一條 */
.debug-tools { display: contents; }
.debug-tools[hidden] { display: none !important; }

/* 規則說明是新手唯一的入口，要顯眼，但**不要用主色**——
   一個畫面上同時好幾顆亮藍，反而看不出哪個才是現在該按的（GPT 回饋）。
   用外框強調就夠：主色留給當下真正的主要操作（確定佈陣、換你囉）。 */
/* v231：次按鈕本身就是外框樣式，這裡只加粗外框強調，不再用藍色。 */
#guide { font-weight: 700; box-shadow: inset 0 0 0 1px var(--btn-secondary-line); }

/* 「⋯」與它收起來的那一組。桌機：完全不出現，內容照常排在工具列上。 */
.controls-toggle { display: none; }
.controls-more { display: contents; }
@media (max-width: 620px) {
  .controls-toggle { display: inline-flex; align-items: center; }
  .controls { position: relative; }
  .controls-more {
    display: none; position: absolute; right: 0; top: calc(100% + 6px); z-index: 25;
    background: var(--panel-bg); border: 1px solid var(--panel-line); border-radius: 12px;
    padding: 10px 12px; box-shadow: 0 12px 30px rgba(70,48,22,.22);
    flex-direction: column; align-items: stretch; gap: 10px; min-width: 168px;
  }
  .controls-more.is-open { display: flex; }
  .controls-more .btn { width: 100%; }
}
.g-svg { display: block; margin: 10px auto 6px; width: 100%; max-width: 420px; height: auto; }
.g-t { font-size: 11px; fill: var(--ink); }
.g-t--seat { font-size: 13px; font-weight: 700; fill: #fff; }
.g-t--tiny { font-size: 10.5px; fill: var(--ink-soft); }
.g-t--em { font-size: 11px; font-weight: 700; fill: var(--ink); }
.g-seat { stroke: none; }
.g-seat0 { fill: var(--seat-0); } .g-seat1 { fill: var(--seat-1); }
.g-seat2 { fill: var(--seat-2); } .g-seat3 { fill: var(--seat-3); }
.g-mate { stroke: var(--ink-soft); stroke-width: 2; stroke-dasharray: 5 4; fill: none; }
.g-arrow { stroke: var(--ink-soft); stroke-width: 1.6; fill: none; }
.g-arrowhead { fill: var(--ink-soft); }
.g-chip, .g-node { fill: var(--node-fill); stroke: var(--seat-0); stroke-width: 2; }
.g-hq { fill: var(--hq-fill); stroke: var(--seat-0); stroke-width: 2; }
.g-camp { fill: none; stroke: var(--camp-stroke); stroke-width: 3; }
.g-road { stroke: var(--line-road); stroke-width: var(--road-width); }
.g-rail { stroke: var(--line-rail); stroke-width: var(--rail-width); }
.g-ins .ins { fill: var(--seat-2); stroke: none; }
.g-ins .ins--tool-handle, .g-ins .ins--tool { fill: none; stroke: var(--seat-0); stroke-width: 1.6; }
.g-ins .ins--tool { fill: var(--seat-0); }

.demo { display: grid; gap: 10px; justify-items: center; }
/* 裁切後是「直的」（一家的陣地高大於寬），所以要用高度限制，
   用寬度限制會超出視窗被切掉——軍旗在最下面那排，切掉就整段白講。 */
/* 寬度吃滿、高度封頂：走子示範的鏡頭是橫的（鐵路橫跨整盤），
   只用高度限制會讓它縮在中間看不清楚；佈陣示範是直的，仍然由 max-height 收住。 */
.demo-board { width: 100%; max-height: 52vh; max-height: 52dvh; height: auto; }
.demo-box { fill: var(--node-fill); stroke: var(--seat-0); stroke-width: 2; }
.demo-hq { fill: var(--hq-fill); stroke: var(--seat-0); stroke-width: 2.4; }
.demo-camp { fill: none; stroke: var(--camp-stroke); stroke-width: 3; }
.demo-name { font-size: 11px; fill: var(--ink); }
.demo-ins .ins { fill: var(--seat-2); stroke: none; }
.demo-cell { transition: opacity .3s; }
.demo-cell.is-flash .demo-box, .demo-cell.is-flash .demo-hq { stroke: var(--select-ring); stroke-width: 4; }
.demo-cell.is-gone { opacity: .12; }
.demo-foe { transition: none; }
.demo-foe.is-moving { transition: transform .9s ease-in-out; }
.demo-foe-box { fill: var(--seat-1); }
.demo-foe-name { font-size: 11px; fill: #fff; font-weight: 600; }
.demo-board.is-win .demo-cell { opacity: .25; }
/* 說明文字跟指南其他頁一樣大：13.5px 在桌機上太小（Lynch 回報）。 */
.demo-caption { font-size: 16px; line-height: 1.7; text-align: center; min-height: 2.8em; }
/* 按鈕列釘在可視範圍底部：手機上不捲動就看不到「下一步」，
   使用者會以為指南只有第一頁（Lynch 手機實測：「一開始沒下拉我還以為就這樣要開始」）。 */
/* 導覽列被搬到彈窗的動作列，跟「開始玩」同一排，所以不再需要 sticky 與底色 */
.demo-bar { display: flex; align-items: center; gap: 10px; }
.modal-actions:has(.demo-bar) { justify-content: space-between; align-items: center; }
.demo-dots { white-space: nowrap; }
/* 手機：五個控制項擠一排會超出畫面（實測 390px 寬的畫面上，「開始玩」的右邊
   落在 431px，整顆被切掉，頁碼也被擠成直排）。改成兩列：
   上一步／頁碼／下一步 一列，自動播放與開始玩一列。
   用 display:contents 把 .demo-bar 的按鈕交給外層的 grid 排，不必改 DOM 結構。 */
@media (max-width: 560px) {
  .modal-actions:has(.demo-bar) {
    display: grid; grid-template-columns: 1fr auto 1fr; gap: 10px; align-items: center;
  }
  .modal-actions .demo-bar { display: contents; }
  .modal-actions .demo-prev { grid-area: 1 / 1; }
  .modal-actions .demo-dots { grid-area: 1 / 2; text-align: center; }
  .modal-actions .demo-next { grid-area: 1 / 3; }
  .modal-actions .demo-play { grid-area: 2 / 1 / 3 / 2; }
  .modal-actions:has(.demo-bar) > .btn--primary { grid-area: 2 / 2 / 3 / 4; }
  .modal-actions .demo-btn, .modal-actions:has(.demo-bar) > .btn--primary { width: 100%; }
}
.demo-dots { font-size: 12px; color: var(--ink-soft); }
.demo-btn:disabled { opacity: .4; cursor: default; }


/* ── 窄螢幕：手機實機回報，桌機的雙欄在這裡完全不能用 ─────────────────
   問題（Android Chrome 實測）：
   1. 雙欄沒收合 → 棋盤被擠到左下角不到 150px，根本點不到
   2. 容器太窄 → 中文自動折行，「四國軍棋」「確定佈陣」變成一個字一行
   3. 模式下拉右側被裁掉
   收成單欄：棋盤佔滿寬度置頂，座位／戰報移到下面。 */
@media (max-width: 760px) {
  .app {
    grid-template-columns: 1fr;          /* 單欄 */
    grid-template-rows: auto auto auto auto;
    padding: 10px;
    gap: 10px;
    height: auto;                        /* 手機要能往下捲，不要鎖在一屏 */
    min-height: 100dvh;
  }
  .topbar {
    flex-wrap: wrap;                     /* 讓它換行，而不是把每個字擠成一行 */
    gap: 8px 12px;
    align-items: baseline;
  }
  .title, .turn, .gamecode, .btn, .toggle, .setup-who, .setup-hint {
    white-space: nowrap;                 /* 中文不要逐字折行 */
  }
  .controls { margin-left: 0; flex-wrap: wrap; gap: 8px; width: 100%; }
  .controls select { max-width: 100%; }

  .stage { order: -1; }                  /* 棋盤排到最上面：那是唯一要操作的東西 */
  .board { width: 100%; height: auto; }

  .setupbar { flex-wrap: wrap; gap: 8px; padding: 8px 10px; }
  .setup-actions { margin-left: 0; width: 100%; flex-wrap: wrap; }
  .setup-actions .btn { flex: 1 1 auto; }

  .side { flex-direction: column; }
  .panel { max-height: 34vh; }
}

/* 窄螢幕（單格 < 26px）：只留符號，文字拿掉並把符號置中。
   6.5px 的中文讀不出來，留著只會蓋掉符號。 */
.board.is-compact .piece-label { display: none; }
/* 不顯示文字時，整個格子都是符號的——原本只把符號往下移，留白佔了三分之一，
   在 18px 的格子上等於白白浪費解析度（Lynch：「你沒有善用這個格子，圖案還是太小」）。
   放大到品字形三顆星剛好填滿 42×36 的格子；單顆星維持同尺寸不放大（符號規格），
   所以單顆看起來比較空是正確的——那是「數量」的訊息。 */
/* board.js 把符號往上偏移 6（為了在下面留文字的位置）；不顯示文字時要「剛好抵銷」
   才會置中——補償不足就會整體偏下（Lynch 回報）。 */
/* 精簡模式沒有文字，就把「為文字留位」的偏移整個取消（不要用 CSS 猜補償量），
   再把符號放大到接近填滿格子。 */
.board.is-compact .badge { transform: none; }
.board.is-compact .insignia { transform: scale(1.35); }

/* 音效試聽面板 */
.sfxpick { display: grid; gap: 16px; width: min(88vw, 460px); }
.sfx-group { display: grid; gap: 4px; }
.sfx-title { font-weight: 700; font-size: 14px; }
.sfx-row { display: flex; align-items: center; gap: 10px; padding: 5px 2px; cursor: pointer; }
.sfx-name { flex: 1; font-size: 13px; }
.sfx-play { padding: 3px 12px; font-size: 12px; }

/* 本局統計 */
.stats { margin-top: 18px; text-align: left; }
.stats-title { font-weight: 700; font-size: 14px; margin-bottom: 8px; }
.stats-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; }
.stats-list li { display: flex; align-items: baseline; gap: 10px; font-size: 13px; flex-wrap: wrap; }
.stats-k { color: var(--ink-soft); min-width: 6.5em; }
.stats-v { font-weight: 700; }
.stats-note { color: var(--ink-soft); font-size: 12px; }

/* 解鎖成就 */
.ach { margin-top: 16px; padding: 12px 14px; border-radius: 12px;
  background: var(--hq-fill); border: 1px solid var(--rank-star); }
.ach-title { font-weight: 700; font-size: 13px; color: var(--rank-star); margin-bottom: 6px; }
.ach-item { display: flex; gap: 10px; align-items: baseline; font-size: 13px; margin: 3px 0; }
.ach-name { font-weight: 700; white-space: nowrap; }
.ach-desc { color: var(--ink-soft); font-size: 12px; }
.ach-progress { margin-top: 8px; font-size: 12px; color: var(--ink-soft); }

/* 殘存大子表 */
.alive-table { border-collapse: collapse; margin-top: 6px; font-size: 13px; }
.alive-table th, .alive-table td { padding: 4px 12px; text-align: center; }
.alive-table th { font-weight: 700; color: var(--ink-soft); font-size: 12px; }
.alive-table .alive-seat { text-align: left; font-weight: 700; font-size: 13px; }
.alive-table td { font-variant-numeric: tabular-nums; font-weight: 700; }
.alive-table td.is-none { color: var(--ink-soft); font-weight: 400; }

/* 指南圖上的直接標示 */
/* 教學的標註：另一個顏色的大方框 ＋ 上方的色塊文字。
   不要用小點或圈圈疊在棋子上（會糊成一坨），也不要讓字直接壓在鐵路線上。 */
.g-mark { fill: rgba(15,118,110,.14); stroke: #0F766E; stroke-width: 3; }
.g-chip { fill: #0F766E; }
.g-chip-text { font-size: 12px; font-weight: 700; fill: #fff; }

/* 版本號：看得到就好，不要搶到標題前面 */
.uiver { font-size: 11px; color: var(--ink-soft); letter-spacing: .04em;
  padding: 2px 6px; border: 1px solid var(--line-road); border-radius: 6px; }
/* 有新版時才醒目，平常只是個不搶戲的版本號 */
.uiver.is-stale { cursor: pointer; color: var(--btn-primary-ink);
  background: linear-gradient(180deg, var(--btn-primary-top), var(--btn-primary-bottom));
  border-color: var(--btn-primary-edge); font-weight: 600; }

/* 手機：棋盤壓低一點，讓說明文字與按鈕列跟圖同時看得到 */
@media (max-width: 768px) {
  /* 說明文字要看得舒服，高度就得從棋盤那邊借。
     這兩個數字是配著量出來的：最長的那一頁（工兵轉彎）在 375x812 下剛好不用捲。 */
  .demo-board { max-height: 32vh; max-height: 32dvh; }
  .demo-caption { font-size: 16px; line-height: 1.7; }
}

/* 鏡頭拉近時可以拖曳平移：要關掉瀏覽器自己的觸控捲動，否則手指一動就在捲頁面 */
.board.is-pannable { touch-action: none; cursor: grab; }
.board.is-pannable:active { cursor: grabbing; }

/* 三段大小的切換：擠在一起像一組，不要看起來像三顆各自獨立的按鈕 */
.zoombox { display: inline-flex; gap: 2px; }
.zoom-btn { min-width: 40px; padding-left: 10px; padding-right: 10px; }
.zoom-btn.is-on { background: var(--btn-secondary-ink); border-color: var(--btn-secondary-ink); color: var(--btn-secondary-bg); }

@media (max-width: 768px) {
  /* 手機：左右留白吃掉太多寬度，統計表會被擠到換行 */
  .overlay-code {
  margin: 2px 0 14px; font-size: 13px; letter-spacing: .08em;
  color: var(--muted, #8a8172); font-variant-numeric: tabular-nums;
}
.overlay-card { padding: 24px 18px; min-width: 0; width: 100%; }
}

.modal-lead { font-size: 15px; line-height: 1.7; margin-bottom: 10px; }
/* 視窗裡的輸入框（取代稱、輸入房號）。之前完全沒有樣式，長得像瀏覽器預設。 */
.modal-input {
  font: inherit; font-size: 16px;            /* 16px 以下 iOS 會自動放大整頁 */
  width: 100%; padding: 10px 12px; margin: 10px 0;
  border: 1px solid var(--panel-line); border-radius: 10px;
  background: #fff; color: var(--ink);
}
.modal-input:focus { outline: 2px solid var(--btn-secondary-line); outline-offset: 1px; border-color: transparent; }
.report-code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 14px;
  background: #fff; border: 1px solid var(--line-road); border-radius: 8px; padding: 10px 12px;
  margin: 8px 0; user-select: text; }
.gamecode { cursor: pointer; }

/* 盤邊對照表：只在桌機看整盤時出現（由 board.js 加上 .has-refs） */
.layer-refs { display: none; }
.board.has-refs .layer-refs { display: block; }
/* 盤邊說明的字級（單位是棋盤座標，桌機約 1.3 倍才是實際像素）。
   Lynch：「桌機我覺得說明文字都可以再更大一點」。 */
.ref-h { font-size: 22px; font-weight: 600; fill: var(--board-ink); }
.ref-t { font-size: 19px; fill: var(--board-ink); }
.ref-s { font-size: 17px; fill: var(--board-ink-soft); }
.layer-refs .ins { opacity: .95; }

.demo-play.is-on { background: var(--btn-secondary-ink); border-color: var(--btn-secondary-ink); color: var(--btn-secondary-bg); }


/* 這一局的頭銜：結算畫面最上面，要一眼看到 */
.title-card { text-align: center; padding: 14px 12px 12px; margin-bottom: 10px;
  border-radius: 14px; background: linear-gradient(180deg, #FFF6E0, var(--panel-bg));
  border: 1px solid var(--rank-star); }
.title-name { font-size: 26px; font-weight: 800; letter-spacing: .08em; color: var(--rank-star); }
.title-desc { font-size: 14px; color: var(--ink-soft); margin-top: 4px; }
.title-streak { margin-top: 8px; font-size: 13px; font-weight: 700; color: var(--seat-0); }

/* 大廳：誰坐哪、邀請連結 */
.lobby-seats { display: grid; gap: 8px; margin: 12px 0 4px; }
.lobby-row { display: flex; align-items: center; gap: 10px; }
.lobby-seat { font-weight: 700; min-width: 4.5em; color: var(--indicator, var(--ink)); }
.lobby-name { flex: 1; color: var(--ink-soft); }
.online-choice { display: flex; flex-direction: column; align-items: flex-start; gap: 2px;
  width: 100%; text-align: left; margin-top: 8px; padding: 12px 14px; }
.online-choice span { font-size: 13px; color: var(--ink-soft); font-weight: 400; }

/* 遊戲分級標示：標識至少 45×45（法規），文字要看得清楚，不是灰到看不見的小字 */
.rating-note { display: flex; align-items: center; gap: 10px; margin: 18px auto 0; max-width: 360px;
  font-size: 12px; line-height: 1.6; color: #b7aa98; text-align: left; }
.rating-note img { width: 48px; height: 48px; flex: none; }
.modal .rating-note { margin: 14px 0 0; max-width: none; color: inherit; opacity: .8; }
/* ── 進場畫面（v234，B 款暖木；WO-army-1015-04）──────────────────────
   照 GPT 首頁視覺稿：暖米牆＋木桌棋館場景當背景（img/home/bg-*.webp，GPT 畫的，不是程式畫的），
   上方膠囊小標＋深綠大標題（跟大廳 .lb-title 同一套字樣與顏色）＋兩行說明，
   下方三張入口卡（金色主卡＋兩張米白卡）、代稱膠囊、戰績、普遍級標示、營運者一行。
   ⚠ 它是蓋在遊戲上的一層（position: fixed），按下卡片才收掉；帶 ?room= 或 #lobby 進來的人不會看到。
   ⚠ 夜間版（body[data-skin="night"]）也固定用這套白天配色：背景圖本身是白天的暖色，
     文字跟著換成淺色反而看不清楚。所以這裡的顏色全部寫死在 .landing 自己的變數裡，不吃全站的 --ink／--btn-*。
   ⚠ 太矮的視窗（< 812 的手機、矮的桌機）讓 .landing 自己捲，整個 document 不捲。 */
.landing[hidden] { display: none; }
.landing {
  --ld-green: #24401f;                     /* 跟大廳標題同一個深綠 */
  --ld-green-line: rgba(45,90,58,.5);
  --ld-ink: #3a2a18;
  --ld-ink-soft: #5d4e38;
  --ld-cream: rgba(255,250,240,.9);
  position: fixed; inset: 0; z-index: 30;
  overflow-x: hidden; overflow-y: auto; overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  background: #efe3cc url(img/home/bg-desktop.webp) center / cover no-repeat;
  color: var(--ld-ink); font-family: var(--font);
  animation: landing-in .5s ease-out;
}
@keyframes landing-in { from { opacity: 0 } to { opacity: 1 } }
.landing-inner {
  display: flex; flex-direction: column; min-height: 100%;
  width: clamp(400px, 42vw, 700px);
  margin-left: clamp(24px, 4vw, 80px);
  padding: clamp(22px, 5.5vh, 64px) 0 clamp(16px, 3.5vh, 40px);
}
.landing-top { display: flex; flex-direction: column; align-items: flex-start; }
.landing-chip {
  display: inline-block; padding: 5px 22px; border-radius: 999px;
  font-size: 15px; font-weight: 600; letter-spacing: .12em; color: var(--ld-green);
  background: rgba(255,250,240,.82); border: 1.5px solid var(--ld-green-line);
  box-shadow: 0 2px 6px rgba(90,60,25,.12);
}
.landing-title {
  margin: clamp(10px, 2.4vh, 22px) 0 clamp(8px, 2vh, 18px);
  font-size: clamp(56px, 6.6vw, 104px); line-height: 1.05; font-weight: 600; letter-spacing: .06em;
  color: var(--ld-green);
  text-shadow: 0 1px 0 rgba(255,255,255,.7), 0 2px 12px rgba(255,248,230,.9);
}
.landing-sub {
  margin: 0; font-size: clamp(15px, 1.45vw, 21px); line-height: 1.85; font-weight: 600; letter-spacing: .03em;
  color: var(--ld-green); text-shadow: 0 1px 0 rgba(255,250,240,.85), 0 0 10px rgba(255,248,230,.9);
}
.landing-gap { flex: 0 0 clamp(18px, 6vh, 72px); }

/* 三張入口卡：還是原本的 <button>，只換長相 */
.landing-cards { display: grid; gap: clamp(10px, 1.8vh, 18px); }
.landing-card {
  display: flex; align-items: center; gap: 18px; width: 100%; text-align: left;
  font: inherit; color: var(--ld-ink); cursor: pointer;
  padding: clamp(10px, 1.7vh, 16px) 22px; border-radius: 18px;
  background: var(--ld-cream); border: 1.5px solid var(--ld-green-line);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.8), 0 4px 14px rgba(90,60,25,.16);
  transition: background .12s ease, filter .12s ease, transform .08s ease;
}
.landing-card:hover { background: #fffaf0; }
.landing-card:active { transform: translateY(1px); }
.landing-card:focus-visible { outline: 3px solid #f3d27a; outline-offset: 2px; }
/* 主卡「開始遊戲」：跟主按鈕 .btn--primary 同一套暖金（顏色寫死，夜間版不變） */
.landing-card--main {
  background: linear-gradient(180deg, #f8dc7e, #dba63a); border-color: #b98728; color: #3a2810;
  box-shadow: inset 0 1px 0 rgba(255,250,225,.8), inset 0 -3px 0 rgba(120,80,15,.22),
              0 3px 0 #b98728, 0 8px 18px rgba(120,80,15,.26);
}
.landing-card--main:hover { background: linear-gradient(180deg, #f8dc7e, #dba63a); filter: brightness(1.04); }
.landing-card-icon {
  width: clamp(48px, 6.5vh, 64px); height: auto; aspect-ratio: 1; flex: none; object-fit: contain;
  filter: drop-shadow(0 2px 3px rgba(60,35,10,.25));
}
.landing-card b {
  display: block; font-size: clamp(20px, 2.1vw, 30px); line-height: 1.25; font-weight: 600; letter-spacing: .06em;
  color: var(--ld-green);
}
.landing-card--main b { color: #3a2810; }
.landing-card small { display: block; margin-top: 2px; font-size: clamp(13px, 1.15vw, 16px); font-weight: 600; color: var(--ld-ink-soft); }
.landing-card--main small { color: #4a3414; }

/* 代稱、戰績、普遍級、營運者：米白半透明底＋深色字，壓在背景圖上也看得清楚 */
.landing-meta { display: flex; flex-direction: column; align-items: flex-start; gap: 10px; margin-top: clamp(14px, 2.6vh, 26px); }
.landing-foot { margin: 0; font-size: 13px; color: var(--ld-ink-soft); letter-spacing: .04em; }
.landing-foot a { color: inherit; }
.landing-stats {
  align-self: stretch; padding: 6px 14px; border-radius: 10px;
  font-size: 14px; font-weight: 600; color: var(--ld-ink);
  background: rgba(255,250,240,.78); box-shadow: 0 2px 6px rgba(90,60,25,.10);
}
.landing-stats:empty { display: none; }
/* 代稱：Lynch 說「太不明顯」→ 看得出可以按的膠囊 */
.landing-name {
  font: inherit; font-size: 15px; font-weight: 600; color: var(--ld-green); cursor: pointer; letter-spacing: .04em;
  padding: 7px 18px; border-radius: 999px;
  border: 1.5px solid var(--ld-green-line); background: rgba(255,250,240,.86);
  box-shadow: 0 2px 6px rgba(90,60,25,.12);
  display: inline-flex; align-items: center; gap: 10px;
  transition: background .12s ease;
}
.landing-name b { color: var(--ld-ink); font-weight: 600; }
.landing-name-edit { color: var(--ld-green); border-left: 1px solid var(--ld-green-line); padding-left: 10px; }
.landing-name:hover { background: #fffaf0; }
/* 普遍級標示：標識 48×48（法規至少 45×45），兩句警語一字不改 */
.landing .rating-note {
  margin: 0; max-width: none; gap: 14px; padding: 10px 16px; border-radius: 16px;
  font-size: 14px; font-weight: 600; line-height: 1.7; color: var(--ld-ink);
  background: rgba(255,250,240,.9); border: 1px solid rgba(185,138,82,.45);
  box-shadow: 0 4px 14px rgba(90,60,25,.16);
}
.landing .rating-note img { width: 48px; height: 48px; }
.landing-op {
  padding: 5px 14px; border-radius: 999px; font-size: 13px; font-weight: 600;
  background: rgba(255,250,240,.78); color: var(--ld-ink-soft);
}

/* 桌機（夠寬）：普遍級那張卡放到右下角，跟稿一樣；左欄只放文字、卡片、代稱、戰績、營運者 */
@media (min-width: 960px) and (min-aspect-ratio: 4/5) {
  .landing .rating-note {
    position: absolute; right: clamp(16px, 4vw, 80px); bottom: clamp(16px, 6vh, 72px);
    width: min(46vw, 760px); padding: 14px 22px; font-size: clamp(14px, 1.2vw, 17px);
  }
}
/* 矮的桌機（1280×720、1366×768）：收一點上下間距，一屏放得下（實測 720 高原本多出 21px 要捲） */
@media (min-aspect-ratio: 4/5) and (min-width: 621px) and (max-height: 780px) {
  .landing-gap { flex-basis: 14px; }
  .landing-meta { margin-top: 12px; gap: 8px; }
}

/* 大廳最上面那一行 */
.lobby-me { display: flex; align-items: center; gap: 10px; margin: 12px 0 4px;
  padding: 8px 10px; border-radius: 10px; background: rgba(120,95,55,.06); font-size: 14px; }
.lobby-me span { flex: 1; color: var(--ink-soft); }
.lobby-me b { color: var(--ink); }

/* 進場畫面・手機（與直立平板）：跟大廳同一個斷點。bg-mobile.webp（900×1947）長寬比正好是 375×812，
   牆在上、桌子約在圖高 31～58%。文字放牆上、中間空一段露出桌子、入口卡從桌子下緣往下排。
   375×812 一屏放得下；更矮的手機由 .landing 自己捲（背景固定在畫面上，不跟著捲）。 */
@media (max-width: 620px), (max-aspect-ratio: 4/5) {
  /* 背景整張往上挪 6vh（9vh 會讓說明字壓到紅椅子）（上方是空牆，挪掉不可惜），讓桌子與下方藍色座位離入口卡遠一點；
     底下挪出來的空位用同色漸層接上，不會看到圖的硬邊。 */
  .landing { background: linear-gradient(180deg, rgba(239,227,204,0) 76%, #efe3cc 90%),
                         url(img/home/bg-mobile.webp) center -6vh / 100% auto no-repeat, #efe3cc; }
  .landing-inner {
    width: auto; margin: 0; min-height: 100%;
    padding: max(14px, env(safe-area-inset-top)) 16px max(10px, env(safe-area-inset-bottom));
  }
  .landing-chip { font-size: 13px; padding: 3px 16px; }
  .landing-title { margin: 8px 0 6px; font-size: clamp(40px, 12.5vw, 64px); }
  .landing-sub { font-size: clamp(12.5px, 3.6vw, 16px); line-height: 1.7; }
  /* 10/17 Miles 看 App 版：系統字體調大時說明會擠成三行、壓到紅椅子上的小人。加一層淡米底，
     字放大壓到圖也看得清楚（App 殼的文字縮放另請架構長固定 100%）。 */
  .landing-sub { align-self: stretch; margin: 2px -8px 0; padding: 3px 8px; border-radius: 12px;  /* 左右負邊距：底往外推，文字寬度不變，一般字級不會因此多斷一行 */
    background: rgba(255,250,240,.78); box-shadow: 0 2px 8px rgba(90,60,25,.10); }
  /* 桌子那一段：至少留 110px，有空間就全部給它（卡片自然沉到桌子下緣） */
  .landing-gap { flex: 1 0 110px; }
  .landing-cards { gap: 8px; }
  .landing-card { gap: 14px; padding: 7px 16px; border-radius: 14px; }
  .landing-card-icon { width: 40px; }
  .landing-card b { font-size: 18px; }
  .landing-card small { font-size: 12px; margin-top: 0; }
  .landing-meta { gap: 6px; margin-top: 10px; }
  .landing-name { font-size: 13.5px; padding: 4px 14px; }
  .landing-stats { font-size: 12.5px; padding: 3px 12px; }
  /* 第一句 25 個字：375 寬時 11px、字距收 .05em 才排得進一行（實測 12px 會斷成「…避／免沉迷」三行）。
     更窄的手機排不下時 keep-all 讓它在逗號斷，不會把「沉迷」拆開。 */
  .landing .rating-note { gap: 6px; padding: 6px 8px; font-size: 11px; letter-spacing: -.05em; line-height: 1.6; word-break: keep-all; }
  .landing-op { align-self: center; font-size: 11.5px; padding: 2px 12px; }
  /* 產品長 10/15：手機首頁三張卡蓋住了桌子下半與藍色座位。收法：開始遊戲照舊一整張，
     多人連線、新手指南並排成兩張小卡（窄卡放不下說明，只留標題）；代稱和戰績排同一行。
     卡片整塊往下沉約 60px，四個座位都露出來。 */
  .landing-cards { grid-template-columns: 1fr 1fr; }
  .landing-card--main { grid-column: 1 / -1; }
  .landing-card:not(.landing-card--main) { gap: 10px; padding: 7px 12px; }
  .landing-card:not(.landing-card--main) .landing-card-icon { width: 34px; }
  .landing-card:not(.landing-card--main) small { display: none; }
  .landing-meta { flex-direction: row; flex-wrap: wrap; align-items: center; }
  .landing-meta > .rating-note, .landing-meta > .landing-op { flex-basis: 100%; }
  .landing-meta > .landing-op { flex-basis: auto; margin: 0 auto; }
}
/* 直立平板（例 768×1024）：卡片不要拉滿整個寬，置中限寬；背景往上挪一點，讓桌子落在文字與卡片之間 */
@media (min-width: 621px) and (max-aspect-ratio: 4/5) {
  .landing { background-position: center 22%; }
  .landing-inner { max-width: 560px; margin: 0 auto; }
}

/* 大廳的座位表 */
.lobby-row.is-me { background: rgba(47,95,208,.07); border-radius: 8px; padding: 4px 6px; margin: 0 -6px; }
.lobby-team {
  font-size: 11px; padding: 2px 7px; border-radius: 999px; letter-spacing: .06em;
  border: 1px solid currentColor; opacity: .75;
}
.lobby-team--0 { color: var(--seat-0); }
.lobby-team--1 { color: var(--seat-1); }
.lobby-error { margin-top: 10px; color: var(--camp-stroke); font-size: 13px; }

/* 多人連線視窗裡的「加入房間」 */
.join-box { margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--panel-line); }
.join-row { display: flex; gap: 8px; margin-top: 8px; }
.join-input { flex: 1; margin: 0; font-variant-numeric: tabular-nums; letter-spacing: .18em; }

/* 「換你囉」橫幅：高度固定，不論輪到誰都在，所以不會把下面的面板推來推去 */
/* v231（B 款）：輪到你時長得像稿上那顆金色「換你囉」主按鈕；沒輪到你時是淡米色、同樣高度。
   ⚠ 它不是按鈕：cursor 維持預設、沒有 hover／按下效果，免得玩家以為要按它。
   ⚠ 兩種狀態的 height 與邊框寬度完全一樣，只換顏色與陰影（v228 的「不准跳」規矩）。 */
.turnbanner {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  height: 44px; border-radius: 999px; font-size: 15px; font-weight: 600;
  color: var(--ink-soft); cursor: default;
  background: color-mix(in srgb, var(--panel-bg) 82%, var(--btn-primary-top));
  border: 1px solid color-mix(in srgb, var(--panel-line) 70%, var(--btn-primary-edge));
  box-shadow: inset 0 1px 0 rgba(255,255,255,.5);
}
.turnbanner.is-my-turn {
  color: var(--btn-primary-ink); font-size: 17px; font-weight: 700; letter-spacing: .12em;
  background: linear-gradient(180deg, var(--btn-primary-top), var(--btn-primary-bottom));
  border-color: var(--btn-primary-edge);
  box-shadow: inset 0 1px 0 rgba(255,250,225,.75), inset 0 -2px 0 rgba(120,80,15,.22),
              0 2px 0 var(--btn-primary-edge), 0 6px 16px rgba(120,80,15,.28);
}
.turnbanner.is-my-turn::before {
  content: ''; width: 9px; height: 9px; border-radius: 50%; background: var(--btn-primary-ink);
  animation: turn-pulse 1.4s ease-in-out infinite;
}

.join-error { margin-top: 8px; font-size: 13px; color: var(--camp-stroke); }

/* 戰報：走子那幾行可以點開，棋盤上會亮出起點與終點 */
.log li.is-clickable { cursor: pointer; }
.log li.is-clickable:hover { color: var(--ink); background: rgba(120,95,55,.07); border-radius: 6px; }
.log li.is-pinned { color: var(--ink); background: rgba(47,95,208,.10); border-radius: 6px;
  box-shadow: inset 2px 0 0 var(--seat-0); padding-left: 8px; }

.btn--tiny { padding: 3px 10px; font-size: 12px; }
.online-choice--lobby { box-shadow: inset 0 0 0 1px var(--btn-secondary-line); }

/* ── 手機：整個遊戲畫面就是一屏，棋盤佔最大 ────────────────────────────
   Lynch 10/14 實機：「點到棋盤他就會自動置中，畫面晃得很嚴重」「我希望著重在棋盤本身，
   而不是下面一大堆東西」。晃動來自頁面本身會捲（棋盤拖到底還會 scrollBy、網址列收放、
   面板長短變動）；所以手機上頁面不准捲：頂欄一行、棋盤吃掉剩下的高度、底下只留一條
   「換你囉」，座位與戰報收進「戰報」按鈕的面板。 */
@media (max-width: 620px) {
  html, body { overflow: hidden; overscroll-behavior: none; }
  .app {
    display: flex; flex-direction: column; gap: 6px;
    height: 100dvh; min-height: 0; overflow: hidden; padding: 8px;
  }
  /* v231（B 款）：頂欄是一條米白膠囊，像稿上方那顆「輪到你」的外框——
     但**不放「輪到你」字樣**（v230 刪掉的重複，手機上只靠下方橫幅講一次）。 */
  .topbar {
    flex: none; flex-wrap: nowrap; gap: 8px; align-items: center;
    padding: 4px 5px 4px 12px; border-radius: 999px;
    background: color-mix(in srgb, var(--panel-bg) 90%, transparent);
    border: 1px solid var(--panel-line);
    box-shadow: inset 0 1px 0 rgba(255,255,255,.6), 0 3px 10px rgba(90,60,25,.12);
  }
  .title { font-size: 16px; }
  .turn { display: none; }                 /* 底下那條橫幅已經在講輪到誰——不准拿掉這行 */
  .controls { width: auto; margin-left: auto; flex-wrap: nowrap; gap: 6px; }
  .controls .btn { padding: 5px 10px; }
  /* 「戰報」「⋯」：稿右下那種小方圓鈕（次按鈕的顏色、圓角方塊的形狀、底緣一道實心陰影） */
  .controls > .side-toggle, .controls > .controls-toggle {
    border-radius: 11px; min-width: 38px; justify-content: center;
    box-shadow: inset 0 1px 0 rgba(255,255,255,.7), 0 2px 0 color-mix(in srgb, var(--btn-secondary-line) 35%, transparent);
  }
  /* 手機桌邊 7px：再厚棋盤就被擠小（棋盤的大小是由寬度決定的，每多 1px 桌邊就少 2px 棋盤） */
  .stage { padding: 7px; border-radius: 17px; }
  .board { border-radius: 10px; }
  .side-toggle { display: inline-flex; align-items: center; }
  .setupbar { flex: none; }
  .stage { order: 0; flex: 1 1 0; min-height: 0; }
  .board { width: 100%; height: 100%; max-height: 100%; }
  .side { flex: none; display: block; }
  .turnbanner { height: 40px; font-size: 15px; }
  .turnbanner.is-my-turn { font-size: 16px; }
  .side-sheet { display: none; }
  body.side-open .side-sheet {
    display: flex; flex-direction: column; gap: 8px;
    position: fixed; left: 8px; right: 8px; bottom: 56px; z-index: 20;
    max-height: 62dvh; overflow-y: auto; padding: 10px; border-radius: 14px;
    background: var(--panel-bg); border: 1px solid var(--panel-line);
    box-shadow: 0 14px 34px rgba(70,48,22,.28);
  }
  body.side-open .side .panel { max-height: none; }
  body.side-open .side .panel:has(.log) { min-height: 0; }
  /* 面板打開時「戰報」反白（深綠底米白字），不改尺寸 */
  body.side-open .side-toggle { background: var(--btn-secondary-ink); color: var(--btn-secondary-bg); border-color: var(--btn-secondary-ink); }
}

/* ── 配對大廳／等人畫面（v232，B 款「暖木棋館」，WO-army-1015-03）──────────────
   原本是彈出框裡排方框（產品長 10/15：「陽春」、跟首頁斷掉），現在是蓋住整個視窗的一頁：
   背景是 GPT 畫的空桌場景圖（ui/img/lobby/），四個座位是疊在椅子上的真元素（app.js 的 lbSeat）。
   ⚠ 稿上的書法掛軸、植物、吊燈都是背景圖的一部分；這裡只有色塊、漸層、陰影、圓角，不畫插圖。

   版面怎麼算：上方標題區（.lb-head）與下方按鈕區（.lb-foot）的實際高度由 app.js 量好寫進
   --top-res／--bot-res；場景圖（.lb-scene）在「蓋滿視窗」與「四個座位剛好塞進兩者之間」
   兩個尺寸裡取小的，並讓座位那一段置中在兩者之間。
   → 手機 375×812 剛好蓋滿；更矮的手機場景會縮小一點，兩側露出模糊的同一張圖，但椅子不會被按鈕蓋住、頁面不必捲。
   座位位置用「佔場景圖寬／高的百分比」寫死在下面，所以不管場景縮放成多大，座位都貼在椅子上。 */
.lobby-page[hidden] { display: none; }
.lobby-page {
  /* overflow: clip 而不是 hidden：hidden 的盒子仍然可以被程式捲動——按下底部按鈕後焦點移動，
     瀏覽器把整頁往上捲了 40px，標題被切掉（本機 375×812 實測）。clip 連程式捲動都不准。 */
  position: fixed; inset: 0; z-index: 26; overflow: hidden; overflow: clip;
  background: #efe3cc; color: #3a2a18; font-family: var(--font);
  --lb-green: #24401f;                     /* 稿上大標題的深綠 */
  --lb-cream: #fbf3e2;
  /* 桌機場景 bg-desktop.webp（1600×1000）。椅子中心佔圖寬／圖高（10/15 疊 5% 格線量的）：
     上（紅）50%／17%、左（黃）24.5%／50%、右（綠）75%／50%、下（藍）50%／83%；
     桌面約 x 33～69%、y 26～71%。 */
  --lb-img: url(img/lobby/bg-desktop.webp);
  --ar: 1.6;
  --s-top: .08; --s-bot: .92;              /* 一定要露出來的那一段：上方小人頭頂～下方小人底 */
  --p-top-x: 50%;   --p-top-y: 17%;
  --p-left-x: 24.5%; --p-left-y: 50%;
  --p-right-x: 75%; --p-right-y: 50%;
  --p-bottom-x: 50%; --p-bottom-y: 83%;
  --fig: 10.5cqw; --fig-side: 10.5cqw;     /* 小人大小（cqw＝場景寬的 1%） */
  --side-shift: 0px;                       /* 左右兩家的名牌往桌子那邊挪多少 */
  --fade: 7%;                              /* 場景圖邊緣淡出的寬度：桌機兩側一定露出模糊背景，淡出長一點才不像貼了一張照片 */
  --top-res: 170px; --bot-res: 130px;      /* app.js 會量實際高度蓋掉這兩個預設值 */
}
/* 手機（與直立的平板）：bg-mobile.webp（900×1948，長寬比＝375×812）。
   椅子中心：上（紅）50%／24%、左（黃）9%／43%、右（綠）91%／43%、下（藍）50%／65%（太守量的，10/15 疊格線複核過）。 */
@media (max-width: 620px), (max-aspect-ratio: 4/5) {
  .lobby-page {
    --lb-img: url(img/lobby/bg-mobile.webp);
    --ar: .46203;
    --s-top: .18; --s-bot: .71;
    --p-top-x: 50%;  --p-top-y: 24%;
    --p-left-x: 9%;  --p-left-y: 43%;
    --p-right-x: 91%; --p-right-y: 43%;
    --p-bottom-x: 50%; --p-bottom-y: 65%;
    --fig: 24cqw; --fig-side: 21cqw;
    --side-shift: 7cqw;                    /* 左右椅子貼著螢幕邊，名牌往桌子挪才不會被切掉 */
    --fade: 3%;
    --top-res: 150px; --bot-res: 190px;
  }
}
/* 兩側露出來的部分：同一張圖放大、模糊，像鏡頭景深，不會出現一條硬邊 */
.lb-backdrop {
  position: absolute; inset: -40px;
  background: var(--lb-img) center / cover no-repeat;
  filter: blur(16px) saturate(.85) brightness(1.03);
}
.lb-scene {
  --avail: calc(100dvh - var(--top-res) - var(--bot-res));
  --span: calc(var(--s-bot) - var(--s-top));
  --H: min(max(100dvh, 100vw / var(--ar)), var(--avail) / var(--span));
  position: absolute; left: 50%;
  height: var(--H); width: calc(var(--H) * var(--ar));
  top: calc(var(--top-res) + (var(--avail) - var(--span) * var(--H)) / 2 - var(--s-top) * var(--H));
  transform: translateX(-50%);
  container-type: size;                    /* 座位用 cqw 跟著場景縮放 */
}
/* 場景圖畫在 ::before：邊緣淡出接到模糊的背景上；座位本身不吃這層遮罩 */
.lb-scene::before {
  content: ''; position: absolute; inset: 0;
  background: var(--lb-img) center / 100% 100% no-repeat;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 var(--fade), #000 calc(100% - var(--fade)), transparent),
                      linear-gradient(180deg, transparent, #000 var(--fade), #000 calc(100% - var(--fade)), transparent);
  -webkit-mask-composite: source-in;
  mask-image: linear-gradient(90deg, transparent, #000 var(--fade), #000 calc(100% - var(--fade)), transparent),
              linear-gradient(180deg, transparent, #000 var(--fade), #000 calc(100% - var(--fade)), transparent);
  mask-composite: intersect;
}

/* ── 座位：中心點就是椅子中心 ── */
.lb-seat {
  position: absolute; left: var(--x); top: var(--y);
  width: var(--fig); height: var(--fig); transform: translate(-50%, -50%);
  margin: 0; padding: 0; border: 0; background: none; font: inherit; color: inherit;
  display: block; z-index: 1;
  -webkit-tap-highlight-color: transparent;
}
.lb-seat--top    { --x: var(--p-top-x);    --y: var(--p-top-y);    --c: #8e2a1e; }
.lb-seat--left   { --x: var(--p-left-x);   --y: var(--p-left-y);   --c: #7a5c12; --shift: var(--side-shift);
  width: var(--fig-side); height: var(--fig-side); }
.lb-seat--right  { --x: var(--p-right-x);  --y: var(--p-right-y);  --c: #2f5a2a; --shift: calc(-1 * var(--side-shift));
  width: var(--fig-side); height: var(--fig-side); }
.lb-seat--bottom { --x: var(--p-bottom-x); --y: var(--p-bottom-y); --c: #24344f; }
button.lb-seat { cursor: pointer; }
.lb-fig {
  display: block; width: 100%; height: 100%; object-fit: contain; pointer-events: none;
  filter: drop-shadow(0 5px 6px rgba(60,35,10,.38));
  -webkit-user-drag: none;
}
.lb-seat.is-ai .lb-fig { opacity: .5; }
/* 名牌那一塊：上家放在小人下面（桌邊）、下家放在小人上面（桌邊）、左右兩家放小人下面並往桌子挪 */
.lb-info {
  position: absolute; left: 50%; z-index: 2;
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  transform: translateX(calc(-50% + var(--shift, 0px)));
  white-space: nowrap; pointer-events: none;
}
.lb-info > * { pointer-events: auto; }
.lb-seat--top .lb-info { top: calc(100% - 8px); }
.lb-seat--bottom .lb-info { bottom: calc(100% - 6px); flex-direction: column-reverse; }
.lb-seat--left .lb-info, .lb-seat--right .lb-info { top: calc(100% - 12px); }
/* 空位：名牌直接放在椅子正中間（往桌子挪一點） */
.lb-seat.is-empty .lb-info { top: 50%; bottom: auto; flex-direction: column;
  transform: translate(calc(-50% + var(--shift, 0px)), -50%); }
.lb-row { display: flex; align-items: center; gap: 5px; }
.lb-plate {
  display: inline-block; max-width: 8.5em; overflow: hidden; text-overflow: ellipsis;
  padding: 4px 12px; border-radius: 8px;
  font-size: 13.5px; font-weight: 600; letter-spacing: .06em; line-height: 1.35;
  color: var(--lb-cream);
  background: linear-gradient(180deg, var(--c), color-mix(in srgb, var(--c) 68%, #000000));
  border: 1px solid rgba(255,232,190,.38);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.18), 0 3px 8px rgba(40,20,5,.35);
}
/* 自己：大一號、深藍底＋金邊，一眼找到 */
.lb-seat.is-me .lb-plate {
  font-size: 16px; padding: 5px 20px; letter-spacing: .14em;
  background: linear-gradient(180deg, #2f4166, #1c2a42);
  border: 1.5px solid #e3bf6a;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 0 0 3px rgba(227,191,106,.25), 0 4px 10px rgba(20,15,5,.4);
}
/* 空位的牌子：深木色。可以坐的那種加金邊、整張椅子有呼吸光暈 */
.lb-seat.is-empty .lb-plate { --c: #3a2716; background: linear-gradient(180deg, #4a321c, #2c1d0f); }
.lb-seat.is-open .lb-plate { border-color: #e3bf6a; font-size: 14.5px; padding: 6px 14px; }
.lb-seat.is-open:hover .lb-plate { background: linear-gradient(180deg, #5a3e22, #352312); }
.lb-seat.is-open:focus-visible { outline: none; }
.lb-seat.is-open:focus-visible .lb-plate { outline: 2px solid #f3d27a; outline-offset: 2px; }
.lb-seat:not(.is-open).is-empty .lb-plate { opacity: .82; font-weight: 500; }
/* 呼吸光暈：只動 opacity，不改大小與位置（手機上版面不能動） */
.lb-glow {
  position: absolute; left: 50%; top: 50%; width: 88%; height: 88%;
  transform: translate(-50%, -50%); border-radius: 50%; pointer-events: none;
  background: radial-gradient(circle, rgba(255,222,140,.62) 0%, rgba(255,222,140,.28) 45%, rgba(255,222,140,0) 70%);
  opacity: .35;
}
.lb-seat.is-open .lb-glow { animation: lb-breathe 2.4s ease-in-out infinite; }
@keyframes lb-breathe { 0%, 100% { opacity: .3; } 50% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .lb-seat.is-open .lb-glow { animation: none; opacity: .8; } }
/* 名牌下面那行小字：隊友／敵方・準備狀態 */
.lb-meta {
  font-size: 11.5px; font-weight: 600; letter-spacing: .04em; line-height: 1.4;
  color: #fff6e4; background: rgba(42,27,12,.62); padding: 1px 8px; border-radius: 999px;
}
.lb-seat.is-ready .lb-meta { background: rgba(30,92,48,.85); }
.lb-act.btn--tiny { padding: 3px 9px; font-size: 12px; }

/* ── 上方：返回／代稱、小字標籤、大標題、人數膠囊、戰績卡 ── */
.lb-head {
  position: absolute; left: 0; right: 0; top: 0; z-index: 3;
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  padding: max(10px, env(safe-area-inset-top)) 16px 10px; text-align: center;
  background: linear-gradient(180deg, rgba(248,240,226,.82), rgba(248,240,226,.5) 70%, rgba(248,240,226,0));
  pointer-events: none;
}
.lb-head > * { pointer-events: auto; }
.lb-bar { align-self: stretch; display: flex; justify-content: space-between; align-items: center; gap: 8px; min-height: 28px; }
.lb-back, .lb-name {
  font: inherit; font-size: 12.5px; line-height: 1.3; cursor: pointer; white-space: nowrap;
  color: var(--lb-green); background: rgba(255,250,240,.78);
  border: 1px solid rgba(45,90,58,.32); border-radius: 999px; padding: 4px 11px;
  transition: background .12s ease;
}
.lb-back:hover, .lb-name:hover { background: #fffaf0; }
.lb-name { min-width: 0; max-width: 62%; overflow: hidden; text-overflow: ellipsis; }
.lb-name b { font-weight: 600; }
.lb-name-edit { margin-left: 7px; padding-left: 7px; border-left: 1px solid rgba(45,90,58,.3); font-weight: 700; }
.lb-label {
  display: flex; align-items: center; gap: 12px;
  font-size: 13px; font-weight: 600; letter-spacing: .3em; color: var(--lb-green);
}
.lb-label::before, .lb-label::after { content: ''; width: 40px; height: 1px; background: currentColor; opacity: .55; }
.lb-title {
  margin: 0; font-size: clamp(32px, 9.5vw, 48px); line-height: 1.12; font-weight: 600; letter-spacing: .14em;
  padding-left: .14em;                     /* 字距會在最後一個字後面多留一格，補回來才置中 */
  color: var(--lb-green);
  text-shadow: 0 1px 0 rgba(255,255,255,.7), 0 2px 10px rgba(255,248,230,.9);
}
.lb-pill-row { display: flex; align-items: center; justify-content: center; gap: 4px 10px; flex-wrap: wrap; margin-top: 3px; }
.lb-pill {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 5px 16px; border-radius: 999px; font-size: 14px; font-weight: 600; letter-spacing: .04em;
  color: var(--lb-green); background: rgba(255,250,240,.88); border: 1px solid rgba(45,90,58,.42);
  box-shadow: 0 2px 6px rgba(90,60,25,.12); font-variant-numeric: tabular-nums;
}
/* 兩個人的小圖示：純 CSS 圓點與半圓，不是插圖 */
.lb-pill-icon {
  width: 17px; height: 13px; flex: none;
  background:
    radial-gradient(circle at 5.5px 3.6px, currentColor 3px, transparent 3.4px),
    radial-gradient(ellipse 5.2px 4.4px at 5.5px 13px, currentColor 98%, transparent 100%),
    radial-gradient(circle at 12.6px 4.2px, currentColor 2.5px, transparent 2.9px),
    radial-gradient(ellipse 4.4px 3.8px at 12.8px 13px, currentColor 98%, transparent 100%);
}
.lb-side-note { font-size: 12.5px; font-weight: 600; color: var(--lb-green); opacity: .85; }
.lb-swap {
  font: inherit; font-size: 12.5px; font-weight: 600; color: var(--lb-green); cursor: pointer;
  background: none; border: 0; padding: 3px 2px; text-decoration: underline; text-underline-offset: 3px;
}

/* 戰績小卡（照 戰績小卡.png）：米白底、木色框。桌機是三格大卡；窄的時候縮成一行（見下方） */
.lb-stats {
  margin-top: 6px; width: min(420px, 100%); padding: 9px 14px 7px; border-radius: 14px;
  background: linear-gradient(180deg, #fffaf0, #f5ead4); color: #2f2414;
  border: 2px solid #b98a52;
  box-shadow: inset 0 0 0 3px #fffaf0, inset 0 0 0 4px rgba(185,138,82,.32), 0 6px 18px rgba(90,60,25,.22);
}
.lb-stats-title { font-size: 15px; font-weight: 600; letter-spacing: .22em; color: var(--lb-green); }
.lb-stats-cells { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; margin: 6px 0 5px; }
.lb-stats-cell {
  display: grid; grid-template-columns: auto auto; justify-content: center; align-items: baseline; column-gap: 3px;
  padding: 3px 6px 4px; border-radius: 10px; border: 1px solid rgba(185,138,82,.42); background: rgba(255,255,255,.55);
}
.lb-stats-k { grid-column: 1 / -1; font-size: 12px; color: #6b5a41; text-align: center; }
.lb-stats-n { font-size: 28px; line-height: 1.1; font-weight: 600; color: #1f2a18; font-variant-numeric: tabular-nums; }
.lb-stats-u { font-size: 12px; color: #6b5a41; }
.lb-stats-sub { font-size: 12.5px; color: #5d4e38; border-top: 1px solid rgba(185,138,82,.35); padding-top: 4px; }
.lb-stats-empty { font-size: 14px; color: #5d4e38; padding: 4px 0 2px; }
/* 很寬的桌機：大卡放左邊牆前（稿上兩側那種木框卡），不佔標題區的高度，桌子就能畫大一點 */
@media (min-width: 1100px) and (min-aspect-ratio: 4/5) {
  .lb-stats { position: fixed; left: 28px; top: 50%; transform: translateY(-50%); width: 250px; margin: 0; }
  .lb-stats-cells { grid-template-columns: 1fr; gap: 6px; }
  .lb-stats-title { padding: 2px 0 2px; }
}
/* 窄的時候（手機、小桌機）：縮成一行，優先保住座位與按鈕 */
@media (max-width: 1099px), (max-aspect-ratio: 4/5) {
  .lb-stats {
    display: flex; align-items: center; justify-content: center; gap: 10px;
    width: auto; max-width: 100%; margin-top: 5px; padding: 4px 12px; border-width: 1.5px; border-radius: 11px;
    box-shadow: inset 0 0 0 2px #fffaf0, 0 3px 10px rgba(90,60,25,.18);
  }
  .lb-stats-title { font-size: 12px; letter-spacing: .06em; white-space: nowrap; }
  .lb-stats-cells { display: flex; gap: 10px; margin: 0; }
  .lb-stats-cell { display: flex; align-items: baseline; gap: 2px; padding: 0; border: 0; background: none; }
  .lb-stats-k { font-size: 11.5px; }
  .lb-stats-n { font-size: 17px; }
  .lb-stats-u { font-size: 11px; }
  .lb-stats-sub { display: none; }
  .lb-stats-empty { font-size: 12.5px; padding: 0; }
}

/* ── 下方：房號、說明一行、主／次按鈕、規則說明 ── */
.lb-foot {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 3;
  display: flex; flex-direction: column; align-items: center; gap: 8px;
  padding: 22px 16px max(12px, env(safe-area-inset-bottom));
  background: linear-gradient(0deg, rgba(246,237,220,.94), rgba(246,237,220,.8) 62%, rgba(246,237,220,0));
  pointer-events: none;
}
.lb-foot > * { pointer-events: auto; }
.lb-hint {
  max-width: 100%; min-height: 1.45em; line-height: 1.45;
  font-size: 14px; font-weight: 600; color: var(--lb-green);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  text-shadow: 0 1px 0 rgba(255,250,240,.85);
}
.lb-hint.is-error { color: var(--camp-stroke); }
.lb-actions { display: flex; justify-content: center; gap: 12px; width: 100%; }
.lb-btn { min-height: 48px; padding: 10px 26px; font-size: 16px; letter-spacing: .06em; }
.lb-btn--main { min-width: 280px; font-size: 18px; letter-spacing: .12em; }
.lb-btn--main.btn--primary { box-shadow: inset 0 1px 0 rgba(255,250,225,.8), inset 0 -3px 0 rgba(120,80,15,.25),
  0 3px 0 var(--btn-primary-edge), 0 8px 18px rgba(120,80,15,.28); }
.lb-btn.is-done { font-size: 16px; letter-spacing: .04em; }
.lb-links { display: flex; align-items: center; gap: 16px; font-size: 13px; }
.lb-link {
  font: inherit; font-size: 13.5px; font-weight: 600; color: var(--lb-green); cursor: pointer;
  background: none; border: 0; padding: 2px 4px; text-decoration: underline; text-underline-offset: 4px;
}
.lb-rating { display: inline-flex; align-items: center; gap: 4px; font-size: 11.5px; color: #6b5a41; }
.lb-rating img { width: 20px; height: 20px; }
/* 朋友房：房號大字＋複製邀請連結（不露網址，v230） */
.lb-room {
  display: flex; align-items: center; gap: 12px;
  padding: 5px 6px 5px 14px; border-radius: 14px;
  background: rgba(255,250,240,.92); border: 1px solid rgba(45,90,58,.35);
  box-shadow: 0 3px 10px rgba(90,60,25,.14);
}
.lb-room-code { display: flex; align-items: baseline; gap: 8px; color: var(--lb-green); }
.lb-room-k { font-size: 12px; letter-spacing: .12em; font-weight: 600; }
.lb-room-code b { font-size: 28px; line-height: 1.15; letter-spacing: .12em; font-weight: 600; color: #1f2a18;
  font-variant-numeric: tabular-nums; user-select: text; -webkit-user-select: text; }
.lb-copy { padding: 8px 14px; }

@media (max-width: 620px), (max-aspect-ratio: 4/5) {
  .lb-head { padding: max(8px, env(safe-area-inset-top)) 12px 8px; gap: 2px; }
  .lb-label { font-size: 12px; }
  .lb-label::before, .lb-label::after { width: 28px; }
  .lb-pill { font-size: 13.5px; padding: 4px 14px; }
  .lb-foot { gap: 7px; padding: 18px 16px max(10px, env(safe-area-inset-bottom)); }
  .lb-hint { font-size: 13px; }
  .lb-actions { flex-direction: column; align-items: center; gap: 9px; }
  .lb-btn { width: min(100%, 330px); min-width: 0; min-height: 46px; }
  .lb-btn--main { min-height: 54px; font-size: 19px; }
  .lb-plate { font-size: 13px; padding: 3px 10px; }
  .lb-seat.is-me .lb-plate { font-size: 15px; padding: 4px 16px; }
  .lb-room-code b { font-size: 26px; }
}

/* 程式沒載入完成時的提示（index.html 的看門狗放進首頁最上面） */
.boot-fail { margin: 0 0 12px; padding: 12px 14px; border-radius: 14px; font-size: 14px; line-height: 1.6;
  background: #fff4e0; border: 1.5px solid #c99419; color: #3a2a18; }
.boot-fail .btn { margin-top: 8px; }
