:root {
  --font-body: 'DM Sans', 'Noto Sans TC', 'Segoe UI', system-ui, sans-serif;
  --text-xs:   clamp(0.75rem,  0.7rem  + 0.25vw, 0.875rem);
  --text-sm:   clamp(0.875rem, 0.8rem  + 0.35vw, 1rem);
  --text-base: clamp(1rem,     0.95rem + 0.25vw, 1.125rem);
  --text-lg:   clamp(1.125rem, 1rem    + 0.75vw, 1.5rem);
  --space-1: 0.25rem; --space-2: 0.5rem; --space-3: 0.75rem;
  --space-4: 1rem;    --space-6: 1.5rem; --space-8: 2rem;
  --radius-sm: 0.375rem; --radius-md: 0.5rem;
  --radius-lg: 0.75rem;  --radius-full: 9999px;
  --transition: 160ms cubic-bezier(0.16, 1, 0.3, 1);
  /* header 實際高度；main.js 用 ResizeObserver 即時覆寫（header 折兩行時 #tab-bar
     sticky top 仍需正確避開遮擋），此為 JS 尚未量測前的 fallback */
  --header-h: 60px;

  /* 深色階（帶冷綠底蘊） */
  --bg:        #0b0e0d;
  --surface:   #101412;
  --surface-2: #161b19;
  --surface-3: #1e2421;
  --border:  rgba(255,255,255,0.08);
  --divider: rgba(255,255,255,0.05);
  --text:       #e6eae8;
  --text-muted: #8b948f;
  --text-faint: #4c554f;

  /* 語意色：base / dim（背景）/ border 三件套 */
  --accent:        #2fc98a;
  --accent-dim:    rgba(47,201,138,0.14);
  --accent-border: rgba(47,201,138,0.3);
  --accent-glow:   rgba(47,201,138,0.22);
  --accent-hover:  #27b57a;
  /* vod／thermal 兩個語意色以前色相只差 12°（35° vs 24°），旁邊還有 error
     的 0°，三色擠在一小段，容易分不出是哪個狀態。拉開到 vod 42°／thermal 14°，
     error 不動（危險紅是最不該挪的錨點）。 */
  --vod:        #e6b23c;
  --vod-dim:    rgba(230,178,60,0.14);
  --vod-border: rgba(230,178,60,0.3);
  --error:        #e05252;
  --error-dim:    rgba(224,82,82,0.14);
  --error-border: rgba(224,82,82,0.3);
  --thermal:        #ff7043;
  --thermal-dim:    rgba(255,112,67,0.14);
  --thermal-border: rgba(255,112,67,0.3);
  --info:        #4da3ff;
  --info-dim:    rgba(77,163,255,0.14);
  --info-border: rgba(77,163,255,0.3);

  --shadow-md: 0 4px 16px rgba(0,0,0,0.4);
  --shadow-lg: 0 12px 40px rgba(0,0,0,0.6);
}
.icon { width: 18px; height: 18px; stroke: currentColor; stroke-width: 1.6;
        fill: none; stroke-linecap: round; stroke-linejoin: round; flex-shrink: 0; }
.icon-sm { width: 14px; height: 14px; }

    /* ── Reset ──────────────────────────────────────────────── */
    *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
    html { -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility; }
    body {
      font-family: var(--font-body);
      font-size: var(--text-sm);
      color: var(--text);
      background: var(--bg);
      min-height: 100dvh;
      display: block;
      /* 底部的固定面板（說明條、時間軸多選操作列）會蓋住頁面最後一段內容。
         --bottom-inset 由 state.js 依目前可見的面板高度即時寫入。
         ⚠ 一定要寫在 padding 簡寫裡，不能另外寫一行 padding-bottom：
         同一個規則裡後面的 `padding: 0` 簡寫會把它重設回 0，
         而且 computed 值看起來就是 0px，完全看不出是被誰蓋掉的。 */
      padding: 0 0 var(--bottom-inset, 0px);
    }
    /* hidden 屬性必須真的能藏住東西。瀏覽器預設的 [hidden] { display: none } 是
       UA 樣式，任何一條作者寫的 display 規則都蓋得過它，而且蓋過去之後
       `el.hidden = true` 看起來有設、元素卻還在畫面上，非常難查。
       這個專案已經被咬過兩次：多畫面模式下單畫面的時間軸沒藏起來（畫面上下各
       一條 24 小時列，上面那條點了不會有反應），以及通知分頁的「載入更多」
       在沒有更多可載時仍然顯示。改成作者樣式 + !important，一次擋掉整類問題。
       ⚠ 需要「設了 hidden 卻仍要顯示」的元素不存在，也不該存在；真有這種需求
       請改用 class 控制，不要放寬這條規則。 */
    [hidden] { display: none !important; }
    button { cursor: pointer; border: none; background: none; font: inherit; color: inherit; }
    select { font: inherit; }
    :focus-visible {
      outline: 2px solid var(--accent);
      outline-offset: 2px;
      border-radius: var(--radius-sm);
    }

    /* ── Header ─────────────────────────────────────────────── */
    header {
      position: sticky;
      top: 0;
      z-index: 50;
      width: 100%;
      max-width: none;
      display: flex;
      align-items: center;
      justify-content: space-between;
      gap: var(--space-3);
      flex-wrap: wrap;
      padding: var(--space-3) var(--space-4);
      background: var(--surface);
      border-bottom: 1px solid var(--border);
    }
    .logo {
      display: flex;
      align-items: center;
      gap: var(--space-2);
      flex-shrink: 0;
    }
    .logo-mark {
      width: 28px; height: 28px;
      color: var(--accent);
    }
    .logo-text {
      font-size: var(--text-base);
      font-weight: 600;
      letter-spacing: -0.01em;
      color: var(--text);
    }
    .header-controls {
      display: flex;
      align-items: center;
      justify-content: center;
      gap: var(--space-3);
      flex-wrap: wrap;
      flex: 1 1 auto;
    }
    .header-status {
      display: flex;
      align-items: center;
      gap: var(--space-2);
      flex-shrink: 0;
    }
    .icon-btn {
      position: relative;
      width: 36px; height: 36px;
      flex-shrink: 0;
      display: inline-flex;
      align-items: center;
      justify-content: center;
      border-radius: var(--radius-full);
      color: var(--text-muted);
      transition: background var(--transition), color var(--transition);
    }
    .icon-btn:hover { background: var(--surface-3); color: var(--text); }

    /* ── Layout shell（雙欄／單欄）───────────────────────────── */
    .layout {
      display: grid;
      grid-template-columns: 1fr;
      gap: var(--space-4);
      padding: var(--space-4);
      max-width: 1720px;
      margin: 0 auto;
    }
    .video-card, #timeline-section, #bottom-panel { max-width: none; }
    @media (min-width: 1200px) {
      .layout { grid-template-columns: minmax(0, 1fr) 400px; align-items: start; }
      /* 3b: max-height → height 固定，三分頁共用同一外框高度、內容各自捲動，
         內容少的分頁（通知/書籤）不再縮得比豬隻狀態短。top 改跟 --header-h
         （header 在 1200~1300px 可能折行，寫死 72px 會滑進 header 底下）。 */
      /* 右欄是 sticky、自己算高度，所以底部面板的留白要另外扣掉，
         否則被蓋住的是右欄清單的最後幾列。 */
      .right-col { position: sticky; top: calc(var(--header-h, 60px) + 12px);
                   height: calc(100dvh - var(--header-h, 60px) - 28px
                                - var(--bottom-inset, 0px)); display: flex; }
      .right-col #bottom-panel { min-height: 0; }
      .right-col #bottom-panel { display: flex; flex-direction: column; width: 100%; }
      .right-col .tab-content.active { overflow-y: auto; flex: 1; }
      /* 3c：>=1200px #tab-bar 不是 sticky 子元素（整個 .right-col 才是 sticky
         對象，見上方），overflow: hidden 在此不會框住任何 sticky 定位，安全地
         照舊裁切 #bottom-panel 圓角（避免最後一列 .selected/.anomaly-row 底色
         方角戳出圓角外）。 */
      #bottom-panel { overflow: hidden; }
    }
    @media (max-width: 1199px) {
      /* top 用 --header-h（main.js ResizeObserver 即時量測），而非寫死 60px：
         窄螢幕 header-controls 會 flex-wrap 折兩行，固定值會讓 tab-bar 被多出的
         一行 header 蓋住。 */
      #tab-bar { position: sticky; top: var(--header-h, 60px); z-index: 40; }
      .timeline-slot { height: 44px; }
      .cal-day { padding: 13px 0; }   /* 月曆日期格觸控目標 ≥44px */
    }
    #bell-badge {
      position: absolute;
      top: 2px; right: 2px;
      background: var(--error);
      color: #fff; /* 對比 --error 紅底的白字，非語意 token */
      font-size: 0.62rem;
      font-weight: 700;
      line-height: 1.3;
      padding: 1px 4px;
      border-radius: var(--radius-full);
      min-width: 15px;
      text-align: center;
    }

    /* ── Controls ───────────────────────────────────────────── */
    .cam-select {
      background: var(--surface-3);
      color: var(--text);
      border: 1px solid var(--border);
      border-radius: var(--radius-md);
      padding: var(--space-2) var(--space-3);
      font-size: var(--text-sm);
      cursor: pointer;
      transition: border-color var(--transition);
      min-width: 120px;
    }
    .cam-select:hover { border-color: rgba(255,255,255,0.18); }

    /* ── Toggle button group ────────────────────────────────── */
    .type-toggle {
      display: flex;
      background: var(--surface-2);
      border: 1px solid var(--border);
      border-radius: var(--radius-md);
      padding: 3px;
      gap: 2px;
    }
    .type-btn {
      padding: var(--space-1) var(--space-4);
      border-radius: calc(var(--radius-md) - 2px);
      font-size: var(--text-xs);
      font-weight: 500;
      letter-spacing: 0.04em;
      color: var(--text-muted);
      transition: background var(--transition), color var(--transition);
      white-space: nowrap;
    }
    .type-btn:hover:not(.active) { color: var(--text); }
    .type-btn.active[data-type="rgb"] {
      background: var(--accent-dim);
      color: var(--accent);
    }
    .type-btn.active[data-type="thermal"] {
      background: var(--thermal-dim);
      color: var(--thermal);
    }
    /* 校正對位鈕也是 .type-btn，但沒有 data-type，套不到上面兩條規則。
       沒有這一條時「進入校正」在按鈕上完全看不出來，使用者會再按一次。 */
    .type-btn.active:not([data-type]) {
      background: var(--info-dim);
      color: var(--info);
    }

    /* ── Video card ─────────────────────────────────────────── */
    .video-card {
      width: 100%;
      background: var(--surface);
      border: 1px solid var(--border);
      border-radius: var(--radius-lg);
      overflow: hidden;
      box-shadow: var(--shadow-lg);
    }

    #vod-banner { display: flex; align-items: center; gap: var(--space-2);
      padding: var(--space-2) var(--space-4); background: var(--vod-dim);
      color: var(--vod); border-bottom: 1px solid var(--vod-border);
      font-size: var(--text-xs); font-weight: 500; }
    #vod-banner[hidden] { display: none; }
    #vod-banner button { margin-left: auto; padding: 2px 10px;
      border: 1px solid var(--vod-border); border-radius: var(--radius-full);
      color: var(--vod); background: none; cursor: pointer; }
    #vod-banner button:hover { background: var(--vod); color: #140d02; } /* 刻意保留：hover 深色文字對比 --vod 底色 */

    #video-wrap {
      position: relative;
      width: 100%;
      /* 比例跟著串流走：player.js 的 syncVideoAspect() 在 loadedmetadata 時把
         videoWidth/videoHeight 寫進 :root 的 --main-video-ar。寫死 4/3 而相機送
         16:9 的時候，contain 會在上下各留一條黑邊，等於畫框有 25% 的高度是黑的。
         metadata 還沒到之前用 16/9 墊著，載入瞬間畫框才不會跳。 */
      aspect-ratio: var(--main-video-ar, 16 / 9);
      background: #000; /* 刻意保留：影片 letterbox 黑底，非語意 token */
      overflow: hidden;
    }
    @media (min-width: 1200px) {
      /* 3a: 影片高度上限依視窗高度計，保證影片＋transport＋footer＋整條時間軸
         同屏可見（offset ≈ header 60 + transport 44 + footer 44 + 時間軸區 120
         + layout padding/gap 48 ≈ 316，取 320）。
         ⚠ 兩個坑：
         (1) 只寫 max-height 的話寬度還是 100%，畫框被拉成比影像更扁，
             contain 就在左右補黑邊。要把高度上限換算成寬度上限（高 × 寬高比），
             寬高一起縮，畫框才永遠剛好等於影像。
         (2) 這個上限要下在整張 .video-card 上，不能只下在畫框上。只縮畫框的話
             卡片仍然滿版，影片兩側留下一大塊空卡片、transport 與頁尾也比影片寬，
             那跟黑邊一樣是浪費版面。卡片一起縮，整張卡就貼合影片。 */
      .video-card {
        width: min(100%, calc((100dvh - 320px) * var(--main-video-ar-num, 1.7778)));
        margin-inline: auto;
      }
      #video-wrap { min-height: 240px; }
    }
    video {
      width: 100%; height: 100%;
      display: block;
      object-fit: contain;
      background: #000; /* 同上 */
    }
    #overlay {
      position: absolute;
      inset: 0;
      pointer-events: none;
    }
    /* bbox/串流同步診斷 HUD（按 d 開關）；DOM 文字可框選複製 */
    #bbox-hud {
      position: absolute;
      top: 6px;
      left: 6px;
      margin: 0;
      padding: 8px 10px;
      font: 12px/1.34 ui-monospace, "DM Mono", Menlo, Consolas, monospace;
      color: #39e6a6; /* 刻意保留：除錯 HUD 專用色，故意與 --accent 區隔以利辨識 */
      background: rgba(0, 0, 0, 0.72);
      border-radius: 4px;
      white-space: pre;
      user-select: text;
      -webkit-user-select: text;
      pointer-events: auto;
      z-index: 5;
      max-width: calc(100% - 12px);
      overflow: auto;
      display: none;
    }
    #bbox-hud.visible { display: block; }

    /* 初始化時的 skeleton shimmer */
    .video-skeleton {
      position: absolute;
      inset: 0;
      display: flex;
      align-items: center;
      justify-content: center;
      background: var(--surface-2);
      opacity: 0;
      pointer-events: none;
      transition: opacity 200ms;
      z-index: 2;
    }
    .video-skeleton.visible { opacity: 1; }
    .skeleton-pulse {
      width: 40px; height: 40px;
      border-radius: var(--radius-full);
      border: 2px solid var(--text-faint);
      border-top-color: var(--text-muted);
      animation: spin 0.9s linear infinite;
    }
    @keyframes spin { to { transform: rotate(360deg); } }

    /* 無訊號佔位：預期狀態（無來源/該時段無錄影），無重試鈕——與「連線錯誤」區分 */
    .no-signal { position: absolute; inset: 0; display: flex; flex-direction: column;
      align-items: center; justify-content: center; gap: var(--space-2);
      background: var(--surface-2); color: var(--text-faint);
      font-size: var(--text-sm); letter-spacing: 0.06em; z-index: 6; }
    .no-signal[hidden] { display: none; }
    .no-signal .icon { width: 40px; height: 40px; }

    /* ── Video footer bar ───────────────────────────────────── */
    .video-footer {
      display: flex;
      align-items: center;
      justify-content: space-between;
      padding: var(--space-3) var(--space-4);
      border-top: 1px solid var(--divider);
      background: var(--surface-2);
      gap: var(--space-3);
      flex-wrap: wrap;
    }

    /* ── 放大影片 ───────────────────────────────────────────
       影片的大小平常被兩件事卡住：寬螢幕是右欄那 400px，矮螢幕是為了讓時間軸
       同屏而設的高度上限。兩者卡的方向不同，所以不各修一半，直接給一個把兩樣
       都收起來的模式：需要看清楚畫面的時候按一下，看完再按一下回來。
       預設版面完全不動。 */
    #video-max-btn { width: 30px; height: 30px; }
    body.video-max .right-col,
    body.video-max #timeline-section { display: none; }
    /* 時間軸都收起來了，指著時間軸的那條操作列留著只會讓人不知道自己在操作什麼。 */
    body.video-max #storage-action-bar.visible { display: none; }
    body.video-max .layout {
      grid-template-columns: minmax(0, 1fr);
      max-width: none;
      padding: var(--space-2);
    }
    /* 高度上限重算：這個模式下 header 60 + transport 44 + footer 44 + padding 16
       ≈ 164，取 180 留一點餘裕。寬度一樣由比例換算回來，兩個方向都不留黑邊。 */
    body.video-max .video-card {
      width: min(100%, calc((100dvh - 180px) * var(--main-video-ar-num, 1.7778)));
      margin-inline: auto;
    }
    #video-max-btn[aria-pressed="true"] { color: var(--accent); }

    /* ── Status pill ────────────────────────────────────────── */
    .status-pill {
      display: inline-flex;
      align-items: center;
      gap: var(--space-2);
      font-size: var(--text-xs);
      font-weight: 500;
      letter-spacing: 0.02em;
      color: var(--text-muted);
      padding: var(--space-1) var(--space-3);
      border-radius: var(--radius-full);
      background: var(--surface-3);
      border: 1px solid var(--border);
      transition: background var(--transition), color var(--transition), border-color var(--transition);
    }
    .status-pill.live {
      color: var(--accent);
      background: var(--accent-dim);
      border-color: var(--accent-border);
    }
    .status-pill.error {
      color: var(--error);
      background: var(--error-dim);
      border-color: rgba(224,82,82,0.25);
    }
    .status-dot {
      display: inline-block;
      width: 6px; height: 6px;
      border-radius: 50%;
      background: currentColor;
      flex-shrink: 0;
    }
    .status-pill.live .status-dot {
      animation: pulse-dot 1.8s ease-in-out infinite;
    }
    @keyframes pulse-dot {
      0%, 100% { opacity: 1; transform: scale(1); }
      50%       { opacity: 0.5; transform: scale(0.7); }
    }

    /* ── Stats chips ─────────────────────────────────────────── */
    .stats-row {
      display: flex;
      align-items: center;
      gap: var(--space-3);
    }
    .stat-chip {
      display: flex;
      align-items: center;
      gap: 5px;
      font-size: var(--text-xs);
      color: var(--text-muted);
      font-variant-numeric: tabular-nums;
    }
    .stat-chip strong {
      color: var(--text);
      font-weight: 600;
    }

    /* ── WS reconnect toast ─────────────────────────────────── */
    .toast {
      position: fixed;
      /* 底部面板開著時往上讓開，否則 z-index 100 的 toast 會壓在說明條正上方。 */
      bottom: calc(var(--space-6) + var(--bottom-inset, 0px));
      left: 50%;
      transform: translateX(-50%) translateY(16px);
      background: var(--surface-3);
      border: 1px solid var(--border);
      border-radius: var(--radius-md);
      padding: var(--space-2) var(--space-4);
      font-size: var(--text-xs);
      color: var(--text-muted);
      box-shadow: var(--shadow-md);
      opacity: 0;
      pointer-events: none;
      transition: opacity 200ms, transform 200ms;
      white-space: nowrap;
      z-index: 100;
    }
    .toast.show {
      opacity: 1;
      transform: translateX(-50%) translateY(0);
    }

    /* ── Responsive ─────────────────────────────────────────── */
    @media (max-width: 480px) {
      .layout { padding: var(--space-3); gap: var(--space-3); }
      .stats-row { display: none; }
    }

    @media (prefers-reduced-motion: reduce) {
      *, *::before, *::after {
        animation-duration: 0.01ms !important;
        transition-duration: 0.01ms !important;
      }
    }

    /* ── Timeline ──────────────────────────────────────────── */
    #timeline-section {
      width: 100%;
      display: flex;
      flex-direction: column;
      gap: var(--space-2);
    }
    #calendar {
      background: var(--surface-2);
      border-radius: var(--radius-md);
      padding: var(--space-2);
    }
    .cal-popover {
      position: absolute;
      top: calc(100% + var(--space-2));
      left: 0;
      width: min(320px, 100%);
      z-index: 60;
      box-shadow: var(--shadow-lg);
      border: 1px solid var(--border);
    }
    #calendar-header, .cal-header { display: flex; align-items: center; gap: var(--space-2); margin-bottom: var(--space-1); }
    #calendar-label, #grid-cal-label { flex: 1; text-align: center; font-size: var(--text-sm); color: var(--text); }
    .cal-nav-btn {
      padding: var(--space-1) var(--space-2);
      background: var(--surface-3);
      border: 1px solid var(--border);
      border-radius: var(--radius-sm);
      color: var(--text);
      cursor: pointer;
    }
    .cal-nav-btn:hover:not(:disabled) { background: var(--surface-2); }
    .cal-nav-btn:disabled { opacity: 0.35; cursor: not-allowed; }
    #calendar-weekdays, .cal-weekdays, #calendar-grid, .cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
    #calendar-weekdays span, .cal-weekdays span { text-align: center; font-size: 11px; color: var(--text-muted); padding: 2px 0; }
    .cal-day {
      text-align: center; font-size: 12px; padding: 6px 0; border-radius: 4px;
      color: var(--text-muted); cursor: pointer; position: relative;
    }
    .cal-day.in-month { color: var(--text); }
    .cal-day.empty { visibility: hidden; cursor: default; }
    .cal-day.has-rec::after {
      content: ""; position: absolute; bottom: 3px; left: 50%; transform: translateX(-50%);
      width: 4px; height: 4px; border-radius: 50%; background: var(--accent);
    }
    .cal-day:not(.empty):hover { background: var(--surface-3); }
    .cal-day.day-selected { background: var(--accent); color: #000; font-weight: 600; } /* 刻意保留：選中格深色文字對比 accent 底色 */
    #timeline-controls { position: relative; display: flex; align-items: center; gap: var(--space-2); }
    #date-btn {
      display: inline-flex;
      align-items: center;
      gap: 6px;
      padding: var(--space-1) var(--space-3);
      background: var(--surface-2);
      border: 1px solid var(--border);
      border-radius: var(--radius-md);
      color: var(--text);
      font-size: var(--text-sm);
      cursor: pointer;
    }
    #date-btn:hover { background: var(--surface-3); }
    #date-btn[aria-expanded="true"] { border-color: var(--accent-border); color: var(--text); }
    #live-btn {
      display: inline-flex;
      align-items: center;
      gap: 4px;
      padding: var(--space-1) var(--space-3);
      background: var(--accent);
      border-radius: var(--radius-full);
      border: none;
      color: #000; /* 刻意保留：Live 藥丸深色文字對比 accent 底色 */
      font-weight: 600;
      font-size: var(--text-xs);
      letter-spacing: 0.04em;
      cursor: pointer;
    }
    #timeline-bar {
      display: flex;
      width: 100%;
      height: auto;
      background: none;
      gap: 3px;
    }
    /* 多選進行中的視覺提示（timeline.js enterSelectMode/exitSelectMode 切換） */
    #timeline-bar.selecting {
      outline: 2px dashed var(--accent-border);
      outline-offset: 4px;
      border-radius: var(--radius-sm);
    }
    .timeline-slot {
      flex: 1;
      position: relative;
      height: 40px;
      border-radius: var(--radius-sm);
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: 0.68rem;
      color: var(--text-faint);
      background: var(--surface-2);
      font-variant-numeric: tabular-nums;
    }
    .timeline-slot.has-data {
      background: var(--surface-3);
      color: var(--text-muted);
      cursor: pointer;
      border: 1px solid var(--border);
      transition: background var(--transition), border-color var(--transition), color var(--transition);
    }
    .timeline-slot.has-data:hover { border-color: var(--accent-border); color: var(--text); }
    .timeline-slot.selected { background: var(--accent); color: #04120c; font-weight: 600; } /* 刻意保留：選中格深色文字對比 accent 底色 */
    .timeline-slot.slot-selected { outline: 2px solid var(--accent); outline-offset: -2px; }
    /* timeline 標記改為真正可點 button(hit area ≥28×28),取代舊 ::after pseudo-element。
       slot 本體點擊播放;marker click stopPropagation 後開 popover(編輯/取消)。 */
    .slot-marker {
      position: absolute;
      top: -6px;
      right: -6px;
      min-width: 28px;
      min-height: 28px;
      display: inline-flex;
      align-items: center;
      justify-content: center;
      background: transparent;
      border: none;
      cursor: pointer;
      font-size: 14px;
      line-height: 1;
      padding: 4px;
      color: #ffd24d; /* 刻意保留：書籤星號金色，獨立於主題語意色之外的裝飾色 */
      touch-action: manipulation;
      z-index: 2;
      filter: drop-shadow(0 1px 2px rgba(0,0,0,.6));
    }
    .slot-marker:hover { background: rgba(255,255,255,.10); border-radius: 6px; }
    .slot-marker.protected-marker { color: #fff; } /* 刻意保留：鎖頭圖示白色，對比深底 */
    .slot-action-menu {
      position: absolute;
      background: var(--surface-2);
      border: 1px solid var(--surface-3);
      border-radius: 6px;
      padding: 4px;
      box-shadow: 0 4px 12px rgba(0,0,0,.5);
      z-index: 100;
      display: flex;
      flex-direction: column;
      gap: 2px;
      min-width: 140px;
    }
    .slot-action-menu button, .slot-action-menu a {
      background: transparent;
      border: none;
      color: var(--text);
      padding: 8px 12px;
      cursor: pointer;
      text-align: left;
      text-decoration: none;
      font-size: 13px;
      border-radius: 4px;
      font: inherit;
      white-space: nowrap;
    }
    .slot-action-menu button:hover, .slot-action-menu a:hover { background: var(--surface-2); }
    /* header 的「更多」選單：跟 timeline 的 slot-action-menu 共用視覺語言，
       差別是錨點固定在 header，用 CSS 相對定位取代 JS 算座標。 */
    .more-wrap { position: relative; }
    .more-menu { top: calc(100% + 6px); right: 0; }
    /* ── 底部固定面板堆疊 ─────────────────────────────────────
       說明條與儲存操作列以前各自 `position: fixed; bottom: 0`，等於搶同一塊位置，
       z-index 高的那個把另一個整條蓋掉。改成裝進同一個 flex column 之後它們垂直
       排開，也只剩一個地方要量高度。 */
    #bottom-stack {
      position: fixed; left: 0; right: 0; bottom: 0;
      z-index: 95;
      display: flex; flex-direction: column;
      pointer-events: none;   /* 沒有面板時不擋住底下的內容 */
    }
    #bottom-stack > * { pointer-events: auto; }

    /* 定位與層級都交給 #bottom-stack。這裡用 display 而不是 transform 開關，
       是因為它現在是 flex 子元素：用 transform 藏的話版面高度還在，
       --bottom-inset 會多墊一條看不見的空白出來。 */
    #storage-action-bar {
      display: none;
      gap: var(--space-3);
      align-items: center;
      padding: var(--space-3) var(--space-4);
      background: var(--surface-2);
      border-top: 1px solid var(--border);
      box-shadow: var(--shadow-lg);
    }
    /* 淡入而不是從畫面外滑上來：堆疊裡它下面還有說明條，往上滑的過程會整條
       壓在說明條上，恰好是這次要修掉的那個症狀。 */
    #storage-action-bar.visible { display: flex; animation: bottom-bar-in 160ms ease-out; }
    @keyframes bottom-bar-in { from { opacity: 0; } to { opacity: 1; } }
    @media (prefers-reduced-motion: reduce) { #storage-action-bar.visible { animation: none; } }
    /* 設定抽屜開著的時候不要讓操作列從抽屜上面穿出來。整個 #bottom-stack 的
       z-index 高過抽屜（說明條必須蓋得過抽屜，否則抽屜裡的欄位就沒有說明可看），
       所以這裡要單獨把操作列收起來。 */
    body:has(#settings-drawer.open) #storage-action-bar.visible { display: none; }
    #storage-action-bar button {
      display: inline-flex;
      align-items: center;
      gap: 6px;
      padding: var(--space-2) var(--space-3);
      border-radius: var(--radius-sm);
      background: var(--surface-3);
      border: 1px solid var(--border);
      cursor: pointer;
    }
    #storage-action-bar button:hover { background: var(--surface); border-color: var(--accent-border); }
    #storage-action-bar .icon { width: 16px; height: 16px; }
    #storage-sel-count { color: var(--text-muted); font-size: var(--text-sm); }
    .status-pill.vod {
      color: var(--vod);
      background: var(--vod-dim);
      border-color: var(--vod-border);
    }

    /* ── Transport / scrubber bar ───────────────────────────── */
    .transport {
      display: flex;
      align-items: center;
      gap: var(--space-3);
      padding: var(--space-2) var(--space-4);
      background: var(--surface-2);
      border-top: 1px solid var(--divider);
      font-variant-numeric: tabular-nums;
    }
    .transport-play {
      width: 30px; height: 30px;
      flex-shrink: 0;
      display: grid; place-items: center;
      border-radius: var(--radius-full);
      background: var(--surface-3);
      color: var(--text);
      font-size: 0.78rem;
      line-height: 1;
      transition: background var(--transition);
    }
    .transport-play:hover { background: rgba(255,255,255,0.12); }
    .transport-time {
      font-size: 0.72rem;
      color: var(--text-muted);
      letter-spacing: 0.02em;
      min-width: 42px;
      text-align: center;
      flex-shrink: 0;
      white-space: nowrap;
    }
    .transport-track {
      position: relative;
      flex: 1;
      height: 18px;
      display: flex;
      align-items: center;
      cursor: pointer;
      touch-action: none;
    }
    .transport-track::before {
      content: '';
      position: absolute;
      left: 0; right: 0;
      height: 4px;
      border-radius: var(--radius-full);
      background: var(--surface-3);
    }
    .transport-buffered {
      position: absolute;
      left: 0; height: 4px; width: 0;
      border-radius: var(--radius-full);
      background: rgba(255,255,255,0.14);
      pointer-events: none;
    }
    .transport-progress {
      position: absolute;
      left: 0; height: 4px; width: 0;
      border-radius: var(--radius-full);
      background: var(--accent);
      pointer-events: none;
      transition: background var(--transition);
    }
    .transport.is-vod .transport-progress { background: var(--vod); }
    .transport-handle {
      position: absolute;
      left: 0;
      width: 12px; height: 12px;
      border-radius: 50%;
      background: #fff; /* 刻意保留：拖曳把手白色，對比軌道深底 */
      border: 2px solid var(--accent);
      transform: translateX(-50%);
      box-shadow: 0 1px 5px rgba(0,0,0,0.55);
      pointer-events: none;
      transition: width 120ms, height 120ms, border-color var(--transition);
    }
    .transport.is-vod .transport-handle { border-color: var(--vod); }
    .transport-track:hover .transport-handle,
    .transport-track.dragging .transport-handle { width: 16px; height: 16px; }
    .transport-live-btn {
      flex-shrink: 0;
      display: inline-flex;
      align-items: center;
      gap: 5px;
      padding: var(--space-1) var(--space-3);
      border-radius: var(--radius-full);
      font-size: 0.7rem;
      font-weight: 600;
      letter-spacing: 0.06em;
      text-transform: uppercase;
      transition: background var(--transition), color var(--transition), box-shadow var(--transition);
    }
    .transport-live-btn[data-state="at-edge"] {
      background: var(--accent-dim);
      color: var(--accent);
      cursor: default;
      box-shadow: 0 0 0 1px var(--accent-border), 0 0 14px var(--accent-glow);
    }
    .transport-live-btn[data-state="at-edge"] .transport-live-dot {
      animation: pulse-dot 1.8s ease-in-out infinite;
    }
    .transport-live-btn[data-state="behind"],
    .transport-live-btn[data-state="vod"] {
      background: var(--surface-3);
      color: var(--text);
    }
    .transport-live-btn[data-state="behind"]:hover,
    .transport-live-btn[data-state="vod"]:hover { background: var(--accent); color: #000; } /* 刻意保留：同 live-btn，深色文字對比 accent 底色 */
    .transport-live-dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; flex-shrink: 0; }
    @media (max-width: 480px) {
      .transport-time { min-width: 34px; font-size: 0.66rem; }
      .transport-live-btn { padding: var(--space-1) var(--space-2); font-size: 0.62rem; }
    }

    /* ── Bottom panel ──────────────────────────────────────── */
    #bottom-panel {
      width: 100%;
      background: var(--surface);
      border: 1px solid var(--border);
      border-radius: var(--radius-lg);
      /* 3c 重疊修正：原本這裡無條件 overflow: hidden 純為裁切圓角。但 <1200px 時
         #tab-bar 是這個容器的子元素、且宣告 position: sticky——CSS 規範下
         sticky 定位會被「最近的、overflow 非 visible 的祖先」框住捲動範圍；
         #bottom-panel 本身在單欄版型不捲動（捲動的是整個頁面 body），於是
         sticky 完全失效，#tab-bar 退化成隨頁面正常捲動，捲動過程中從
         z-index 較低（40）處被 z-index 較高（50）的 sticky header 蓋住——
         這就是使用者回報的重疊，任何 <1200px 寬度捲過 tab-bar 都會重現。
         修法：<1200px 不設 overflow:hidden（sticky 才能生效，改由 #tab-bar
         自己補頂部圓角做視覺銜接）；>=1200px 沒有這個 sticky 子元素（.right-col
         整層才是 sticky 對象），overflow: hidden 照舊裁切圓角不受影響，見下方
         @media (min-width: 1200px) 區塊。
         已知既有限制（非本次修正引入，headless 實測兩者皆有）：Chrome 對
         border-collapse: collapse 的 <table> 不會被祖先的 overflow:hidden+
         border-radius 正確裁圓角（純 <div> 測試會裁，<table> 不會裁——
         Chromium 對 collapsed table 的繪製路徑繞過了祖先圓角裁切），
         #pig-status-table 捲到底、剛好選取/異常列貼齊底邊時方角會露出圓角
         範圍外；stash 回原始碼（無本次任何改動）重測同樣重現，證實與這次
         sticky 修正無關、修正前就存在，不在本任務範圍內處理。 */
    }
    #tab-bar {
      display: flex;
      border-bottom: 1px solid var(--divider);
      background: var(--surface-2);
      border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    }
    .tab-btn {
      padding: var(--space-2) var(--space-4);
      font-size: var(--text-xs);
      font-weight: 500;
      color: var(--text-muted);
      letter-spacing: 0.04em;
      border-right: 1px solid var(--divider);
      transition: color var(--transition), background var(--transition);
    }
    .tab-btn:hover:not(.active) { color: var(--text); }
    .tab-btn.active { color: var(--accent); background: var(--accent-dim); }
    .tab-content { display: none; }
    .tab-content.active { display: block; }
    #bookmark-list { list-style: none; margin: 0; padding: var(--space-3) var(--space-4); }

    /* ── Pig status table ───────────────────────────────────── */
    #pig-status-table {
      width: 100%;
      border-collapse: collapse;
      font-size: var(--text-xs);
    }
    #pig-status-table th {
      padding: var(--space-2) var(--space-3);
      text-align: left;
      color: var(--text-muted);
      font-weight: 500;
      text-transform: uppercase;
      letter-spacing: 0.06em;
      border-bottom: 1px solid var(--divider);
      background: var(--surface-2);
    }
    #pig-status-table td {
      padding: var(--space-2) var(--space-3);
      border-bottom: 1px solid var(--divider);
      color: var(--text);
    }
    .anomaly-row td { background: none; box-shadow: inset 3px 0 0 var(--error); }
    .anomaly-cell { color: var(--error); font-weight: 600; }
    .activity-bar { height: 3px; border-radius: 2px; background: var(--accent);
      opacity: 0.7; margin-top: 3px; max-width: 72px; }
    .anomaly-row .activity-bar { background: var(--error); }
    .pig-empty-msg { color: var(--text-faint); font-style: italic; padding: var(--space-3); }
    /* 可點欄頭排序 */
    #pig-status-table th.sortable { cursor: pointer; user-select: none; white-space: nowrap; }
    #pig-status-table th.sortable:hover { color: var(--text); }
    #pig-status-table th .sort-ind { font-size: 0.8em; opacity: 0.8; }
    /* 點列選取強調 */
    #pig-status-table tr.pig-row { cursor: pointer; }
    #pig-status-table tr.pig-row.selected td { background: var(--accent-dim); box-shadow: inset 2px 0 0 var(--accent); }
    /* selected + anomaly 同時成立：兩個 inset box-shadow 疊加（accent 內層 2px 蓋在
       error 外層 5px 之上，而非互相取代），維持雙重可辨識性而非其中一個訊號消失 */
    #pig-status-table tr.pig-row.selected.anomaly-row td {
      background: var(--accent-dim);
      box-shadow: inset 2px 0 0 var(--accent), inset 5px 0 0 var(--error);
    }
    /* 「只顯示選取」開關 */
    .solo-toggle { display: flex; align-items: center; gap: var(--space-2);
                   padding: var(--space-2) var(--space-3); font-size: var(--text-xs);
                   color: var(--text-muted); cursor: pointer; user-select: none; }
    /* 勾了「只顯示選取」但尚未點選任何豬時的操作提示（由 updateSoloHint 控制顯隱） */
    .solo-hint { color: var(--accent); font-size: var(--text-xs); }
    .solo-hint[hidden] { display: none; }

    /* ── 登入畫面（AUTH_ENABLED=true 時才由 auth.js 顯示）────── */
    .login-overlay { position: fixed; inset: 0; z-index: 1000;
                     display: flex; align-items: center; justify-content: center;
                     background: var(--bg); padding: var(--space-4); }
    .login-overlay[hidden] { display: none; }
    .login-card { width: min(360px, 100%); display: flex; flex-direction: column;
                  gap: var(--space-4); padding: var(--space-8);
                  background: var(--surface); border: 1px solid var(--border);
                  border-radius: var(--radius-lg); box-shadow: var(--shadow-lg); }
    .login-title { font-size: var(--text-lg); text-align: center;
                   margin-bottom: var(--space-2); }
    .login-field { display: flex; flex-direction: column; gap: var(--space-2);
                   font-size: var(--text-xs); color: var(--text-muted); }
    .login-field input { padding: var(--space-3); font-size: var(--text-sm);
                         color: var(--text); background: var(--surface-2);
                         border: 1px solid var(--border);
                         border-radius: var(--radius-sm); }
    .login-field input:focus { outline: none; border-color: var(--accent-border); }
    /* 沒有錯誤時不佔位，避免表單在出錯瞬間跳一下 */
    .login-error { min-height: 1.2em; font-size: var(--text-xs); color: var(--error); }
    .login-submit { padding: var(--space-3); font-size: var(--text-sm);
                    font-weight: 600; color: var(--bg); background: var(--accent);
                    border: none; border-radius: var(--radius-sm); cursor: pointer;
                    transition: background var(--transition); }
    .login-submit:hover:not(:disabled) { background: var(--accent-hover); }
    .login-submit:disabled { opacity: .6; cursor: default; }

    /* ── Notifications ──────────────────────────────────────── */
    .notif-toolbar {
      display: flex;
      gap: var(--space-3);
      align-items: center;
      padding: var(--space-3) var(--space-4);
      border-bottom: 1px solid var(--surface-3);
      /* >=1200px：#alert-list 與本工具列同為 .tab-content.active（flex:1+overflow-y:auto）
         的子節點，清單一長工具列就被捲走；sticky 讓它固定在捲動容器頂端。
         （#bottom-panel 在 >=1200px 是 overflow:hidden，但 sticky 的框限對象是最近的
         捲動祖先 .tab-content.active，不受影響。） */
      position: sticky;
      top: 0;
      background: var(--surface);
      z-index: 1;
    }
    .show-read-label {
      display: flex;
      gap: 6px;
      align-items: center;
      cursor: pointer;
      font-size: var(--text-xs);
      color: var(--text-muted);
    }
    #clear-read-btn { margin-left: auto; }
    #alert-list {
      list-style: none;
    }
    @media (max-width: 1199px) {
      /* <1200px：#bottom-panel 不是固定高度容器（3b 只在 >=1200px 生效），
         單欄版面下捲動的是整個頁面。這裡沿用舊版「清單自帶上限＋內部捲動」
         行為，避免長清單把整頁拉得過長。
         >=1200px：.right-col .tab-content.active 已經是 flex:1+overflow-y:auto
         的固定高度捲動容器（3b），#alert-list 不應再疊一層 220px 上限——
         否則清單被卡在 220px、右欄剩餘空間全浪費（本次修正的問題）。 */
      #alert-list { max-height: 220px; overflow-y: auto; }
    }
    .alert-item {
      display: flex;
      align-items: center;
      justify-content: space-between;
      padding: var(--space-2) var(--space-3);
      border-bottom: 1px solid var(--divider);
      cursor: pointer;
      transition: background var(--transition);
      gap: var(--space-3);
    }
    .alert-item:hover { background: var(--surface-2); }
    .alert-item.unread { border-left: 3px solid var(--error); }
    /* <1200px #bottom-panel 不再 overflow:hidden（3c sticky 修正），
       最後一則通知的全寬背景改自帶下圓角，避免頂出面板圓角露方角 */
    .alert-item:last-child { border-radius: 0 0 var(--radius-lg) var(--radius-lg); }
    .alert-info { display: flex; align-items: center; gap: var(--space-3); flex: 1; flex-wrap: wrap; }
    .alert-cam  { font-weight: 600; color: var(--text); font-size: var(--text-xs); }
    .alert-metric { color: var(--error); font-size: var(--text-xs); }
    .alert-time { color: var(--text-faint); font-size: var(--text-xs); }
    .alert-sigma { color: var(--text-muted); font-size: var(--text-xs); }
    .mark-read-btn {
      padding: var(--space-1) var(--space-2);
      border: 1px solid var(--border);
      border-radius: var(--radius-sm);
      font-size: var(--text-xs);
      color: var(--text-muted);
      background: var(--surface-2);
      flex-shrink: 0;
      white-space: nowrap;
      cursor: pointer;
    }
    .mark-read-btn:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
    .mark-read-btn:disabled { opacity: 0.4; cursor: default; }
    .notif-empty { color: var(--text-faint); font-style: italic; padding: var(--space-3); text-align: center; }
    /* ── 偵測框大小上下限 ───────────────────────────────────── */
    .settings-note {
      font-size: var(--text-xs); color: var(--text-muted);
      line-height: 1.7; margin-bottom: var(--space-2);
    }
    #box-limit-status { font-size: var(--text-xs); color: var(--text-muted); }
    /* 拖示範框時滑鼠要看得出來現在是在框東西，不是在拖對位。 */
    #video-wrap.box-sampling { cursor: crosshair; }

    /* ── 工程模式 ───────────────────────────────────────────── */
    /* 鎖著時把入口收起來。用 body 的 class 而不是逐一設 hidden 屬性：
       校正對位鈕的 hidden 另有 syncAlignButtonVisibility 在管（只有熱像才
       顯示），兩邊搶同一個屬性會互相蓋掉。 */
    body.eng-locked .eng-only { display: none !important; }
    #eng-unlock-box {
      margin-bottom: var(--space-4); padding: var(--space-3);
      border: 1px solid var(--border); border-radius: var(--radius-md);
      background: var(--surface-2);
    }
    #eng-state { font-size: var(--text-xs); color: var(--text-muted); line-height: 1.7; }
    #eng-unlock-form { display: flex; gap: var(--space-2); margin-top: var(--space-2); }
    #eng-unlock-form input {
      flex: 1; min-width: 0; padding: var(--space-1) var(--space-2);
      border: 1px solid var(--border); border-radius: var(--radius-sm);
      background: var(--surface); color: var(--text); font-size: var(--text-sm);
    }
    #eng-unlock-form button, #eng-lock-btn {
      margin-top: var(--space-2); padding: var(--space-1) var(--space-3);
      border: 1px solid var(--accent-border); border-radius: var(--radius-sm);
      background: var(--accent-dim); color: var(--accent);
      font-size: var(--text-xs); cursor: pointer;
    }
    #eng-unlock-form button { margin-top: 0; }
    /* 唯讀時仍然看得清楚數值：這裡調的是「改不動」的觀感，不是把字變糊。 */
    #settings-drawer input:disabled, #settings-drawer select:disabled {
      opacity: 0.75; cursor: not-allowed;
    }

    /* ── 導覽模式 ───────────────────────────────────────────── */
    /* 開啟時整頁降一點對比，明示「現在點東西不會有事」。 */
    body.help-mode .layout { filter: saturate(0.55) brightness(0.92); }
    body.help-mode [data-help] { outline: 1px dashed var(--accent); outline-offset: 2px; }
    body.help-mode { cursor: help; }
    #help-btn[aria-pressed="true"] { border-color: var(--accent); color: var(--accent); }
    /* 定位交給 #bottom-stack（它的 z-index 95 高過設定抽屜的 91：
       抽屜開著時說明條若被蓋掉，抽屜裡那些最需要說明的欄位就沒說明可看）。 */
    #help-sheet {
      padding: var(--space-3) var(--space-4);
      background: var(--surface-2);
      border-top: 1px solid var(--accent);
      color: var(--text);
      font-size: var(--text-sm);
      line-height: 1.6;
      /* 手機上是最可靠的呈現位置：不必算浮層座標，也不會被鍵盤或瀏覽器工具列蓋掉 */
      max-height: 40vh; overflow-y: auto;
    }
    .manual-link {
      display: inline-flex; align-items: center;
      font-size: var(--text-xs); text-decoration: none;
    }

    /* ── 遮罩編輯器 ─────────────────────────────────────────── */
    #mask-canvas {
      position: absolute; inset: 0;
      width: 100%; height: 100%;
      cursor: crosshair;      /* 明示「這裡是拿來點的」 */
      z-index: 3;
    }
    #mask-editor {
      padding: var(--space-2) var(--space-3);
      border-top: 1px solid var(--border);
      background: var(--surface-2);
    }
    .mask-toolbar {
      display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2);
      font-size: var(--text-xs);
    }
    .mask-toolbar .mask-hint { color: var(--text-faint); }
    /* 對位說明是四條並列的事，擠成一段沒有人會讀完。全域 reset 把 ol 的
       縮排歸零了（app.css 開頭的 *{padding:0}），這裡要自己補回來。 */
    .align-hint {
      flex-basis: 100%; margin: var(--space-1) 0 0; padding-left: 1.4em;
      list-style: decimal; line-height: 1.7; max-width: 62ch;
    }
    .align-hint li { margin-bottom: var(--space-1); }
    .align-hint b { color: var(--text-muted); font-weight: 600; }
    .mask-toolbar button {
      padding: var(--space-1) var(--space-2);
      border: 1px solid var(--border); border-radius: var(--radius-sm);
      background: var(--surface); color: var(--text-muted);
      font-size: var(--text-xs); cursor: pointer;
    }
    .mask-toolbar button:hover { border-color: var(--accent); color: var(--accent); }
    .mask-toolbar .mask-save { border-color: var(--accent); color: var(--accent); }
    #mask-region-list { list-style: none; margin: var(--space-2) 0 0; padding: 0; }
    .mask-region-item {
      display: flex; align-items: center; gap: var(--space-2);
      padding: var(--space-1) 0;
      font-size: var(--text-xs);
    }
    .mask-region-item input[type="text"] {
      flex: 1; min-width: 0;
      padding: var(--space-1) var(--space-2);
      background: var(--surface); border: 1px solid var(--border);
      border-radius: var(--radius-sm); color: var(--text);
      font-size: var(--text-xs);
    }
    .mask-region-item button {
      padding: var(--space-1) var(--space-2);
      border: 1px solid var(--border); border-radius: var(--radius-sm);
      background: var(--surface); color: var(--text-muted);
      font-size: var(--text-xs); cursor: pointer;
    }
    .mask-empty { color: var(--text-faint); font-style: italic; font-size: var(--text-xs); }
    .mask-edit-btn {
      width: 100%;
      padding: var(--space-2);
      border: 1px solid var(--border); border-radius: var(--radius-sm);
      background: var(--surface-2); color: var(--text-muted);
      font-size: var(--text-xs); cursor: pointer;
    }
    .mask-edit-btn:hover { border-color: var(--accent); color: var(--accent); }

    /* ── 關注清單：現在該去看哪幾隻豬 ───────────────────────── */
    #focus-section {
      padding: var(--space-2) var(--space-3);
      border-bottom: 1px solid var(--border);
    }
    #focus-header {
      display: flex; align-items: center; justify-content: space-between;
      gap: var(--space-2); margin-bottom: var(--space-1);
    }
    #focus-header h4 {
      margin: 0; font-size: var(--text-xs); font-weight: 600; color: var(--text-muted);
    }
    /* 「畫面上的框」的三態選單。跟 .solo-toggle 同一排視覺重量，兩者是同一類
       設定（都在調影片上要看到什麼），只是一個是下拉、一個是勾選。 */
    .box-mode-row {
      display: flex; align-items: center; gap: var(--space-2);
      padding: var(--space-2) var(--space-3);
      font-size: var(--text-xs); color: var(--text-muted);
    }
    .box-mode-row select {
      flex: 1; min-width: 0;
      padding: 2px var(--space-2);
      font-size: var(--text-xs); font-family: inherit;
      color: var(--text); background: var(--surface-2);
      border: 1px solid var(--border); border-radius: var(--radius-sm);
      cursor: pointer;
    }
    /* 最多顯示 5 列，超過捲動。MOT 的 ID 會跳號，清單長度不受控——不夾住的話
       它會一路往下長，把豬隻狀態表與底下所有東西全推出畫面外。
       max-height 用 em 而不是寫死 px：字級是變數，跟著一起變才不會夾到第 4.5 列。 */
    #focus-list {
      list-style: none; margin: 0; padding: 0;
      /* 高度直接從一列的組成算出來，不寫死 px：字級（--text-xs）是隨視窗
         縮放的 clamp()，寫死的話大螢幕會夾在第 4.5 列、小螢幕又多留一截空白。
         .focus-item 的 line-height 固定成 1.4 就是為了讓這條算式跟實際相符。 */
      max-height: calc(5 * (var(--text-xs) * 1.4 + var(--space-1) * 2));
      overflow-y: auto;
      /* 捲動容器要看得出「下面還有」，否則會以為清單就這麼長。 */
      scrollbar-width: thin;
    }
    #focus-list::-webkit-scrollbar { width: 6px; }
    #focus-list::-webkit-scrollbar-thumb {
      background: var(--border); border-radius: 3px;
    }
    .focus-item {
      display: flex; align-items: center; gap: var(--space-2);
      padding: var(--space-1) var(--space-2);
      border-radius: var(--radius-sm);
      font-size: var(--text-xs);
      /* 固定值，不用預設的 normal：#focus-list 的 max-height 是照這個數字
         算出來的，兩邊要一起改。 */
      line-height: 1.4;
      cursor: pointer;
    }
    .focus-item:hover { background: var(--surface-2); }
    .focus-tag {
      flex-shrink: 0; padding: 0 var(--space-1);
      border-radius: var(--radius-sm); font-weight: 600;
      border: 1px solid currentColor;
    }
    /* 三種顏色與 canvas 上的框一致，清單與畫面才對得起來。 */
    .focus-anomaly   .focus-tag { color: #ff4444; }
    .focus-lowest    .focus-tag { color: #ff9a3c; }
    .focus-reference .focus-tag { color: #3ecf8e; }
    .focus-oid { font-weight: 600; color: var(--text); }
    .focus-act { margin-left: auto; color: var(--text-muted); font-variant-numeric: tabular-nums; }
    .focus-separator {
      margin-top: var(--space-1); padding: var(--space-1) var(--space-2);
      border-top: 1px dashed var(--border);
      font-size: var(--text-xs); color: var(--text-faint);
    }
    /* 「最近消失」：編號已經死掉、畫面上點不到的異常。預設收合，
       整段刻意壓低對比——它不是要人現在去看的東西，只是不要讓剛剛
       還在的警報無聲無息消失。 */
    .focus-recent { margin-top: var(--space-1); border-top: 1px dashed var(--border); }
    .focus-recent summary {
      padding: var(--space-1) var(--space-2); cursor: pointer;
      font-size: var(--text-xs); color: var(--text-faint);
    }
    .focus-recent ul { list-style: none; margin: 0; padding: 0; }
    .focus-recent-item {
      display: flex; gap: var(--space-2); align-items: center; cursor: pointer;
      padding: var(--space-1) var(--space-2);
      font-size: var(--text-xs); color: var(--text-muted);
    }
    .focus-recent-item:hover { background: var(--surface-hover, rgba(127,127,127,0.12)); }
    .focus-gone { margin-left: auto; font-variant-numeric: tabular-nums; }
        /* 熱像對位校正面板。沿用遮罩編輯器那組工具列樣式，多一排微調鈕。 */
    #align-panel {
      border: 1px solid var(--border); border-radius: var(--radius-sm);
      padding: var(--space-2); margin-top: var(--space-2);
      display: flex; flex-direction: column; gap: var(--space-2);
    }
    .align-controls, .align-footer {
      display: flex; align-items: center; gap: var(--space-1);
      flex-wrap: wrap;
    }
    .align-controls button {
      min-width: 2rem; padding: 2px var(--space-1);
    }
    .align-group-label {
      font-size: var(--text-xs); color: var(--text-faint);
      margin-left: var(--space-2);
    }
    #align-readout {
      font-size: var(--text-xs); color: var(--text-faint);
      margin-right: auto;
    }
    /* 校正模式下整個畫框變成拖曳把手。游標是唯一的提示，沒有它會不知道能拖。 */
    #video-wrap.align-editing { cursor: move; touch-action: none; }
    .focus-status {
      padding: var(--space-2); text-align: center;
      font-size: var(--text-xs); color: var(--text-faint); font-style: italic;
    }
    /* 折疊群組的次數標記。一條通知可能代表連續好幾筆告警。 */
    .alert-count {
      padding: 0 var(--space-1);
      border-radius: var(--radius-sm);
      background: var(--surface-2);
      border: 1px solid var(--border);
      color: var(--text-muted);
      font-size: var(--text-xs);
      font-variant-numeric: tabular-nums;
    }
    .load-more-btn {
      display: block;
      width: calc(100% - var(--space-4));
      margin: var(--space-2) auto var(--space-3);
      padding: var(--space-2);
      border: 1px solid var(--border);
      border-radius: var(--radius-sm);
      background: var(--surface-2);
      color: var(--text-muted);
      font-size: var(--text-xs);
      cursor: pointer;
    }
    .load-more-btn:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
    .load-more-btn:disabled { opacity: 0.5; cursor: default; }
    /* ── Settings drawer（唯一使用處，單欄版型） ─────────────── */
    #tab-settings { padding: var(--space-3) var(--space-4); }
    .settings-form { display: grid; grid-template-columns: 1fr; gap: var(--space-3); }
    .settings-field { display: flex; flex-direction: column; gap: var(--space-1); }
    .settings-field label { font-size: var(--text-xs); font-weight: 500; color: var(--text-muted); }
    .settings-field input, .settings-field select {
      padding: var(--space-1) var(--space-2);
      background: var(--surface-2);
      border: 1px solid var(--border);
      border-radius: var(--radius-sm);
      font-size: var(--text-xs);
      color: var(--text);
      width: 100%;
    }
    .settings-field input:focus, .settings-field select:focus {
      outline: none; border-color: var(--accent);
    }
    .settings-save-row { display: flex; align-items: center; gap: var(--space-3); margin-top: var(--space-2); }
    #save-settings-btn {
      padding: var(--space-1) var(--space-4);
      background: var(--accent);
      color: #fff; /* 注意：其餘 accent 底色元件（live-btn/cal-day 選中/timeline-slot 選中）
                       皆用 #000 深色文字對比；此處维持既有白字，非本次收尾範圍，留意後續若調整 accent 明度需一併檢視對比 */
      border-radius: var(--radius-sm);
      font-size: var(--text-xs);
      font-weight: 600;
      cursor: pointer;
    }
    #save-settings-btn:hover { opacity: 0.85; }
    #settings-status { font-size: var(--text-xs); color: var(--text-muted); }

    /* ── Settings drawer container ──────────────────────────── */
    #settings-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.5);
      z-index: 90; opacity: 0; transition: opacity 200ms; }
    #settings-overlay.open { opacity: 1; }
    #settings-drawer { position: fixed; top: 0; right: 0; bottom: 0; width: min(420px, 100vw);
      background: var(--surface); border-left: 1px solid var(--border); z-index: 91;
      transform: translateX(100%); transition: transform 240ms cubic-bezier(0.16,1,0.3,1);
      display: flex; flex-direction: column; }
    #settings-drawer.open { transform: translateX(0); }
    .drawer-head { display: flex; align-items: center; justify-content: space-between;
      padding: var(--space-4); border-bottom: 1px solid var(--divider); }
    .drawer-body { overflow-y: auto; padding: var(--space-4); flex: 1;
      /* 說明條蓋在畫面底部時，抽屜最後一個欄位會被吃掉 */
      padding-bottom: calc(var(--space-4) + var(--bottom-inset, 0px)); }
    /* 手風琴：標題＋說明永遠看得到（放在 summary 裡，<details> 收合時只有
       summary 的內容還看得到），欄位收合。仿 panels.js:128 用原生 <details>
       收合「最近消失」清單的手法：省掉自己維護一份開合狀態。 */
    .settings-group { border-top: 1px solid var(--divider); }
    .settings-group:first-child { border-top: none; }
    .settings-group summary {
      list-style: none; cursor: pointer;
      display: flex; align-items: flex-start; gap: var(--space-2);
      padding: var(--space-3) 0;
    }
    .settings-group summary::-webkit-details-marker { display: none; }
    .settings-chev { margin-top: 3px; color: var(--text-muted); transition: transform var(--transition); flex-shrink: 0; }
    .settings-group[open] .settings-chev { transform: rotate(90deg); }
    .settings-group-head { display: flex; flex-direction: column; gap: 2px; }
    .settings-group-head b { font-size: var(--text-sm); font-weight: 600; color: var(--text); }
    .settings-group-head small { font-size: var(--text-xs); color: var(--text-muted); line-height: 1.5; }
    .settings-group .settings-form { padding: 0 0 var(--space-4) calc(14px + var(--space-2)); }
    /* 欄位底下的一句話補充。用在「這個設定的作用範圍比名字看起來窄」的地方。 */
    .settings-hint {
      display: block; font-size: var(--text-xs); color: var(--text-faint);
      margin-top: var(--space-1); line-height: 1.4;
    }
    .settings-field.disabled { opacity: 0.45; pointer-events: none; }
    .settings-field input.invalid { border-color: var(--error); }
    .field-error { color: var(--error); font-size: var(--text-xs); }

    .storage-pill { font-size: 14px; margin-left: 8px; cursor: default; }
    .storage-pill.ok { color: var(--accent); }
    .storage-pill.degraded { color: var(--vod); }
    .storage-pill.down { color: var(--error); }

    /* Task 8: Grid 監看模式 */
    #grid-view { display: grid; gap: var(--space-3);
      grid-template-columns: repeat(var(--grid-cols, 2), 1fr); }
    #grid-view[hidden] { display: none; }
    .grid-tile { position: relative; background: var(--surface); overflow: hidden;
      border: 1px solid var(--border); border-radius: var(--radius-lg);
      aspect-ratio: var(--video-ar, 16 / 9); cursor: pointer;   /* 同 #video-wrap，由 grid.js 寫入 */
      transition: border-color var(--transition); }
    .grid-tile:hover { border-color: var(--accent-border); }
    .grid-tile video { width: 100%; height: 100%; object-fit: contain; background: #000; } /* 同 video 黑底 */
    .grid-tile.alerting { border-color: var(--error); box-shadow: 0 0 0 1px var(--error); }
    .tile-info { position: absolute; left: 0; right: 0; bottom: 0;
      display: flex; align-items: center; gap: var(--space-2);
      padding: var(--space-1) var(--space-3);
      background: linear-gradient(transparent, rgba(0,0,0,0.75));
      font-size: var(--text-xs); }
    .tile-name { font-weight: 600; }
    .tile-count { color: var(--text-muted); }
    .tile-live { margin-left: auto; color: var(--accent); display: inline-flex;
      align-items: center; gap: 4px; font-weight: 600; letter-spacing: 0.05em; }
    .grid-tile.offline .tile-live, .grid-tile.offline .tile-vod { color: var(--text-faint); }
    .grid-tile.offline video { opacity: 0.2; }
    .grid-tile.nosignal { cursor: default; }
    .grid-tile.nosignal .tile-live, .grid-tile.nosignal .tile-vod { color: var(--text-faint); }
    .tile-alert { position: absolute; top: var(--space-2); right: var(--space-2);
      color: var(--error); }
    .tile-retry { position: absolute; top: 50%; left: 50%; transform: translate(-50%,-50%);
      padding: var(--space-1) var(--space-4); background: var(--surface-3);
      border: 1px solid var(--border); border-radius: var(--radius-full); }
    @media (max-width: 700px) { #grid-view { grid-template-columns: 1fr !important; } }

    /* Finding 2（whole-branch review）：/cameras 失敗的 grid 錯誤佔位 */
    .grid-error { grid-column: 1 / -1; display: flex; flex-direction: column;
      align-items: center; justify-content: center; gap: var(--space-3);
      padding: var(--space-6); color: var(--text-muted); }
    .grid-error-retry { padding: var(--space-1) var(--space-4); background: var(--surface-3);
      border: 1px solid var(--border); border-radius: var(--radius-full); color: inherit; }

    /* grid 時段列：沿用 timeline-slot 視覺，但唯讀（無保留/書籤/多選標記） */
    #grid-timeline { display: flex; flex-direction: column; gap: var(--space-2); margin-top: var(--space-3); }
    #grid-timeline[hidden] { display: none; }
    #grid-timeline-controls { position: relative; display: flex; align-items: center; gap: var(--space-2); }
    #grid-date-btn { display: inline-flex; align-items: center; gap: var(--space-2);
      padding: var(--space-1) var(--space-3); background: var(--surface-2);
      border: 1px solid var(--border); border-radius: var(--radius-full); }
    #grid-timeline-hint { color: var(--text-muted); font-size: var(--text-xs); }
    @media (max-width: 640px) { #grid-timeline-hint { display: none; } }
    #grid-timeline-bar { display: grid; grid-template-columns: repeat(24, 1fr); gap: 2px; }
    .grid-slot { height: 32px; display: flex; align-items: center; justify-content: center;
      font-size: var(--text-xs); color: var(--text-faint); background: var(--surface-2);
      border-radius: var(--radius-sm); }
    .grid-slot.has-data { color: var(--text-muted); cursor: pointer; }
    .grid-slot.has-data:hover { background: var(--surface-3); color: var(--text); }
    .grid-slot.playing { background: var(--vod-dim); color: var(--vod);
      border: 1px solid var(--vod-border); font-weight: 600; }
    #grid-playback-banner { display: flex; align-items: center; gap: var(--space-2);
      margin-bottom: var(--space-2); padding: var(--space-2) var(--space-4);
      background: var(--vod-dim); color: var(--vod);
      border: 1px solid var(--vod-border); border-radius: var(--radius-lg);
      font-size: var(--text-xs); font-weight: 500; }
    #grid-playback-banner[hidden] { display: none; }
    #grid-live-btn { margin-left: auto; padding: 2px 10px;
      border: 1px solid var(--vod-border); border-radius: var(--radius-full);
      color: var(--vod); background: none; cursor: pointer; }
    #grid-live-btn:hover { background: var(--vod); color: #140d02; } /* 同 vod-banner hover */
    .grid-tile .tile-vod { margin-left: auto; color: var(--vod); font-weight: 600;
      letter-spacing: 0.05em; }
    @media (max-width: 700px) { .grid-slot { height: 40px; } }

    /* ── Modals（書籤編輯／刪除確認，共用殼）───────────────────
       display:none/flex 由對應 JS（panels.js openBookmarkEditModal/closeBookmarkEditModal、
       timeline.js onDeleteRecClick/closeDeleteModal）直接切 inline style，此處不設 display，
       避免蓋掉 inline 值。 */
    .modal-overlay {
      position: fixed;
      inset: 0;
      background: rgba(0,0,0,.6);
      z-index: 1000;
      align-items: center;
      justify-content: center;
    }
    .modal-box {
      background: var(--surface-2);
      padding: var(--space-4);
      border-radius: 12px;
      max-width: 420px;
      width: 90%;
    }
    .modal-box h3 { margin: 0 0 12px; }
    .modal-box-delete h3 { margin-bottom: 8px; }
    .modal-form { display: flex; flex-direction: column; gap: 10px; }
    .modal-field { display: flex; flex-direction: column; gap: 4px; font-size: 13px; }
    .modal-field input[type="text"],
    .modal-field textarea {
      padding: 6px 8px;
      background: var(--surface);
      border: 1px solid var(--surface-3);
      color: var(--text);
      border-radius: 4px;
      font: inherit;
    }
    .modal-field textarea { resize: vertical; }
    .modal-actions { display: flex; gap: 8px; justify-content: flex-end; margin-top: 12px; }
    .modal-warn-box {
      background: var(--error-dim);
      border: 1px solid var(--error);
      border-radius: 8px;
      padding: 8px;
      margin: 8px 0;
    }
    .modal-warn-title { color: var(--error); }
    .modal-warn-list { margin: 6px 0 0; padding-left: 18px; }
    .modal-checkbox-row { display: flex; gap: 6px; margin-top: 8px; align-items: center; }
    .modal-danger-text { color: var(--error); font-size: 13px; }
    .btn-danger { background: var(--error); color: #fff; } /* 白字對比 --error 底色，非語意 token */
