/* ================= FindMy 管理台 ================= */
:root {
  color-scheme: dark;   /* 原生控件（select 展開清單、日期時間選擇器、捲軸）跟著走深色 */
  --bg: #0f172a;
  --panel: #1e293b;
  --panel-2: #172033;
  --border: #334155;
  --text: #e2e8f0;
  --muted: #94a3b8;
  --accent: #3b82f6;
  --fresh: #22c55e;
  --stale: #eab308;
  --old: #64748b;
  --danger: #ef4444;
  /* 警示「文字」色（橫額／指令面板／用戶權限）：--stale 當前景放在深色底上偏暗 */
  --warn-text: #fde68a;
  /* 2026-11-03 無障礙波補的三顆「前景／白字底」專用 token，理由是算出來的不是美感：
     --danger 放在 --panel 上只有 3.89:1，而面板多數文字是 12–13px（AA normal 要 4.5:1）；
     白字放在 --accent #3b82f6 上只有 3.68:1。底色繼續用上面那兩顆，
     只有「字要讀得清」的路徑改用這三顆（實測值寫在行尾）。 */
  --danger-text: #f87171;    /* on --panel 5.29:1／on --bg 6.45:1 */
  --accent-text: #60a5fa;    /* on --panel 5.75:1／on --bg 7.02:1（分頁選中、焦點環） */
  --accent-strong: #2563eb;  /* 白字 5.17:1（主要按鈕、badge） */

  /* 2026-11-03 版面波：字級 12 種收成 7 階、圓角 9 種收成 6 階。
     原本的 10.5／11／11.5 和 12.5／13 和 13.5／14 和 14.5／15 各差 0.5px——
     肉眼看不出差別，但「這頁該用哪一號」沒有答案，所以十個分頁會各自挑，
     同一種資訊（表格副行、徽章、按鈕）在不同頁長得不一樣。
     併階規則：帶 .5 的那一號靠到已經存在的那個整數（12.5→13、13.5→14、14.5→15、10.5→11、11.5→11），
     一次動 0.5px，不是重新排版；實測 12.5→13 在最寬的 📊 軌跡表不產生橫向捲動（1100px 下 scrollWidth 皆 1064）。 */
  --fs-xs: 11px;      /* 副行、時間戳、徽章（含原 10.5／11／11.5） */
  --fs-sm: 12px;      /* 次要文字、側欄子列 */
  --fs-md: 13px;      /* 表格、按鈕、表單（含原 12.5）——面板的主力字級 */
  --fs-base: 14px;    /* 內文、輸入框（含原 13.5） */
  --fs-lg: 15px;      /* 小標題（.card h3／.view-bar h2）、icon-btn（含原 14.5） */
  --fs-brand: 16px;   /* 只有頂欄品牌字 */
  --fs-icon: 18px;    /* 品牌 emoji、登入卡標題、開機碼輸入框 */
  /* 圓角：3→4、14→10、24→999 是併階；--r-pill／--r-full 留給「膠囊」與「圓形」兩種真形狀 */
  --r-xs: 4px;        /* 極小色塊、代碼片段（含原 3px） */
  --r-sm: 6px;        /* 按鈕、輸入框、小卡片 */
  --r-md: 8px;        /* 面板卡片、彈窗 */
  --r-lg: 10px;       /* 大卡片、橫額（含原 14px） */
  --r-pill: 999px;    /* 膠囊（含原 24px 的開關軌道） */
  --r-full: 50%;      /* 圓形（狀態點、頭像） */

  /* 2026-11-03 版面外框的「一把尺」（是外框留白，不是元件內距）：
     --gutter   主內容區（.view-bar 標題列、.card、.list-tools 與 .table-wrap／
                 .notify-grid／.bot-grid／.guide-scroll 同一條左邊界，實測原本都是 18px）
     --gutter-sm 頂欄與三塊側欄（#topbar／.sidebar-head／.sp-*／.history-* 原本都寫 14px，
                 但 🔲 地理圍欄的 .zone-* 與 📜 事件的 .filter-box 自寫 12px——同一欄的標題
                 與內容差 2px，讀起來就是「縮進去一格」，這一波把它們收同一把尺）
     --gutter-xs 分頁列（#tabs）
     ≤760px 那一組在手機 query 裡重新宣告，所有引用處一起跟著收，不必逐條改。
     刻意留在尺外的：.ap-head／.ap-sec（🔐 私鑰匯出彈窗是兩欄版面，16px 自成一套）、
     各元件自己的內距（按鈕、輸入框、列表列的 hover 盒、.toast、文字清單的 ul 縮排）。 */
  --gutter: 18px;
  --gutter-sm: 14px;
  --gutter-xs: 10px;

  /* 2026-11-03 第 6 批：行高與動效時長也上尺。行高原來是 19 條宣告、9 種值
     （1／1.2／1.4／1.45／1.5／1.55／1.6／1.65／12px），其中 1.5↔1.55、1.6↔1.65 在 13px
     上只差 0.65px——肉眼分不出，但「這段該用哪一號」沒有答案，於是各頁自己挑。
     併階靠到**已有那一檔**：1.4／1.45→1.5、1.55／1.65→1.6，一次動 ≤0.1 倍（實測逐元素行盒
     最多 +1.1px、−0.6px，見 README §4.1.4 第 6 批對照表）。三階的分工看**讀者的動作**不是看尺寸：
     tight＝單行控件、base＝一到兩行的提示與表格文字、read＝要讀完一段的說明。 */
  --lh-tight: 1.2;    /* 按鈕（.btn）這類點一下就收的控件 */
  --lh-base: 1.5;     /* body 預設、橫額、短提示、終端式 curl 區塊 */
  --lh-read: 1.6;     /* 多行說明：指引段落、彈窗內容、結果句、步驟清單、🔐 log */

  /* 動效時長原本三個近似值（.15s／.2s／.25s）加上各寫一次的脈動（1.2s／1.3s），收兩顆意圖
     token ＋ 一顆脈動：fast＝狀態回饋（邊框／文字換色，要「立刻」）、move＝位移（側欄收起、
     開關軌道與旋鈕，要看得見過程）。圖釘脈動由 1.3s 併到 1.2s——同一個「有新資料」事件在列表、
     圖釘、統計卡是三條動畫，舊值讓地圖那側晚 0.1s 收尾，現在同一拍結束。
     刻意留在尺外的兩條：`transition: none`（減少動態偏好那塊，是開關不是時長）與
     `kxWaitPulse 1.7s`（等待時的呼吸拍，語意不是「回饋」，而且只有一處用到）。 */
  --t-fast: .15s;
  --t-move: .25s;
  --t-flash: 1.2s;
}

* { box-sizing: border-box; margin: 0; padding: 0; }
html, body { height: 100%; }
body {
  font-family: -apple-system, "PingFang TC", "PingFang SC", "Microsoft JhengHei", "Noto Sans TC", sans-serif;
  background: var(--bg); color: var(--text); font-size: var(--fs-base);
  /* 之前 body 沒設 line-height → 吃瀏覽器 normal（約 1.2），而面板大量文字是 12–13px 的
     中文，行與行貼在一起。表格／按鈕另有自己的行高，這裡只補「沒被點名」的那幾處。 */
  line-height: var(--lh-base);
  display: flex; flex-direction: column; overflow: hidden;
}

.hidden { display: none !important; }
/* 只在「視覺」上藏起來：仍在無障礙樹裡、仍會被螢幕閱讀器讀到，也不會吃版面。
   不能用 display:none／visibility:hidden——那兩個會連無障礙樹一起拿掉。 */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0;
}
/* 分頁按鈕以 hidden 屬性做 RBAC 隱藏；!important 防被元件樣式的 display 蓋掉 */
[hidden] { display: none !important; }
.muted { color: var(--muted); }

/* ---------- 頂部工具列 ---------- */
/* 換行是「內容驅動」而不是「手機驅動」：#topbar 的自然單列寬實測約 1130px，
   1280px 的筆電只剩約 150px 餘裕——多一顆 badge、站名長一點就會溢出被裁掉，
   而舊規則只在 ≤760px 才 wrap，所以 900px 那一帶是「既不換行也看不全」。
   wrap 搬到基線規則後：寬幅仍是一列（實測 1440px），窄幅自己換列（實測 900px）。
   row-gap 6px 是換列後兩列之間的縫，手機那塊再壓 padding。 */
#topbar {
  display: flex; align-items: center; gap: 10px;
  flex-wrap: wrap; row-gap: 6px;
  padding: 8px var(--gutter-sm); background: var(--panel);
  border-bottom: 1px solid var(--border); flex: 0 0 auto;
}
.brand { display: flex; align-items: center; gap: 8px; font-weight: 700; font-size: var(--fs-brand); }
.brand-icon { font-size: var(--fs-icon); }
.topbar-spacer { flex: 1; }

/* 膠囊家族只有一套餐具：.pill（狀態）與 .chip（可點，見下方 ❓ 指引）共用外形，
   兩者過去各自寫了一份一樣的 border／圓角／內距／字級——改一顆另一顆不會跟著改。
   刻意不折進來的：.badge（分頁上的計數泡泡，1px 6px＋實心底色）、.m-badge（HTTP 方法色，
   方角、白字、留在 token 外）、.zone-tag（行內小標）——它們是另一種形狀，不是同一種東西。 */
.pill, .chip {
  border: 1px solid var(--border); border-radius: var(--r-pill);
  padding: 3px 12px; font-size: var(--fs-sm);
}
.pill { font-weight: 600; }
.pill-green { color: var(--fresh); border-color: var(--fresh); }
.pill-red { color: var(--danger-text); border-color: var(--danger); }
.pill-gray { color: var(--muted); }
.pill-yellow { color: var(--stale); border-color: var(--stale); }

.accounts { display: flex; gap: 6px; font-size: var(--fs-sm); color: var(--muted); }
.accounts .acct {
  background: var(--panel-2); border: 1px solid var(--border);
  padding: 3px 10px; border-radius: var(--r-sm); max-width: 220px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.accounts .acct b { color: var(--text); font-weight: 600; }

/* ---------- Apple 未登入提示橫額 ---------- */
.banner {
  flex: 0 0 auto; padding: 7px var(--gutter-sm); font-size: var(--fs-md); line-height: var(--lh-base);
  background: rgba(234, 179, 8, .12); color: var(--warn-text);
  border-bottom: 1px solid rgba(234, 179, 8, .35);
}
.banner code {
  background: rgba(0, 0, 0, .3); padding: 1px 5px; border-radius: var(--r-xs);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: var(--fs-xs);
}
.banner { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.banner-btn { flex: 0 0 auto; width: auto; }

/* ---------- Apple 帳號登入面板 ---------- */
.apple-panel {
  position: fixed; left: 0; right: 0; z-index: 1500;
  background: var(--panel); border-bottom: 1px solid var(--border);
  box-shadow: 0 14px 40px rgba(0, 0, 0, .55);
  max-height: calc(100vh - 56px); overflow: auto;
}
.ap-head {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 16px; border-bottom: 1px solid var(--border);
  position: sticky; top: 0; background: var(--panel); z-index: 1;
}
.ap-title { font-weight: 700; font-size: var(--fs-lg); }
/* 展開時需能在 flex 列中收縮，否則長句會把關閉鈕擠出面板 */
.ap-note { font-size: var(--fs-sm); flex: 1 1 auto; min-width: 0; line-height: var(--lh-base); }
.ap-close { margin-left: auto; }

.ap-grid {
  display: grid; grid-template-columns: 1fr 1fr; gap: 0;
}
.ap-sec { padding: 14px 16px; display: flex; flex-direction: column; gap: 10px; }
.ap-sec + .ap-sec { border-left: 1px solid var(--border); }
.ap-sec-title { font-weight: 600; font-size: var(--fs-base); }
.ap-sec-status { font-size: var(--fs-sm); color: var(--muted); }
.ap-sec-status.ok { color: var(--fresh); }
.ap-sec-status.wait { color: var(--stale); }

.ap-form { display: flex; flex-direction: column; gap: 8px; }
.ap-form input[type="email"], .ap-form input[type="password"], .ap-form input[type="text"] {
  /* 注意：不要加 width:100% —— 與 .ap-row 的 flex 搭配會觸發 Chromium
     insertText 的自動化異常；flex:1 已負責撐滿寬度 */
  background: var(--panel-2); color: var(--text);
  border: 1px solid var(--border); border-radius: var(--r-sm); padding: 7px 10px; font-size: var(--fs-md);
}
.ap-form select {
  flex: 1; background: var(--panel-2); color: var(--text);
  border: 1px solid var(--border); border-radius: var(--r-sm); padding: 7px 10px; font-size: var(--fs-md);
}
.ap-row { display: flex; gap: 8px; align-items: center; }
.ap-row input { flex: 1; }
.ap-actions { display: flex; gap: 8px; }
.ap-hint { font-size: var(--fs-sm); color: var(--muted); line-height: var(--lh-base); }
.ap-check { display: flex; align-items: center; gap: 7px; font-size: var(--fs-sm); color: var(--muted); }
.ap-ok {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  font-size: var(--fs-md); color: var(--fresh);
}
.ap-err { color: var(--danger-text); font-size: var(--fs-md); line-height: var(--lh-base); }
.ap-err button { margin-left: 8px; }

/* ---------- AirTag 私鑰匯出 ---------- */
.kx {
  margin-top: 4px; padding: 12px;
  background: var(--panel-2); border: 1px solid var(--border); border-radius: var(--r-md);
  display: flex; flex-direction: column; gap: 10px;
}
.kx-title { font-weight: 600; font-size: var(--fs-base); }
.kx-status { font-size: var(--fs-sm); color: var(--muted); font-variant-numeric: tabular-nums; }
.kx-status.ok { color: var(--fresh); }
.kx-status.wait { color: var(--stale); }
.kx-status.err { color: var(--danger-text); }
.kx-step { font-size: var(--fs-md); color: var(--accent-text); font-variant-numeric: tabular-nums; }
.kx-wait {
  background: rgba(59, 130, 246, .10);
  border: 1px solid rgba(59, 130, 246, .35);
  border-radius: var(--r-md); padding: 10px;
  display: flex; flex-direction: column; gap: 8px;
}
.kx-wait-title { font-size: var(--fs-md); font-weight: 600; }
.kx-wait-hint { white-space: pre-line; }
.kx-wait-hint.warn { color: var(--warn-text); }
/* 需要使用者輸入：邊框脈動兩下，避免在長面板裡被忽略 */
.kx-wait-urgent {
  border-color: var(--accent);
  animation: kxWaitPulse 1.7s ease-in-out 2;
}
@keyframes kxWaitPulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(59, 130, 246, 0); }
  50% { box-shadow: 0 0 0 5px rgba(59, 130, 246, .30); }
}
.kx-actions { display: flex; gap: 8px; flex-wrap: wrap; }
.kx-log {
  max-height: 200px; overflow: auto;
  font-size: var(--fs-xs); line-height: var(--lh-read);
  background: rgba(0, 0, 0, .3); color: #cbd5e1;
  border-radius: var(--r-md); padding: 8px 10px;
  white-space: pre-wrap; word-break: break-all;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.kx-result { font-size: var(--fs-md); line-height: var(--lh-read); }
.kx-result ul { margin: 6px 0 0; padding-left: 18px; }
.kx-result li { margin: 2px 0; }
.kx-done-title { color: var(--fresh); font-weight: 600; }
@media (max-width: 900px) {
  .ap-grid { grid-template-columns: 1fr; }
  .ap-sec + .ap-sec { border-left: none; border-top: 1px solid var(--border); }
}

.stats { display: flex; gap: 12px; font-size: var(--fs-sm); color: var(--muted); font-variant-numeric: tabular-nums; }
/* 任一裝置的 stale_reason 不是 ok（會話過期／抓取失敗／被隔離）→ 資料時間徽章轉警告色 */
.stats .stat-warn { color: var(--danger-text); font-weight: 600; }
.warn-line { color: var(--danger-text); }

.ctl { display: flex; align-items: center; gap: 6px; font-size: var(--fs-sm); color: var(--muted); }
select, input {
  background: var(--panel-2); color: var(--text);
  border: 1px solid var(--border); border-radius: var(--r-sm);
  padding: 5px 8px; font-size: var(--fs-md);
}
/* 焦點環：舊寫法只有 `select:focus, input:focus`（1px、--accent），按鈕／分頁／可點列
   用鍵盤 Tab 走到時完全看不見焦點在哪。:focus-visible 而不是 :focus → 滑鼠點擊不會套環，
   只有 Tab／Enter／Space 這種「用鍵盤聚焦」才亮。環用 --accent-text（深色底 5.75:1，
   非文字要求 3:1 也過）。 */
:focus-visible { outline: 2px solid var(--accent-text); outline-offset: 2px; }
/* 貼著容器邊的元素用 +2px 會被裁掉（#tabs 在手機上還會橫向捲），所以各自往內收 */
.tab:focus-visible, .device-item:focus-visible, .zone-item:focus-visible { outline-offset: -2px; }
/* 黏住的表頭用 +2px 會被捲動容器上緣裁掉一半，同樣往內收 */
th[data-sort]:focus-visible { outline-offset: -2px; }

.btn {
  background: var(--panel-2); color: var(--text);
  border: 1px solid var(--border); border-radius: var(--r-sm);
  padding: 5px 10px; font-size: var(--fs-base); cursor: pointer; line-height: var(--lh-tight);
}
/* hover 的邊框變色只給「真的還能按」的鈕：實測 app.js 有 49 處 disabled，
   而停用態之前 0 條樣式，按不下去的鈕仍會邊框變藍＝假保證。 */
.btn:hover:not(:disabled) { border-color: var(--accent); }
.btn-ghost { background: transparent; }
.btn-primary { background: var(--accent-strong); border-color: var(--accent-strong); color: #fff; font-weight: 600; width: 100%; padding: 9px; }
.btn-primary:hover:not(:disabled) { filter: brightness(1.1); }
.btn-sm { padding: 3px 9px; font-size: var(--fs-sm); }
.btn:disabled, .icon-btn:disabled, .fab-sidebar:disabled { opacity: .45; cursor: not-allowed; }
input:disabled, select:disabled, textarea:disabled { opacity: .55; cursor: not-allowed; }

/* ---------- 主體佈局 ---------- */
#layout { flex: 1 1 auto; display: flex; min-height: 0; }

#sidebar {
  --sidebar-w: 300px;
  flex: 0 0 var(--sidebar-w); display: flex; flex-direction: column;
  background: var(--panel); border-right: 1px solid var(--border);
  min-width: 0; overflow: hidden;
  transition: margin-left var(--t-move) ease;
}
/* 收起：負 margin 把整欄滑出左側，右側內容因此變寬（視窗沒變，Leaflet 不會自己察覺
   → 由 app.js 的 watchMapResize 用 ResizeObserver 補 invalidateSize），與分頁切換的 .hidden 互不干擾 */
#sidebar.collapsed { margin-left: calc(-1 * var(--sidebar-w)); border-right-color: transparent; }
.sh-right { display: flex; align-items: center; gap: 8px; }
.icon-btn {
  background: transparent; border: 1px solid var(--border); border-radius: var(--r-sm);
  color: var(--muted); width: 26px; height: 26px; padding: 0; cursor: pointer;
  /* 這裡的 `1` 不是行距而是「別加行距」： glyph 要正好坐在 26×26 的盒子裡，
     換成 var(--lh-*) 會多出 leading，符號就往下掉出置中線。 */
  font-size: var(--fs-lg); line-height: 1; flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
}
.icon-btn:hover:not(:disabled) { color: var(--text); border-color: var(--accent); }

/* 側欄收起後浮出的展開鈕（位於主區域左上，避開 Leaflet 縮放控制） */
.fab-sidebar {
  position: absolute; top: 10px; left: 50px; z-index: 900;
  background: var(--panel); border: 1px solid var(--border); color: var(--text);
  border-radius: var(--r-md); padding: 7px 12px; font-size: var(--fs-md); font-weight: 600;
  cursor: pointer; box-shadow: 0 4px 16px rgba(0,0,0,.45);
  display: inline-flex; align-items: center; gap: 6px;
}
.fab-sidebar:hover:not(:disabled) { border-color: var(--accent); }
#main { flex: 1 1 auto; display: flex; min-width: 0; min-height: 0; position: relative; }
.view { flex: 1 1 auto; display: flex; flex-direction: column; min-width: 0; min-height: 0; }
.pane { flex: 1 1 auto; overflow-y: auto; min-height: 0; }
.sidebar-head {
  display: flex; justify-content: space-between; align-items: center;
  padding: 10px var(--gutter-sm); font-weight: 700; font-size: var(--fs-md);
  border-bottom: 1px solid var(--border);
}
.device-list { flex: 1; overflow-y: auto; }

.device-item {
  display: grid; grid-template-columns: 10px 1fr auto; gap: 4px 10px;
  padding: 10px var(--gutter-sm); border-bottom: 1px solid var(--border);
  cursor: pointer; position: relative;
}
.device-item:hover { background: var(--panel-2); }
.device-item.active { background: rgba(59, 130, 246, .15); box-shadow: inset 3px 0 0 var(--accent); }

/* ---------- 「這一輪真的抓到了新資料」的看見感 ---------- */
/* 裝置靜止時 lat/lon 只在小數第六位（約 1 公尺）跳，光看地圖完全分辨不出頁面還在更新，
   所以列表列、圖釘與「資料 N 前」徽章各閃一下。圖釘只動 filter，不要動 transform——
   Leaflet 把定位寫在 .leaflet-marker-icon 的 inline transform 上，動畫會蓋掉它、圖釘會跳位。 */
@keyframes fmRowFlash {
  0% { background: rgba(34, 197, 94, .30); }
  100% { background: transparent; }
}
.device-item.fm-changed { animation: fmRowFlash var(--t-flash) ease-out 1; }

@keyframes fmMarkerFlash {
  0% { filter: drop-shadow(0 0 0 rgba(34, 197, 94, 0)); }
  30% { filter: drop-shadow(0 0 9px rgba(34, 197, 94, .95)); }
  100% { filter: drop-shadow(0 0 0 rgba(34, 197, 94, 0)); }
}
.fm-marker-changed { animation: fmMarkerFlash var(--t-flash) ease-out 1; }

@keyframes fmStatFlash {
  0% { color: var(--fresh); font-weight: 700; }
  100% { color: var(--muted); }
}
.stats .stat-flash { animation: fmStatFlash var(--t-flash) ease-out 1; }

/* 倒數那顆徽章數字每秒在變，固定寬度才不會把頂欄推得左右抖 */
#next-tick { min-width: 104px; text-align: center; font-variant-numeric: tabular-nums; }
#btn-pause.btn-paused { color: var(--stale); border-color: var(--stale); }

.dot { width: 9px; height: 9px; border-radius: var(--r-full); margin-top: 6px; }
.dot.fresh { background: var(--fresh); box-shadow: 0 0 6px var(--fresh); }
.dot.stale { background: var(--stale); }
.dot.old { background: var(--old); }

.di-main { min-width: 0; }
.di-name { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.di-sub { font-size: var(--fs-sm); color: var(--muted); display: flex; gap: 8px; margin-top: 2px; }
.di-side { text-align: right; font-size: var(--fs-sm); color: var(--muted); }
.di-batt { font-weight: 600; color: var(--text); }

.empty { padding: 30px 16px; text-align: center; font-size: var(--fs-md); }

/* 已離開的群組：淡顯提示，但仍可取消勾選 */
.wg-left { opacity: 0.55; }
.wg-left:hover { opacity: 1; }

/* ---------- 地圖 ---------- */
#view-map { position: relative; }
#map { position: absolute; inset: 0; background: #0b1220; }
#view-tracks { position: relative; }
#map-tracks { flex: 0 0 46%; min-height: 240px; background: #0b1220; }
/* 桌面上軌跡表靠單行不換行才擠得下五欄；手機（≤760px）改卡片式排版後必須能換行，
   所以這條 ID 規則留在 ≥761px——它的權重高於 .m-cards 的類別規則，混在一起會贏。 */
@media (min-width: 761px) {
  #trk-table th, #trk-table td { white-space: nowrap; }
}
/* 即時座標列（#trk-live）：選了裝置之後經緯度在這裡自己跳，用等寬字避免數字一變就抖 */
.trk-live { margin: 2px 0 6px; min-height: 1.3em; }
.trk-live .mono { color: var(--fresh); }
.trk-warn { color: var(--stale); }
/* 表格列是「設成追蹤裝置」的入口，不是純資料；淡掉未選中裝置與地圖同一個語言 */
#trk-tbody tr.trk-row { cursor: pointer; }
#trk-tbody tr.trk-row:hover { background: var(--panel-2); }
#trk-tbody tr.trk-dim { opacity: .45; }
#trk-tbody tr.trk-dim:hover { opacity: 1; }
#trk-tbody tr.trk-now { background: rgba(59, 130, 246, .14); }
#trk-tbody tr.trk-now td:first-child { border-left: 2px solid var(--accent); }
/* .trk-here＝地圖上正圈著的那一列（點列定位那一筆）；.trk-now 只在沒有點定對象時才出現，
   兩者由 markTrackRows() 互斥，所以不會看到兩個亮框搶同個位置 */
#trk-tbody tr.trk-here { background: rgba(59, 130, 246, .28); }
#trk-tbody tr.trk-here td:first-child { border-left: 3px solid var(--accent); }
/* 高亮圈本身是 Leaflet 的 circleMarker（白虛線、interactive:false），這裡只補一道保險：
   絕不吃滑鼠，否則圈住的那個圓點就點不到、換不了那一筆 */
.trk-focus { pointer-events: none; }
.trk-swatch {
  display: inline-block; width: 8px; height: 8px; border-radius: var(--r-full);
  margin-right: 6px; vertical-align: middle;
}
/* 軌跡起點（空心）／終點（實心）與方向箭頭：都是 divIcon，外層要清掉 Leaflet 預設的白底框。
   旋轉寫在內層 .trk-arrow，不能寫在外層——外層的 transform 是 Leaflet 用來定位的（同主地圖圖釘）。 */
.trk-ep-wrap, .trk-arrow-wrap { background: none !important; border: none !important; }
.trk-dot { width: 14px; height: 14px; border-radius: var(--r-full); border: 2px solid #fff; box-sizing: border-box; }
.trk-start { background: transparent; }
.trk-end { background: #fff; }
.trk-arrow-wrap { color: var(--accent-text); }
/* `12px` 是這顆 divIcon 的**盒子高度**本身（箭頭靠 transform 旋轉、以 50% 50% 為軸），
   不是行距；改成倍數會跟著字級長高，箭頭中心就離開 polyline。 */
.trk-arrow { font-size: var(--fs-xs); line-height: 12px; text-align: center; transform-origin: 50% 50%; }
.trk-tip { background: var(--panel-2); color: var(--text); border: 1px solid var(--border); }
/* 設定卡片放在軌跡表的捲動容器內（.table-wrap 是 flex:1 1 0 的捲動區），
   這樣卡片按內容自然長高，不會被 flex 壓扁 */
#rtset-card { margin-top: 16px; }
#rt-locked-table th { position: static; }
.leaflet-container { background: #0b1220; font-family: inherit; }
.leaflet-popup-content-wrapper, .leaflet-popup-tip {
  background: var(--panel); color: var(--text);
  box-shadow: 0 4px 18px rgba(0,0,0,.5);
}
.leaflet-popup-content { margin: 10px 14px; font-size: var(--fs-md); line-height: var(--lh-read); }
.leaflet-popup-content b { font-size: var(--fs-base); }
.leaflet-control-attribution { background: rgba(30,41,59,.8) !important; color: var(--muted) !important; }
.leaflet-control-attribution a { color: var(--accent-strong) !important; }
.leaflet-bar a { background: var(--panel) !important; color: var(--text) !important; border-color: var(--border) !important; }
.leaflet-bar a:hover { background: var(--panel-2) !important; }

/* 裝置標記 */
.dev-marker-wrap { background: none !important; border: none !important; }
.dev-marker {
  width: 32px; height: 32px; border-radius: var(--r-full);
  display: flex; align-items: center; justify-content: center;
  font-size: var(--fs-lg); border: 2px solid #fff;
  background: var(--old); color: #fff;
  box-shadow: 0 2px 8px rgba(0,0,0,.5);
}
.dev-marker.fresh { background: var(--fresh); }
.dev-marker.stale { background: var(--stale); }
.dev-marker.old { background: var(--old); }
.dev-marker.selected { border-color: var(--accent); transform: scale(1.15); }

.popup-actions { margin-top: 8px; display: flex; gap: 6px; flex-wrap: wrap; }
.popup-actions .btn-sm { background: var(--accent-strong); border-color: var(--accent-strong); color: #fff; }
.popup-actions .btn-ghost { background: transparent; }

/* ---------- 歷史軌跡面板 ---------- */
#history-panel {
  position: absolute; right: 14px; top: 64px; bottom: 14px; width: 320px;
  background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-lg);
  display: flex; flex-direction: column; z-index: 1000;
  box-shadow: 0 8px 30px rgba(0,0,0,.5);
}
.history-head {
  display: flex; justify-content: space-between; align-items: center;
  padding: 10px var(--gutter-sm); border-bottom: 1px solid var(--border); font-weight: 700;
}
.history-list { flex: 1; overflow-y: auto; padding: 6px 0; }
/* ⛏ 深挖入庫列：住在歷史軌跡面板標題下、列表上方，不佔列表的滾動空間 */
.hist-dive {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 8px var(--gutter-sm); border-bottom: 1px solid var(--border); background: var(--panel-2);
}
.hist-dive label { font-size: var(--fs-sm); color: var(--muted); white-space: nowrap; }
.hist-dive select { background: var(--panel); color: var(--text); border: 1px solid var(--border);
  border-radius: var(--r-sm); padding: 3px 6px; font-size: var(--fs-sm); }
#hist-dive-status { flex: 1 1 100%; line-height: var(--lh-base); }
#hist-dive-status.busy { color: var(--accent-text); }
#hist-dive-status.err { color: var(--danger-text); }
#hist-dive-status.done { color: var(--fresh); }
.history-row {
  display: flex; justify-content: space-between; gap: 10px;
  padding: 7px var(--gutter-sm); font-size: var(--fs-sm); cursor: pointer;
  border-left: 3px solid transparent;
}
.history-row:hover { background: var(--panel-2); border-left-color: var(--accent); }
.history-row .t { color: var(--muted); font-variant-numeric: tabular-nums; white-space: nowrap; }
.history-row .c { font-variant-numeric: tabular-nums; }

/* ---------- 登入浮層 ---------- */
#login-overlay {
  position: fixed; inset: 0; z-index: 2000;
  background: rgba(15, 23, 42, .92); backdrop-filter: blur(3px);
  /* 直列：這裡同時可能有 OTP 卡、共用錯誤列、API Key 摺疊卡與開機碼卡。
     舊的橫排（row）在開機碼卡可見時是 380px × 2 並排＝手機直接溢出，
     多一條登入路就會復發，所以改成欄式並留捲動。
     safe center：內容高於視窗時退回頂部對齊。純 center 會把多出來的上半部
     推到捲動範圍之外（flex 置中溢出的老坑），到時最上面的標題與電話欄根本點不到。
     不支援 safe 的瀏覽器會整條宣告失效 → 變成預設的頂部對齊，仍然可用。 */
  display: flex; flex-direction: column; align-items: center; justify-content: safe center;
  gap: 12px; padding: 20px 0; overflow-y: auto;
}
.login-card {
  width: 380px; max-width: 92vw; background: var(--panel);
  border: 1px solid var(--border); border-radius: var(--r-lg); padding: 26px;
  display: flex; flex-direction: column; gap: 14px;
}
.login-card h2 { font-size: var(--fs-icon); }
.login-card p { font-size: var(--fs-md); line-height: var(--lh-base); }
.login-card label { display: flex; flex-direction: column; gap: 6px; font-size: var(--fs-md); color: var(--muted); }
.login-error { color: var(--danger-text); font-size: var(--fs-md); }
/* OTP 兩步：步驟容器本身也用欄式排版，讓 label／按鈕維持 14px 間距 */
#otp-step-phone, #otp-step-code { display: flex; flex-direction: column; gap: 14px; }
#input-code { letter-spacing: 6px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: var(--fs-icon); text-align: center; }
.login-adv summary { cursor: pointer; }
.login-adv label { margin-top: 8px; }
/* API Key 那張救援卡是「第二條路」：預設摺疊，展開後仍是同一張 .login-card 的節奏。
   summary 是可點的，所以焦點環照第 2 批的規則走 :focus-visible（不寫 :focus，
   滑鼠點擊不該糊一層環）；它不在黏住容器裡，+2px 不會被裁。 */
.login-alt { width: 380px; max-width: 92vw; }
.login-alt > summary { cursor: pointer; text-align: center; }
.login-alt > summary:focus-visible { outline: 2px solid var(--accent-text); outline-offset: 2px; }
.login-alt[open] { display: flex; flex-direction: column; gap: 8px; }
.login-alt > .login-card { width: 100%; }

/* ---------- 身分標示（頂欄） ---------- */
#who-pill {
  max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
#who-pill:empty { display: none; }

/* ---------- 用戶管理（👥）：逐路由權限編輯器 ---------- */
.scope-groups {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(min(320px, 100%), 1fr));
  gap: 12px; margin: 12px 0;
}
.scope-group {
  border: 1px solid var(--border); border-radius: var(--r-lg); padding: 10px 12px;
  background: var(--panel-2);
}
.scope-group h4 { margin: 0 0 8px; font-size: var(--fs-md); color: var(--accent-text); }
.scope-group-manage { border-color: rgba(234, 179, 8, .45); }
.scope-group-manage h4 { color: var(--warn-text); }
.scope-row {
  display: flex; align-items: center; gap: 8px;
  font-size: var(--fs-md); padding: 3px 0; cursor: pointer;
}
.scope-row code {
  margin-left: auto; font-size: var(--fs-xs); color: var(--muted);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.user-ops { white-space: nowrap; }
.user-ops .btn { margin-right: 4px; }
/* 出口釘在卡片底：這張卡有 9 個權限群／31 個勾選框，離線儀裝實測高 1,595px（632px 視窗）
   ／874px（1200×900），而「儲存權限／取消」原本落在視窗下緣以外 1,139px／125px——按不到
   就被讀成「這頁不能關閉」。黏住之後不捲動也一直在畫面上（標題列那顆 ✕ 是另一個出口）。
   分隔線用 inset 上邊不是 border：多一條 border 會把這一列再撐高 1px，而它是要黏著的。 */
.ue-actions {
  position: sticky; bottom: 0;
  background: var(--panel); box-shadow: inset 0 1px 0 var(--border);
  padding: 8px 0;
}

/* ---------- Toast ---------- */
.toast {
  position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%);
  background: var(--panel); border: 1px solid var(--accent);
  padding: 10px 18px; border-radius: var(--r-md); z-index: 3000;
  box-shadow: 0 6px 24px rgba(0,0,0,.5); font-size: var(--fs-md); max-width: 80vw;
  pointer-events: none;  /* 不擋住下方元件的點擊 */
}
.toast.error { border-color: var(--danger); color: var(--danger-text); }

/* ---------- 分頁列 ---------- */
#tabs {
  flex: 0 0 auto; display: flex; gap: 2px;
  background: var(--panel-2); border-bottom: 1px solid var(--border);
  padding: 0 var(--gutter-xs);
}
.tab {
  background: none; border: none; color: var(--muted);
  padding: 9px 16px; font-size: var(--fs-md); font-weight: 600; cursor: pointer;
  border-bottom: 2px solid transparent; position: relative;
}
.tab:hover { color: var(--text); }
.tab.active { color: var(--accent-text); border-bottom-color: var(--accent); }
.badge {
  display: inline-block; min-width: 18px; padding: 1px 6px; margin-left: 5px;
  background: var(--accent-strong); color: #fff; border-radius: var(--r-pill);
  font-size: var(--fs-xs); font-weight: 700; text-align: center;
}

/* ---------- 共用小元件 ---------- */
.small { font-size: var(--fs-sm); }
.mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.err { color: var(--danger-text); font-size: var(--fs-md); line-height: var(--lh-base); }
.btn-danger { border-color: var(--danger); color: var(--danger-text); }
.btn-danger:hover:not(:disabled) { background: rgba(239, 68, 68, .15); }
.btn-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 10px 0; }
/* 「標題在左、控件在右」只這一條基底（2026-11-03）：🔑 匯出（.kx-head）與 🔐 彈窗（.ap-sec-head）
   原本各寫一份**逐字相同**的宣告，👥 權限編輯器的標題列（.ue-head，為了放那顆 ✕）是第三個需要它的人。
   三類共用一條不是改版：離線儀裝比對過三個節點改前／改後的 computed 逐字相同。 */
.ap-sec-head, .kx-head, .ue-head {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
}
.check-row { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin: 8px 0; }
.check-row label { display: flex; align-items: center; gap: 6px; font-size: var(--fs-md); }
.inline-row { display: flex; align-items: center; gap: 8px; margin: 8px 0; font-size: var(--fs-md); }
/* 通知頁逐格自動儲存的行內回饋（.wa-hint 由 app.js 掛進那一列）：
   訊息自己佔一整行，否則它會當成 flex 元件擠進同一行、把 input／select 壓窄。
   換行只對「真的掛了訊息」的那幾列開放（.wa-host），不動其他頁的 .inline-row 版面。 */
.wa-host { flex-wrap: wrap; }
.wa-hint { flex: 1 1 100%; }
/* 欄位群（⚙️ 設定卡／三個編輯器）：與 .scope-groups 同一個 auto-fill 語意，
   320px 是欄位的最小寬，寬卡片自動併欄、窄卡片（含手機）回到單欄 */
.inline-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(min(320px, 100%), 1fr));
  column-gap: 18px;
}
code {
  background: rgba(0, 0, 0, .3); padding: 1px 5px; border-radius: var(--r-xs);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: var(--fs-xs);
}

/* ---------- 地理圍欄：側邊列表 ---------- */
.zone-toolbar {
  display: flex; flex-direction: column; gap: 6px;
  padding: 10px var(--gutter-sm); border-bottom: 1px solid var(--border);
}
.zone-list { flex: 1; overflow-y: auto; }
.zone-item {
  display: grid; grid-template-columns: 12px 1fr auto; gap: 4px 10px;
  padding: 10px var(--gutter-sm); border-bottom: 1px solid var(--border);
  cursor: pointer; align-items: center;
}
.zone-item:hover { background: var(--panel-2); }
.zone-item.active { background: rgba(59, 130, 246, .15); box-shadow: inset 3px 0 0 var(--accent); }
.zone-swatch { width: 12px; height: 12px; border-radius: var(--r-xs); }
.zone-name { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.zone-sub { font-size: var(--fs-xs); color: var(--muted); grid-column: 2; }
.zone-badge { font-size: var(--fs-xs); color: var(--muted); text-align: right; }
.zone-badge b { color: var(--fresh); }
.zone-badge-out { color: var(--danger-text); }
.zone-badge-out b { color: var(--danger-text); }
.zone-tag {
  font-size: var(--fs-xs); color: var(--warn-text); background: rgba(234, 179, 8, .14);
  border-radius: var(--r-xs); padding: 1px 5px; margin-left: 4px; white-space: nowrap;
}

/* ---------- 事件篩選 ---------- */
.filter-box { display: flex; flex-direction: column; gap: 10px; padding: var(--gutter-sm); }
/* 通知側欄：發送對象清單（號碼／群組勾選） */
.ns-list {
  display: flex; flex-direction: column; gap: 6px;
  max-height: 170px; overflow-y: auto; padding: 2px;
}
.ns-item { display: flex; align-items: center; gap: 8px; font-size: var(--fs-md); cursor: pointer; }
.ns-item span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ns-item .ns-sub { color: var(--muted); font-size: var(--fs-xs); }
.filter-box label { display: flex; flex-direction: column; gap: 5px; font-size: var(--fs-md); color: var(--muted); }

/* ---------- 視圖（事件 / 通知） ---------- */
.view-bar {
  display: flex; justify-content: space-between; align-items: center; gap: 10px;
  padding: 12px var(--gutter); border-bottom: 1px solid var(--border); background: var(--panel);
}
.view-bar h2 { font-size: var(--fs-lg); }
.view-actions { display: flex; gap: 8px; }
/* 側欄收起時，浮鈕位於主區左上（top:10 / left:50，寬約 68px），會壓到
   「事件 / 軌跡」分頁的標題。僅在收起狀態下為這兩個分頁的標題列預留左側空間，
   讓標題右移避開浮鈕（通知分頁本就隱藏浮鈕，無須處理；地圖 / 指引無 view-bar）。 */
body.sidebar-collapsed #view-events .view-bar,
body.sidebar-collapsed #view-tracks .view-bar { padding-left: 132px; }
.table-wrap { flex: 1 1 0; min-height: 0; overflow: auto; padding: 0 var(--gutter) var(--gutter); }
table { width: 100%; border-collapse: collapse; font-size: var(--fs-md); }
th, td { text-align: left; padding: 8px 10px; border-bottom: 1px solid var(--border); vertical-align: middle; }
/* sticky × border-collapse: collapse 的老衝突——合併邊框掛在「表格網格」上而不是掛在黏住的
   th 上，所以一往下捲，表頭那條底邊就留在原地跟著內容滾走，表頭與資料之間變成無分界的一坨。
   box-shadow 是畫在 th 自己的盒子內緣、不受該規則影響，視覺與那條 1px 邊框同一顆 token。 */
th { color: var(--muted); font-weight: 600; position: sticky; top: 0; background: var(--panel); white-space: nowrap; box-shadow: inset 0 -1px 0 var(--border); }
/* 可排序的表頭要「看起來能按」：舊寫法只有文字，使用者得先猜這裡有沒有排序（實測四張表
   本來也沒有）。有 data-sort 才給手型指標與箭頭占位，沒有的一律不假裝能排。
   箭頭用 ::after 畫在欄名右邊，未排序時是一顆淡的 ⇅（告訴人有這條路），排了才換成實心
   方向並轉成強調色——顏色服從「這一輪的起點在哪」而不是裝飾。 */
th[data-sort] { cursor: pointer; user-select: none; }
th[data-sort]:hover { color: var(--text); }
th[data-sort]::after { content: '\21C5'; margin-left: 5px; color: var(--muted); font-weight: 400; font-size: var(--fs-xs); }
th[data-sort][data-dir="asc"]::after { content: '\25B2'; color: var(--accent-text); }
th[data-sort][data-dir="desc"]::after { content: '\25BC'; color: var(--accent-text); }
td { font-variant-numeric: tabular-nums; }
tr:hover td { background: var(--panel-2); }
.ev-enter { color: var(--fresh); font-weight: 700; }
.ev-exit { color: var(--stale); font-weight: 700; }
.notify-ok { color: var(--fresh); }
.notify-fail { color: var(--danger-text); }
.notify-none { color: var(--muted); }
/* 卡片本體已負責左右內距，捲動容器再疊一份 18px 會讓表格左緣比同一張卡的 h3 多 18px
   （手機那兩條 .card .table-wrap 規則早已 padding:0，桌面卻留著舊的雙重內縮）。
   統一歸零：桌面的表格與標題同一條左緣。 */
.card > .table-wrap { padding: 0; }

/* ---------- 列表工具列（📜 事件／📨 訊息記錄／📋 審計的搜尋＋分頁） ----------
   三個列表原本一次就把 200 列塞進 DOM，要找「某台裝置的離欄紀錄」只能用眼睛掃。
   搜尋與分頁全在前端跑：資料仍由原本那個 limit 決定，所以零額外請求，而搜尋範圍
   就是「已載入的那批」——狀態列要老實這樣講，不能讓人以為問了整張表。
   直接放在 view 底下時吃主內容框內距＋一條底邊；放進 .card 時左右交給卡片自己的
   padding（再疊一份會與同一張卡的 h3 左緣差一個 gutter）。 */
.list-tools {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 8px var(--gutter); border-bottom: 1px solid var(--border);
}
.card > .list-tools { padding: 8px 0; border-bottom: 0; }
.list-tools input[type="search"] { flex: 1 1 200px; min-width: 0; max-width: 320px; }
/* 手機那張表卡片化後 thead 是 display:none，表頭按不到＝排序在手機上不存在。
   同一份欄位規格（th[data-sort]）由 app.js 再多生一顆 select，只在 ≤760px 露面；
   桌面藏起來而不是不做，兩邊讀寫的是同一個 listState。 */
.list-sort { display: none; }
.list-nav { display: flex; align-items: center; gap: 6px; }
.list-page { white-space: nowrap; }
.list-count { margin-left: auto; }

/* ---------- 通知設定 ---------- */
.notify-grid {
  flex: 1 1 0; min-height: 0; overflow-y: auto; display: grid;
  grid-template-columns: repeat(auto-fit, minmax(380px, 1fr));
  gap: 16px; padding: var(--gutter); align-content: start;
  /* 卡片按自身內容長高（不拉到同行最高），避免短卡片中段空一大片 */
  align-items: start;
}
.card {
  background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-lg);
  padding: 16px var(--gutter); display: flex; flex-direction: column; gap: 8px;
}
/* 卡片節奏：間距只由容器的 gap 決定，直接子排組自己不再疊一份垂直 margin。
   舊寫法是 gap:4px ＋ .btn-row{margin:10px 0}／.check-row{margin:8px 0}／.inline-row{margin:8px 0}
   ＋ h3{margin-bottom:8px} 疊在一起，實測同一張相鄰塊量出 4/12/14/18/20/22/24/26 八種值——
   「這一塊比前一塊重要」完全是靠運氣。歸零後只剩 8px 一種（標題也吃 gap，不再特殊）。
   清單是列舉式的（不用 .card > *）：`pre.curl` 這類「元素+class」選擇器權重 (0,1,1) 會贏過
   `.card > *` (0,1,0)， blanket 重置看起來比較嚴謹、實際蓋不住。新增卡片內排組時把 class
   加進這裡，或乾脆別寫垂直 margin。唯一的例外是 .sep：分隔線是「.section break」的語意，
   留 8px margin 讓它兩側各 16px（= 2 個 8 單位），不屬於雜訊。 */
.card h3 { font-size: var(--fs-lg); margin: 0; }
.card > .btn-row, .card > .check-row, .card > .inline-row, .card > .inline-grid,
.card > .wa-status-row, .card > #wa-qr-box, .card > .ze-fieldset, .card > .vertex-list,
.card > .field-label, .card > .curl,
.sp-body > .btn-row, .sp-body > .check-row, .sp-body > .inline-row, .sp-body > .inline-grid,
.sp-body > .ze-fieldset, .sp-body > .vertex-list, .sp-body > .field-label, .sp-body > .curl {
  margin-top: 0; margin-bottom: 0;
}
.card > .sep, .sp-body > .sep { margin: 8px 0; }
/* 同一條 .btn-row 裡混進一個 .inline-row（🤖 Bot 訊息記錄的「篩選」就是這樣）時，
   label 自己的 8px 上下 margin 會被算進 flex 列的橫軸尺寸，實測把整列撐到 65.6px、
   旁邊兩顆 .btn-sm 被 align-items:stretch 拉到 66px 高（文字只有 13px，等於按鈕胖三倍）。
   列內已由 gap:8px spacing，margin 交給它。 */
.btn-row > .inline-row { margin-top: 0; margin-bottom: 0; }
/* 行內標籤不準被壓到一個字寬：中文可以在任意字之間斷行，flex 的 min-width:auto
   對它等於「一個字」，所以欄位一擠，「地圖縮放」就變成四行（實測 24×78px）。 */
.inline-row > span:first-child { flex: 0 0 auto; }
/* 通知卡片是自動高度：表格要按內容撐高（min-height 保底），
   超過 max-height 才內部滾動——否則 flex-basis:0 會把表格壓成一條縫 */
.notify-grid .card .table-wrap {
  flex: 1 1 auto; min-height: 140px; max-height: 360px;
}
.wa-status-row { display: flex; align-items: center; gap: 10px; margin: 6px 0 10px; }
#wa-qr-box {
  display: flex; flex-direction: column; align-items: center; gap: 8px;
  padding: 14px; background: #fff; border-radius: var(--r-lg); margin-bottom: 10px;
}
#wa-qr-box p { color: #334155; }
#wa-qr-img { width: 240px; height: 240px; image-rendering: pixelated; }
#rc-table input { width: 100%; }
#rc-table td { vertical-align: middle; }

/* ---------- 地理圍欄編輯面板 ---------- */
.side-panel {
  position: absolute; right: 14px; top: 14px; bottom: 14px; width: 340px;
  background: var(--panel); border: 1px solid var(--border); border-radius: var(--r-lg);
  display: flex; flex-direction: column; z-index: 1100; overflow: hidden;
  box-shadow: 0 8px 30px rgba(0, 0, 0, .5);
}
.sp-head {
  display: flex; justify-content: space-between; align-items: center;
  padding: 10px var(--gutter-sm); border-bottom: 1px solid var(--border); font-weight: 700;
  flex: 0 0 auto;
}
/* 注意 min-height:0 —— 沒有它 flex 項目不會縮到內容以下，sp-foot 會被擠出面板 */
/* gap 8px 與 .card 同一套節奏；欄位自己的 margin-bottom 交給 gap，不再疊第二份 */
.sp-body { flex: 1 1 0; min-height: 0; overflow-y: auto; padding: 12px var(--gutter-sm); display: flex; flex-direction: column; gap: 8px; }
.sp-body > label { display: flex; flex-direction: column; gap: 5px; font-size: var(--fs-md); color: var(--muted); }
.sp-foot {
  display: flex; gap: 8px; padding: 10px var(--gutter-sm);
  border-top: 1px solid var(--border); flex: 0 0 auto;
}
.ze-fieldset {
  border: 1px solid var(--border); border-radius: var(--r-md); padding: 8px 10px;
  margin: 4px 0 10px; font-size: var(--fs-md);
}
.ze-fieldset legend { color: var(--muted); padding: 0 5px; }
.check-list { display: flex; flex-direction: column; gap: 5px; max-height: 160px; overflow-y: auto; }
.check-list label { display: flex; align-items: center; gap: 7px; font-size: var(--fs-md); color: var(--text); }
.vertex-list { display: flex; flex-direction: column; gap: 4px; margin: 8px 0; max-height: 180px; overflow-y: auto; }
.vertex-row { display: flex; align-items: center; gap: 6px; font-size: var(--fs-sm); }
.vertex-row .v-num { color: var(--muted); min-width: 34px; }
.vertex-row .v-coord { flex: 1; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.vertex-row button { flex: 0 0 auto; }

/* 地圖上的區域標籤 */
.zone-label {
  background: var(--panel); border: 1px solid var(--border);
  border-radius: var(--r-sm); padding: 2px 8px; font-size: var(--fs-xs); font-weight: 600;
  color: var(--text); white-space: nowrap; box-shadow: 0 2px 8px rgba(0, 0, 0, .4);
}
.zone-label small { color: var(--muted); font-weight: 400; margin-left: 4px; }

/* ---------- 窄屏適配 ---------- */
@media (max-width: 760px) {
  /* 外框尺在手機收到 12px：一張卡片兩側共省 12px 閱讀寬度（348px 螢幕上約 3.5%），
     而且是在這一處宣告，所以 .card／.view-bar／.sidebar-head／.zone-item 這一批一起跟著收，
     不會出現「卡片縮了但標題沒縮」。 */
  :root { --gutter: 12px; --gutter-sm: 12px; }
  #sidebar { --sidebar-w: 220px; }
  .stats { display: none; }
  #history-panel { width: calc(100vw - 28px); }
  .side-panel { width: calc(100vw - 28px); }
  #tabs { overflow-x: auto; justify-content: space-between; gap: 0; }
  /* 換行已在基線 #topbar 規則裡（內容驅動，不限手機）；這裡只壓 padding 與欄距 */
  #topbar { column-gap: 8px; padding: 6px var(--gutter-sm); }
  .topbar-spacer { display: none; }
  /* 分頁標籤保持不換行、可橫向滑動，避免文字被壓成兩行 */
  .tab { white-space: nowrap; padding: 9px 11px; }
  /* 「一顆主按鈕＋一顆次要按鈕」在手機並排各半、主操作靠右（2026-11-03 收成共用 class）。
     原本是 #botcmd-editor 那三條 ID 規則（只有 🤖 指令編輯器有），👥 權限編輯器的出口列是第二個需要它的人。
     只寫「並排」與「寬度」兩條：display/gap 已在 base 的 .btn-row 給過，這裡再宣告一次就是第二套餐具。
     桌面不受影響（這一組 query 之外 `.btn-primary{width:100%}` 仍是主按鈕整條）。 */
  .btn-row-split .btn { flex: 1 1 0; width: auto; padding: 9px 10px; }
  .btn-row-split .btn-primary { order: 2; }
}

/* ---------- 手機（≤760px）：表格 → 卡片列表（.m-cards） ----------
   一列一卡、標籤在值上方：長內容靠換行讀完，不依賴橫向捲動。
   規則掛在 class（不是 table id），所以新表格只要 ① 掛 class="m-cards"
   ② 每個 <td> 補 data-label（要當卡片標題的那欄再加 td-title）就共用同一套餐具。
   刻意不卡片化：#rt-locked-table（3 欄短文字，橫向放得下）、.guide-table（❓ 指引是文件頁）。 */
@media (max-width: 760px) {
  .m-cards thead { display: none; }
  .list-sort { display: block; flex: 0 1 auto; min-width: 0; max-width: 220px; font-size: var(--fs-sm); }
  /* 多一顆排序控件不該再多吃一列：手機讓搜尋與排序共用第一列（實測並排前 4 列 134px、
     並排後 3 列 98px，與第 4 批加入搜尋列時的高度一致）。兩邊_basis_ 加總 286px < 348px 可用寬，
     所以這一列不需要真的壓縮任何人。 */
  .list-tools input[type="search"] { flex: 1 1 120px; max-width: none; }
  .m-cards, .m-cards tbody, .m-cards tr, .m-cards td { display: block; width: auto; }
  .m-cards tbody tr {
    border: 1px solid var(--border); border-radius: var(--r-lg);
    padding: 9px 11px; margin-bottom: 8px; background: var(--panel-2);
  }
  .m-cards td {
    border: none; padding: 2px 0;
    white-space: normal; word-break: break-word; overflow-wrap: anywhere;
  }
  .m-cards td::before {
    content: attr(data-label);
    display: block; color: var(--muted);
    font-size: var(--fs-xs); font-weight: 600; letter-spacing: .3px; margin-bottom: 1px;
  }
  /* 沒有標籤的欄位（操作鈕那一欄、分類列）不要留一條空標籤 */
  .m-cards td:not([data-label])::before { display: none; }
  /* 卡片標題欄：不顯示標籤、字級放大，一眼認出這張卡是誰 */
  .m-cards td.td-title { font-size: var(--fs-base); padding-bottom: 3px; }
  .m-cards td.td-title::before { display: none; }
  /* 短欄位：標籤與值同行，而且願意和隔壁欄位共用同一行（max-width 保住不超出卡片） */
  .m-cards td.td-inline { display: inline-block; width: auto; max-width: 100%; margin-right: 14px; }
  .m-cards td.td-inline::before { display: inline; margin: 0 6px 0 0; }
  /* 真的沒內容的欄位不佔位（空的備註、非管理者看不到按鈕的操作欄） */
  .m-cards td:empty { display: none; }
  /* 操作欄：按鈕靠左排列、加大點擊區 */
  .m-cards td.td-ops { padding-top: 8px; }
  .m-cards td.td-ops .btn { margin-right: 6px; padding: 5px 12px; font-size: var(--fs-md); }
  /* 分類標題列（🤖 指令表按分類分段）不是資料，不套卡片外觀 */
  .m-cards tbody tr.cat-row {
    background: transparent; border: none; padding: 10px 2px 3px; margin: 0;
  }
  .m-cards tbody tr.cat-row:hover { background: transparent; }
  .m-cards tr.cat-row td { padding: 0; }
  /* 載入中／錯誤／「尚無…」這些跨欄提示列不套卡片外觀 */
  .m-cards tbody tr:has(td.empty) { border: none; background: transparent; padding: 0; margin-bottom: 0; }
  /* 💬 通知分頁：手機改單欄，卡片依內容長高，表格不再各自捲動
     （minmax(380px,1fr) 在 348px 螢幕會撐破容器，所以要顯式改 1fr） */
  .notify-grid { grid-template-columns: 1fr; padding: 12px; gap: 12px; }
  .notify-grid .card { padding: 13px var(--gutter); }
  .notify-grid .card .table-wrap {
    flex: 0 0 auto; min-height: 0; max-height: none; overflow: visible; padding: 0;
  }
}

/* 軌跡圖像排程 */
#ti-editor { margin-top: 12px; }
#ti-editor h4 { margin: 0 0 10px; }
.ti-ops { white-space: nowrap; }
.ti-ops .btn { margin-right: 4px; }
#ti-weekdays .check-row-inline { display: inline-flex; flex-wrap: wrap; gap: 8px; }
#ti-weekdays label { display: inline-flex; align-items: center; gap: 3px; }
#ti-table td, #ti-table th { vertical-align: middle; }

/* ---------- 指引頁（❓） ---------- */
.guide-scroll {
  flex: 1 1 0; min-height: 0; overflow-y: auto;
  display: flex; flex-direction: column; gap: 16px; padding: var(--gutter);
}
.guide-group { margin: 14px 0 18px; }
.guide-group:last-child { margin-bottom: 0; }
.guide-group > h4 {
  font-size: var(--fs-base); margin-bottom: 8px; color: var(--text);
  display: flex; align-items: center; gap: 6px;
}
.guide-group > h4 .count { color: var(--muted); font-weight: 400; font-size: var(--fs-sm); }
/* 指引頁的表格吃全域 table/th/td 那套餐具（12.5px、8px 10px、sticky 表頭、tr:hover），
   這裡只補兩件全域沒有的：多行說明要頂對齊，以及欄寬（原本是寫在 th 上的內聯 style）。 */
.guide-table td { vertical-align: top; }
.guide-table th.col-method { width: 76px; }
.guide-table th.col-ops { width: 64px; }
.guide-table th.col-param { width: 110px; }
.guide-desc { margin: 2px 0 8px; font-size: var(--fs-md); color: var(--muted); white-space: pre-wrap; line-height: var(--lh-read); }
pre.curl {
  margin: 4px 0 8px; padding: 10px 12px; border-radius: var(--r-md);
  background: rgba(0, 0, 0, .35); color: var(--text); font-size: var(--fs-sm);
  overflow-x: auto; white-space: pre; line-height: var(--lh-base);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.guide-cmd {
  border: 1px solid var(--border); border-radius: var(--r-lg);
  padding: 12px 14px; margin-bottom: 12px; background: var(--panel-2);
}
.guide-cmd:last-child { margin-bottom: 0; }
.guide-cmd-head {
  display: flex; justify-content: space-between; align-items: baseline;
  gap: 8px; flex-wrap: wrap; margin-bottom: 4px;
}
.guide-cmd-title { font-weight: 700; font-size: var(--fs-base); }
.guide-cmd p { font-size: var(--fs-md); line-height: var(--lh-read); }
.guide-chips { display: flex; flex-wrap: wrap; gap: 6px; margin: 8px 0 4px; }
/* 外形吃上面那條共用基底，這裡只加「可以點」那部分 */
.chip {
  background: var(--panel); color: var(--text); cursor: pointer;
  transition: border-color var(--t-fast), color var(--t-fast);
}
.chip:hover { border-color: var(--accent); color: var(--accent-text); }
.m-badge {
  display: inline-block; padding: 1px 8px; border-radius: var(--r-sm);
  font-size: var(--fs-xs); font-weight: 700; color: #fff; white-space: nowrap;
}
.m-get { background: #2563eb; }
.m-post { background: #c2410c; }
.m-put { background: #7c3aed; }
.m-delete { background: #dc2626; }

/* 通知設定：地理圍欄文字內容分隔與小標 */
.sep { border: none; border-top: 1px solid var(--border); margin: 14px 0; }
.field-label { font-size: var(--fs-md); font-weight: 600; color: var(--text); margin: 4px 0 2px; }

/* ---------- Bot 頁（🤖）：指令管理 + 訊息記錄 ---------- */
.bot-grid {
  flex: 1 1 0; min-height: 0; overflow-y: auto;
  /* 右側訊息表欄位較多，給它多一點寬度；min() 保證窄屏時不會超出可用寬度 */
  display: grid;
  grid-template-columns: minmax(min(340px, 100%), .85fr) minmax(0, 1.15fr);
  gap: 16px; padding: var(--gutter); align-content: start; align-items: start;
}
/* 平板／窄螢幕：單列堆疊，避免兩張卡都太窄 */
@media (max-width: 980px) {
  .bot-grid { grid-template-columns: 1fr; }
}
/* 卡片是自動高度：表格要按內容撐高（min-height 保底），超過 max-height 才內部滾動。
   不覆寫會沿用全域 .table-wrap 的 flex:1 1 0 + min-height:0，
   在 grid 卡片裡會被壓成一條縫（只剩表頭殘影）。 */
.bot-grid .card .table-wrap {
  flex: 1 1 auto; min-height: 160px; max-height: 460px;
}
/* 編輯器欄位在窄卡片內要能伸縮，否則 input 被壓成一小截 */
.bot-grid .card .inline-row input { flex: 1 1 auto; min-width: 0; width: 100%; }
.bot-cat {
  background: var(--panel-2); color: var(--muted);
  font-size: var(--fs-xs); font-weight: 700; letter-spacing: .5px;
  padding: 5px 10px !important;
}
#botmsg-table td, #botcmd-table td { vertical-align: top; }
/* 桌面上欄位不要被壓成豎排：時間／對象／操作維持單行，關鍵字與內容可換行。
   只在 ≥761px 生效，手機（卡片式）不受這些固定寬度影響。 */
@media (min-width: 761px) {
  #botcmd-table th, #botmsg-table th { white-space: nowrap; }
  #botcmd-table td:nth-child(3) { max-width: 250px; }        /* 關鍵字：可換行，但不吃光整列 */
  #botcmd-table td.bc-ops { white-space: nowrap; }           /* 編輯／刪除按鈕不折行 */
  #botcmd-table td.bc-ops .btn { white-space: nowrap; }
  /* 訊息表用固定版面：欄寬由 CSS 指定，內容欄吃掉剩餘並換行，避免整表橫向滾動 */
  #botmsg-table { table-layout: fixed; }
  #botmsg-table td:nth-child(1), #botmsg-table th:nth-child(1) { width: 146px; white-space: nowrap; }
  #botmsg-table td:nth-child(2), #botmsg-table th:nth-child(2) { width: 74px; white-space: nowrap; }
  #botmsg-table td:nth-child(3), #botmsg-table th:nth-child(3) { width: 118px; }
  #botmsg-table td:nth-child(5), #botmsg-table th:nth-child(5) { width: 120px; }
  #botmsg-table td { word-break: break-word; overflow-wrap: anywhere; }
}

/* ---------- Bot 頁：手機（≤760px）------------
   表格轉卡片的部分已在上方共用區塊（.m-cards），這裡只留 Bot 兩張表自己的事：
   整頁縱向滾動（避免頁面＋卡片雙層滾動互相卡住）與指令編輯器的版面。 */
@media (max-width: 760px) {
  #view-bot { overflow-y: auto; -webkit-overflow-scrolling: touch; }
  #view-bot .view-bar { position: sticky; top: 0; z-index: 5; }
  .bot-grid {
    flex: 0 0 auto; overflow: visible;
    grid-template-columns: 1fr; padding: 12px; gap: 12px;
  }
  .bot-grid .card { padding: 13px var(--gutter); }
  .bot-grid .card .table-wrap {
    flex: 0 0 auto; min-height: 0; max-height: none; overflow: visible; padding: 0;
  }
  /* 節奏交給 .card 的 gap（先前這裡另寫一份 margin，實測會蓋掉全域歸零、
     手機與桌面的卡片節奏就分成兩套）。這裡只改欄位直排——標籤放到輸入框上方，
     欄位才不會被壓縮——與標籤字級。 */
  .bot-grid .card .inline-row { flex-direction: column; align-items: stretch; gap: 4px; }
  .bot-grid .card .inline-row > span { font-size: var(--fs-sm); color: var(--muted); }
  /* 編輯器按鈕列的手機行為（儲存／取消並排、主操作靠右）已在上方共用層 `.btn-row-split`。 */
}

/* ---------- 說明摺疊：(i) 圖示，點擊展開/收合，讓登入介面保持精簡 ---------- */
.info-btn {
  flex: none; width: 18px; height: 18px; padding: 0;
  border-radius: var(--r-full); border: 1px solid var(--border);
  background: transparent; color: var(--muted);
  font-size: var(--fs-xs); font-weight: 700; font-style: italic;
  font-family: Georgia, "Times New Roman", serif;
  /* 同 .icon-btn：`1` 是讓那個「i」坐在圓點盒子正中，不是行距刻度 */
  line-height: 1; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
}
.info-btn:hover { color: var(--accent-text); border-color: var(--accent); }
.info-btn[aria-expanded="true"] {
  color: var(--accent-text); border-color: var(--accent);
  background: rgba(59, 130, 246, .10);
}
.ap-info-inline { display: inline-flex; align-items: center; gap: 6px; }

/* 受信裝置讀碼引導：驗證碼只能由人從裝置螢幕讀出，步驟要顯眼、不能擠成一團 */
.ap-steps {
  margin: 6px 0 0; padding-left: 18px;
  font-size: var(--fs-sm); color: var(--muted); line-height: var(--lh-read);
}
.ap-steps li { margin-bottom: 3px; }
.ap-steps b { color: var(--text); }

/* ---------- 裝置管理：啟用／停用開關 ---------- */
.tag-on  { color: var(--fresh); font-weight: 600; }
.tag-off { color: var(--danger-text); font-weight: 600; }
.switch { position: relative; display: inline-block; width: 42px; height: 24px; }
.switch input { opacity: 0; width: 0; height: 0; }
.switch .slider {
  position: absolute; cursor: pointer; inset: 0;
  background: #cbd5e1; border-radius: var(--r-pill); transition: var(--t-move);
}
.switch .slider::before {
  content: ""; position: absolute; height: 18px; width: 18px; left: 3px; bottom: 3px;
  background: #fff; border-radius: var(--r-full); transition: var(--t-move);
}
.switch input:checked + .slider { background: var(--fresh); }
.switch input:checked + .slider::before { transform: translateX(18px); }

/* ---------- 無障礙補丁（2026-11-03）---------- */
/* 開關的 <input> 被壓成 0×0（只剩可聚焦），環畫在它身上等於看不見，也會在開關左緣
   留下一個多餘的小方塊 → 收掉再掛到 .slider（那個才是使用者看到的那顆開關）。 */
.switch input:focus-visible { outline: none; }
.switch input:focus-visible + .slider { outline: 2px solid var(--accent-text); outline-offset: 2px; }

/* 尊重系統的「減少動態效果」。注意這裡關的是「動畫」不是「訊號」：
   fm-changed／stat-flash 是「這一輪真的抓到了新位置」唯一的視覺證據，
   所以 reduce 時改成靜態的高對比態，而不是讓它整條消失（那等於把功能關掉）。 */
@media (prefers-reduced-motion: reduce) {
  .device-item.fm-changed { animation: none; background: rgba(34, 197, 94, .22); }
  .stats .stat-flash { animation: none; color: var(--fresh); }
  .fm-marker-changed { animation: none; filter: drop-shadow(0 0 6px rgba(34, 197, 94, .95)); }
  .kx-wait-urgent { animation: none; }
  #sidebar { transition: none; }
  .switch .slider, .switch .slider::before { transition: none; }
}
