/* QuantWing adaptive navigation.
   One five-button DOM changes geometry only: desktop rail, mobile bottom dock.
   Viewer nodes and bot lifecycle stay untouched. */
:root {
  --qw-desktop-rail-w: 236px;
  --qw-desktop-rail-z: 11020;
  --qw-titlebar-h: 44px;
  --qw-floating-glass-background: color-mix(in srgb, var(--qw-glass-surface, #152337) 35%, transparent);
  --qw-floating-glass-filter: blur(24px) saturate(160%);
  /* 2026-09-16：70 -> 50。站長「縮扁一點 太高了」。50 是 LINE TODAY 的實測值
     （原圖 1170x2532 = iPhone @3x，量到 150 實際 px / 3 = 50 CSS px）。
     ⭐ 這個變數另外三處在算內容區 padding-bottom（:438 :441 與 quantum-flight.css:1064）
        ⇒ 改這一個，四處一起跟上，⛔ 不要各改各的。 */
  --qw-mobile-dock-h: 68px;
  --qw-mobile-dock-bottom: max(14px, calc(env(safe-area-inset-bottom, 0px) - 10px));
  /* ⭐ 2026-09-16 新增，因為「dock 內距」這一個數字被**四處**各自手抄：
       .tabs 的 padding、.tab 的 height、.tab 的 border-radius、.qw-tab-indicator 的
       top/left/width/height/radius。上一輪只改了容器高度，其餘三處留在舊算式上
       ⇒ 按鈕戳出藥丸、選中膠囊整個歪掉，而 CSS 一個字都不報錯。
     ⛔ 這四處以後一律從這兩個變數算，不准再寫死。 */
  --qw-dock-pad: 4px;
  --qw-dock-item-h: calc(var(--qw-mobile-dock-h) - 2 * var(--qw-dock-pad));
  --qw-dock-press-duration: 176ms;
  --qw-dock-release-duration: 600ms;
  --qw-dock-spring: cubic-bezier(.2, 1.35, .3, 1);
}

.window-drag-strip { display: none; }

/* ⭐ 標題列保留高度只屬於桌面殼（2026-09-14，站長：「上方不要被Logo 狀態佔空間」）。
   ⛔ --qw-titlebar-h 原本宣告在**裸 :root** 上、沒有任何宿主條件 ⇒ 網頁版每一頁的內容
      都被 #appShell 的 padding-top 往下推 44px，而那 44px 裡**什麼都沒有**：
      真正會畫在那裡的 .window-drag-strip 只在 html[data-host="tauri"] 下 display:block
      （E1：1440×900 逐點 elementFromPoint 掃 y=2..38，回傳全是純背景的 div#appShell）。
   ⚠ 只改變數、⛔ 不動 :45 那條 padding-top —— 桌面殼那邊拖曳條與三顆視窗鈕都寫
      height: var(--qw-titlebar-h, 44px)，它們全在 html[data-host="tauri"] 前綴下，
      拿到的仍是 44px ⇒ 桌面殼一個像素都不會變（已跑負對照）。
   ⓘ 特異性 (0,1,1) > :root 的 (0,0,1)，⛔ 不靠載入順序硬撐。 */
html:not([data-host="tauri"]) { --qw-titlebar-h: 0px; }

/* ---- 「總覽」＝四宮格，選中時填實（站長 2026-09-15 推翻 09-14 的商標版）----
   逐字原話：「網站和桌面板你那樣設計更醜了，還是弄回去原本的總攬圖案 / Logo和QuantWing 放一起才好看」
   ＋「總攬的 Logo 選中 沒有圖案變化 補上 自己想怎改」。

   ⛔ 這裡原本有一整段（35 行）把總覽那一格特殊化：fill:currentColor / stroke:none /
      選中時 scale(1.08) ＋ mask-image:none。它存在的唯一理由是「那一格放的是實心商標，
      不能套隔壁四顆的線稿→填滿」。圖示退回四宮格之後，那個理由整段消失 ⇒ 已刪，
      ⛔ 不是「順手精簡」。（memory: a_rule_whose_reason_is_already_satisfied_is_not_a_blocker）

   ⚠ 順帶被那段掩蓋掉的缺陷：手機 dock 的 .tab 本來就是 color:var(--text)（本檔 :324 一帶），
      ⇒ 未選中已經是最亮色、選中無處可亮，而商標只靠「亮度＋放大 8%」表達狀態
      ⇒ 手機上等於**完全沒有變化**（桌面 rail 才有 --muted→--text 的提亮）。那就是站長說的
      「選中 沒有圖案變化」。四宮格改用形狀（空心→實心）表達，兩個平台都看得到。

   ⓘ 為什麼這一條**刻意不包在 media query 裡**：站長對「選中要有變化」的要求同時涵蓋手機與桌面，
      而形狀變化在兩邊都成立 ⇒ 這是唯一一條有意跨平台的宣告。本輪其他改動（隱藏文字、收窄 dock）
      全部關在 @media (max-width: 760px) 內。
   ⓘ 特異性：共用的手機選中態是 body:not(.auth-locked) .app-header .tab.active svg ＝ (0,4,2)，
      這條加了 [data-tab] 是 (0,5,2) ⇒ 兩邊都贏，⛔ 不是靠載入順序硬撐。
   ⚠ 為什麼要把 stroke 關掉（共用那條寫 stroke-width:2.6）：四宮格上下兩塊在 24 座標系只隔 3 單位，
      兩邊各加粗 1.3 之後剩 0.4 單位（26px 下約 0.43px）⇒ 四塊會黏成一坨、讀不出是儀表板。
      它是**封閉**路徑，光靠 fill 就填得滿，不需要 stroke 補（隔壁四顆有開放路徑才需要）。 */
body:not(.auth-locked) .app-header .tab[data-tab="dashboard"].active svg {
  fill: currentColor;
  stroke: none;
  stroke-width: 0;
}

/* Caption controls are fixed to the viewport. Only the mobile nav may scale;
   its header must never establish a transformed/filter containing block. */
body:not(.auth-locked) .app-header {
  /* The ancestor owns stacking: children cannot escape fullscreen main. */
  z-index: var(--qw-desktop-rail-z);
  pointer-events: none;
  transform: none;
  filter: none;
  perspective: none;
  contain: none;
  will-change: auto;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}
body:not(.auth-locked) .app-header :is(.tabs, .header-status, .window-controls, .window-drag-region) { pointer-events: auto; }

/* 登入／阻擋畫面也保留同一條無框拖曳面；三顆按鈕仍是 no-drag。 */
html[data-host="tauri"] body.auth-locked .window-drag-region {
  display: block;
  position: fixed;
  inset: 0 138px auto 0;
  z-index: 11990;
  height: var(--qw-titlebar-h);
  border-bottom: 0;
  background: transparent;
  -webkit-app-region: drag;
  user-select: none;
}

@media (min-width: 761px) {
  body:not(.auth-locked) .ai-chat-card.is-presentation-fullscreen {
    left: var(--qw-desktop-rail-w);
    min-height: 0 !important;
  }
  body:not(.auth-locked) #appShell {
    box-sizing: border-box;
    min-height: 100dvh;
    padding-left: var(--qw-desktop-rail-w);
    padding-top: var(--qw-titlebar-h);
  }

  body:not(.auth-locked) .app-header {
    position: fixed;
    inset: 0 auto 0 0;
    z-index: var(--qw-desktop-rail-z, 9000);
    box-sizing: border-box;
    width: var(--qw-desktop-rail-w);
    min-height: 100dvh;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    padding: 0 16px 20px;
    border-right: 1px solid color-mix(in srgb, var(--muted) 18%, transparent);
    border-bottom: 0;
    background: transparent;
    box-shadow: none;
    /* A backdrop-filter on the header would become the containing block for
       the fixed native window controls and pull them into the rail. */
    backdrop-filter: none;
  }
  body:not(.auth-locked) .app-header::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    background: rgba(8, 16, 29, .94);
  }
  @supports ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
    body:not(.auth-locked) .app-header::before {
      background: var(--qw-floating-glass-background);
      -webkit-backdrop-filter: var(--qw-floating-glass-filter);
      backdrop-filter: var(--qw-floating-glass-filter);
    }
  }

  body:not(.auth-locked) .app-header .brand {
    width: 100%;
    /* 收掉 46px 的商標圖之後，這一塊只剩產品名一行 ⇒ 104px 會留一大片空白。
       站長反覆要求的就是「上方不要被 Logo 狀態佔空間」⇒ 一併把高度收回來。 */
    height: 56px;
    min-width: 0;
    flex: 0 0 56px;
    flex-direction: row;
    justify-content: flex-start;
    gap: 12px;
    padding: 10px 2px 0;
    pointer-events: none;
  }
  /* ⭐ 站長 2026-09-15：「Logo和QuantWing 放一起才好看」⇒ 商標回到產品名旁邊。
     ⛔ 09-14 收掉它的理由是「商標已經是 rail 第一格的圖示，同一欄出現兩次」——
        總覽退回四宮格之後**那個重複不存在了**，所以這裡收掉的理由也消失。
     ⚠ 這不牴觸站長講過兩次的「上方不要被Logo 狀態佔空間」：那句話要的是**垂直空間**，
        而 .brand 的 height:56px 一個像素都沒動（見上一條規則），商標是**橫向**擺在產品名左邊。
        手機（≤760px）那邊 .brand 整塊仍然 display:none，完全不受這條影響。
     ⚠ 必須寫死 width/height，而且**必須帶 #appShell**：quantum-flight.css:939 有一條
        `#appShell .brand-logo` 把它訂成 56x56。那條是 (1,0,1)，而
        `body:not(.auth-locked) .app-header .brand-logo` 只有 (0,3,1) —— **id 贏過任何數量的 class**
        ⇒ 不加 #appShell 的話這裡寫幾 px 都沒用，實測仍是 56x56（46px 的內容區裝 56px ⇒ 溢出）。
        ⛔ 這不是「載入順序」問題，adaptive-navigation.css 本來就排在 quantum-flight.css 前面，
           但就算排到後面，同樣輸給 id。加 #appShell 之後是 (1,3,1) ⇒ 兩邊都贏。
        ⚠ 我第一版的註解寫「這一段沒有任何 .brand-logo 尺寸規則」——**那句是錯的**，
           是實際渲染量到 56x56 才發現。留著這行當教訓：⛔ 別把「我 grep 過的檔」當成「全部的檔」。 */
  body:not(.auth-locked) #appShell .app-header .brand-logo {
    display: block;
    flex: 0 0 34px;
    width: 34px;
    height: 34px;
  }
  body:not(.auth-locked) .app-header .brand-text {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0;
    padding-block: 2px;
  }
  body:not(.auth-locked) .app-header .brand strong {
    display: block;
    color: var(--text);
    /* The rail clips horizontal labels while expanding. A full line box must
       still contain descenders such as QuantWing's g at every display scale. */
    font: 700 24px/1.3 "Segoe UI Variable", "Segoe UI", sans-serif;
    letter-spacing: -.03em;
  }

  body:not(.auth-locked) .app-header .tabs {
    display: flex;
    width: 100%;
    height: auto;
    flex: 0 0 auto;
    flex-direction: column;
    align-items: stretch;
    gap: 8px;
    padding: 12px 0 0;
    border: 0;
    border-radius: 0;
    background: transparent;
    box-shadow: none;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
  body:not(.auth-locked) .app-header .tab {
    width: 100%;
    min-width: 0;
    height: 56px;
    min-height: 56px;
    grid-template-columns: 28px minmax(0, 1fr);
    grid-template-rows: 1fr;
    justify-items: start;
    gap: 14px;
    padding: 0 16px;
    border-color: transparent;
    border-radius: 14px;
    color: var(--muted);
    background: transparent;
    box-shadow: none;
    font-size: 18px;
    font-weight: 600;
    letter-spacing: 0;
  }
  body:not(.auth-locked) .app-header .tab svg { width: 24px; height: 24px; }
  body:not(.auth-locked) .app-header .tab:hover {
    color: var(--text);
    background: color-mix(in srgb, var(--text) 6%, transparent);
    transform: translateX(2px);
  }
  body:not(.auth-locked) .app-header .tab.active {
    color: var(--text);
    border-color: color-mix(in srgb, var(--accent) 20%, transparent);
    background: color-mix(in srgb, var(--accent) 12%, transparent);
    box-shadow: inset 3px 0 var(--accent), inset 0 1px rgb(255 255 255 / 6%);
  }

  body:not(.auth-locked) .app-header .header-status {
    width: 100%;
    margin-top: auto;
    flex-direction: column;
    gap: 10px;
  }
  body:not(.auth-locked) .app-header .header-update-badge,
  body:not(.auth-locked) .app-header .system-status {
    box-sizing: border-box;
    width: 100%;
    min-width: 0;
    min-height: 56px;
    justify-content: flex-start;
    padding: 10px 16px;
    white-space: normal;
  }
  body:not(.auth-locked) .app-header .header-update-badge { font-size: 15px; line-height: 1.4; }
  body:not(.auth-locked) .app-header .header-update-badge::before { margin-right: 0; font-size: 14px; }
  body:not(.auth-locked) .app-header .header-update-badge { gap: 10px; }
  body:not(.auth-locked) .app-header .system-status { gap: 10px; font-size: 16px; line-height: 1.4; }
  body:not(.auth-locked) .app-header .system-status-text {
    min-width: 0;
    max-width: none;
    overflow-wrap: anywhere;
    white-space: normal;
  }
  /* 2026-10-18 根治：面板以前用 left:calc(100% + 12px) 跟著 rail 寬度走，而面板又是 rail 的
     子元素 ⇒ 游標移到面板＝hover rail ⇒ rail 展開 ⇒ 面板被推走 ⇒ 游標離開 ⇒ rail 收起 ⇒ 面板回來，
     形成每秒數次的開關迴圈。
     ⭐ 2026-10-18 第二輪（站長：「收回來後就不會再外移出去，畫面很奇怪」）：主流側欄的彈出面板
     ＝面板開著時側欄「釘住」展開（:has(.system-status-panel:not([hidden]))），面板固定貼在
     「展開寬度」外側 ⇒ 位置只有一個、不隨游標移動，也就不可能再形成迴圈；關閉面板才回到 hover 行為。 */
  body:not(.auth-locked) .app-header .system-status-panel {
    position: fixed;
    top: auto;
    right: auto;
    bottom: 16px;
    left: calc(var(--qw-desktop-rail-expanded-w, var(--qw-desktop-rail-w)) + 12px);
  }

  /* Windows keeps a discoverable drag region beside its caption buttons.
     This strip is the same app surface as the rail, separated only by hairlines. */
  html[data-host="tauri"] body:not(.auth-locked) .window-drag-strip {
    display: block;
    position: fixed;
    inset: 0 138px auto 0;
    /* The rail must receive hover over its logo. This transparent strip used
       to cover the upper half of the brand at z=11990 and steal the hit. */
    z-index: calc(var(--qw-desktop-rail-z, 9000) - 1);
    height: var(--qw-titlebar-h);
    border-bottom: 0;
    background: transparent;
    -webkit-app-region: drag;
    user-select: none;
  }
  /* 頂列空間由 appShell 統一保留；2x2 的 main 全高規則不再能把它覆寫掉。 */
}

@media (min-width: 761px) and (max-width: 1100px) {
  :root { --qw-desktop-rail-w: 204px; }
  body:not(.auth-locked) .app-header { padding-inline: 12px; }
  body:not(.auth-locked) .app-header .brand { gap: 8px; padding-inline: 0; }
  /* ⛔ 這裡原本還有一條 .brand-logo { width:40px; height:40px; flex-basis:40px }。
     商標圖已在上面的 761px 區塊 display:none（站長「頂欄那顆收掉」）⇒ 那條是死碼，
     留著會讓下一個讀的人以為窄桌面還有一顆 40px 的品牌圖。已刪。 */
  body:not(.auth-locked) .app-header .tab { padding-inline: 12px; gap: 10px; }
}

@media (max-width: 760px) {
  body:not(.auth-locked) #appShell { padding-left: 0; }
  body:not(.auth-locked) .app-header {
    /* Keep fixed descendants anchored to the viewport, not this header. */
    backdrop-filter: none;
  }
  html[data-host="tauri"] body:not(.auth-locked) .app-header .header-status {
    margin-right: 126px;
  }
  body:not(.auth-locked) .app-header .tabs {
    position: fixed;
    isolation: isolate;
    /* ⭐ 站長 2026-09-15：「常駐按紐左右間距太遠 拉近一點」。
       ⛔ 原本是左右各留 12px 再 repeat(5, 1fr) 撐滿整條螢幕 ⇒ 那不是「間距設太大」，
          是**根本沒有間距設定**：五格把所有剩餘空間平分掉，圖示中心距隨螢幕寬線性拉開
          （改動前實測：375px=68.1px／414px=76.0px／760px=145.2px，而圖示本身只有 26px）。
       ⇒ 改成內容寬的浮動藥丸置中：格寬鎖 54px、中心距恆為 56px，與螢幕寬**脫鉤**。
       ⓘ left/right 同時為 0 加 margin-inline:auto 就是置中，⛔ 不用 transform ——
          這條 .tabs 的 transform 已經被「按住整條縮放」佔用（:has(.tab:active) 那條 transform:scale(1.02)），
          再塞 translateX 會被那條整條覆蓋掉，而 CSS 一個字都不會報錯。
       ⚠ 用 minmax(0, 54px) 不是寫死 54px：極窄機器碰到 max-width 時五格還能一起縮，
          而膠囊的等分算式是跟著 .tabs **實際**寬度算的 ⇒ 縮了仍然逐格對齊。
       ⚠ safe-area 的左右 inset 在直式是 0；藥丸只有 290px，橫放時離邊緣還很遠 ⇒ 這裡用
          固定的 24px 邊距就夠，不必再把 env() 串進 max-width（那個 calc 少數引擎會整條丟掉）。 */
    left: 0;
    right: 0;
    margin-inline: auto;
    width: max-content;
    max-width: calc(100% - 24px);
    /* 2026-09-16：54 -> 66。站長「且太窄了 弄寬」。
       ⚠ 這不是推翻 09-15 那次「間距拉近」——那次治的是「格寬隨螢幕寬線性拉開」，
         鎖成固定值是對的；這次只是把那個固定值調到 LINE 的比例。
       算式：5*66 + 4*2(gap) + 2*6(padding) = 350，對上 LINE 實測的 346。 */
    grid-template-columns: repeat(5, minmax(0, 66px));
    height: var(--qw-mobile-dock-h);
    /* 2026-09-16：站長「位置放低點」。⛔ 原式在 iPhone 上其實是 34px（safe-area 值），
       比 LINE 實測的 26.7px 還**高** —— max(10, 34) 取的永遠是 safe-area 那半。
       改成從 safe-area 扣 10px：iPhone 得 max(14, 24)=24px，貼近 LINE；
       沒有 safe-area 的機器得 14px，仍離得開螢幕邊。 */
    bottom: var(--qw-mobile-dock-bottom);
    gap: 2px;
    /* 2026-09-16 二修：6 -> 3。與 .tab 的高度算式是**同一條**約束：
       按鈕高 = dock 高 - 2*padding。padding 6px 時按鈕只剩 44-6=44... 實際是 50-12=38px，
       低於 Apple HIG 的 44px 觸控下限；收到 3px 才讓按鈕正好 44px。
       ⛔ 改這個值必須同時看 .tab 的 height calc，否則兩者又會各自漂移（本輪的根因就是這個）。 */
    padding: var(--qw-dock-pad);
    overflow: visible;
    border: 0;
    /* = 高的一半 ⇒ 全藥丸。Material 3 FloatingToolbarTokens 的 ContainerShape 也是 CornerFull。
       ⚠ 高度改了這裡要跟著改，否則藥丸會變成圓角矩形。 */
    border-radius: calc(var(--qw-mobile-dock-h) / 2);
    background: transparent;
    box-shadow: none;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    transform-origin: center;
    transition: transform var(--qw-dock-release-duration) var(--qw-dock-spring);
  }
  /* One glass layer, borrowed from the verified TranslationAI mobile dock.
     The nav/text stays opaque and the stars remain visible beneath the tint. */
  body:not(.auth-locked) .app-header .tabs::before {
    content: "";
    position: absolute;
    z-index: -1;
    inset: 0;
    border-radius: inherit;
    pointer-events: none;
    background: color-mix(in srgb, var(--qw-glass-surface, #152337) 92%, transparent);
    border: 1px solid color-mix(in srgb, var(--muted) 45%, transparent);
    /* 2026-09-14：這兩個數字原本是 0 0 24px rgb(0 0 0 / 5%) + inset 白 45%，跟著上面那句
       "borrowed from the TranslationAI mobile dock" 一起搬過來的 —— 而那是一組**亮色**參數。
       深色底上 5% 的黑投影等於沒有（看不出 dock 浮在內容之上），45% 的白內光則亮到像一條高光管。
       ⛔ 這不是主題切換的問題：本專案只有一套深色（全庫 0 個 data-theme／prefers-color-scheme），
          所以沒有任何機制會替它換回來，只能就地改對。 */
    /* 2026-09-16：站長「還要有 邊緣殘影效果」。加的是兩條內緣線＋一層近距投影：
       頂緣高光讓玻璃有厚度、底緣微光收邊、近距投影讓它真的浮在內容之上。
       ⛔ 刻意不用 SVG feDisplacementMap 做「真折射」：WebKit Bug 245510 自 2022 年
          NEW 至今未修，Safari 根本不畫，而 CSS.supports 對它回 true ⇒ 會靜默沒效果。 */
    box-shadow:
      0 10px 28px rgb(0 0 0 / 38%),
      0 2px 6px rgb(0 0 0 / 22%),
      inset 0 1px 0 rgb(255 255 255 / 22%),
      inset 0 -1px 0 rgb(255 255 255 / 8%);
  }
  /* Edge sheen is a paint-only layer. Keep the backdrop blur on ::before alone
     so Safari and low-power mobile GPUs do not sample a nested backdrop root. */
  body:not(.auth-locked) .app-header .tabs::after {
    content: "";
    position: absolute;
    z-index: -1;
    inset: 1px;
    border-radius: inherit;
    pointer-events: none;
    border: 1px solid rgb(230 250 255 / 28%);
    background: linear-gradient(112deg,
      rgb(210 244 255 / 12%) 0%, transparent 27%, transparent 70%,
      rgb(147 222 255 / 10%) 100%);
    box-shadow: inset 2px 2px 4px rgb(255 255 255 / 13%),
      inset -2px -2px 5px rgb(74 170 211 / 12%);
  }
  @supports ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
    body:not(.auth-locked) .app-header .tabs::before {
      /* 單一半透明玻璃層：保留後方色彩，僅模糊細節；不疊亮度濾鏡或整體 opacity。 */
      background: var(--qw-floating-glass-background);
      -webkit-backdrop-filter: var(--qw-floating-glass-filter);
      backdrop-filter: var(--qw-floating-glass-filter);
    }
  }
  body:not(.auth-locked) .app-header .tabs:has(.tab:active) {
    /* 收小（原 1.043）：現在被按那一顆自己會壓下去，整條再放大那麼多就會蓋過它。 */
    transform: scale(1.045);
    transition-duration: var(--qw-dock-press-duration);
  }
  body:not(.auth-locked) .app-header .tab {
    /* ⛔ 2026-09-16 二修：原本是寫死的 58px，而它跟容器的 70px 是**手動對上的**
       （70 - 2*6 = 58）。上一輪把容器改成 50px 時這裡沒跟著改 ⇒ 按鈕比藥丸高 20px、
       配上 overflow:visible 直接戳出玻璃外面，站長看到的「還是差很多」就是這個。
       ⇒ 改成從 --qw-mobile-dock-h 算，兩者從此不可能各自漂移。
       50 - 2*3 = 44px，正好是 Apple HIG 的觸控下限（比原本的 38px 可用高度大）。 */
    height: var(--qw-dock-item-h);
    min-height: 0;
    /* ⛔ 原本是 `28px auto` 兩列（圖示上、文字下）＋ gap:3px。文字藏起來之後那個 auto 列
       會塌成 0、但 gap 還在 ⇒ 內容比可用高度矮 3px 並整體偏上一點點。
       ⇒ 直接收成單列、gap 歸零，圖示由 styles.css 的 place-items:center 置中。 */
    grid-template-rows: 1fr;
    align-content: stretch;
    gap: 0;
    padding: 3px;
    border: 1px solid transparent;
    /* = 按鈕高的一半 ⇒ 全藥丸。⛔ 同樣改成算的，理由同上面那段。 */
    border-radius: calc(var(--qw-dock-item-h) / 2);
    color: var(--text);
    background: transparent;
    box-shadow: none;
    font-size: clamp(13px, 3.2vw, 14.5px);
    font-weight: 600;
    line-height: 1.1;
    letter-spacing: 0;
    -webkit-tap-highlight-color: transparent;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    transition: color 160ms ease, background 160ms ease, border-color 160ms ease;
  }
  body:not(.auth-locked) .app-header .tab svg { width: 26px; height: 26px; }
  /* ⭐ 站長 2026-09-15：「改成 只有圖案 不要文字 只有該頁面 標題有文字」。
     ⛔ 用 display:none **不刪 DOM**：<span> 是 test/web-shell-navigation.test.js 的取材錨點
        （它比對五顆的文字是不是「總覽/工作台/Webhook/API/設定」），刪掉那條測試會紅，
        而且刪了就再也沒有東西釘住「導覽文字與頁內標題一致」。
     ⚠ 無障礙**沒有**退化：五顆 <button> 本來就各帶 aria-label（index.html:107-111），
        而 aria-label 的優先序高於文字內容 ⇒ 讀螢幕軟體念到的名稱一個字都沒變。
     ⚠ 「該頁面標題有文字」的前置已經成立：#dashboard 的 <h1 class="page-heading"> 在 375px
        實測 display:flex、31px、可見（quantum-flight.css:975 只縮 gap，沒有任何隱藏規則）。 */
  body:not(.auth-locked) .app-header .tab span { display: none; }
  /* 沒有文字之後，滑鼠／鍵盤使用者要有別的方式知道那一顆是什麼（窄視窗的桌面瀏覽器與
     桌面殼縮小視窗時都會落進這個斷點）。真手機沒有 hover ⇒ 這層對它是**額外**不是替代，
     手機那邊靠的是頁面標題。
     ⛔ 不用 title 屬性：那要改三個宿主共用的 DOM，而桌面 rail 的文字本來就看得見
        ⇒ 會在 rail 上多出一層重複的原生提示。寫在 media query 裡才是分軌。
     ⛔ display 一定要寫：styles.css:3200 有一條 `.app-header .tab::before, .app-header .tab::after`
        把 display 設成 none，只寫 content 不寫 display 的話這顆泡泡永遠不會出現，而且零報錯。
     ⓘ .tabs 是 overflow:visible、.tab 在下方 (a) 段有 position:relative ⇒ 泡泡浮得出去。 */
  body:not(.auth-locked) .app-header .tab::after {
    content: attr(aria-label);
    display: block;
    position: absolute;
    z-index: 2;
    bottom: calc(100% + 10px);
    left: 50%;
    padding: 5px 10px;
    border-radius: 10px;
    color: var(--text);
    background: rgba(12, 24, 41, .94);
    box-shadow: 0 8px 20px rgb(0 0 0 / 38%);
    font-size: 13px;
    font-weight: 600;
    line-height: 1;
    white-space: nowrap;
    opacity: 0;
    pointer-events: none;
    transform: translateX(-50%);
    transition: opacity 140ms ease;
  }
  body:not(.auth-locked) .app-header .tab:hover::after,
  body:not(.auth-locked) .app-header .tab:focus-visible::after { opacity: 1; }
  /* ⛔ 站長 2026-09-14：「按鈕 按了都要變換」。
     舊版只有**整條 dock** 放大、單顆明確寫 transform:none（註解理由：兩個一起放大會複合）
     ⇒ 結果是按任何一顆畫面都長一樣，使用者看不出自己按到哪一顆 —— 那正是「按了沒變換」。
     ⇒ 改成鍵帽模型：整條的位移收小（1.043→1.02，複合問題消失），**被按那一顆自己壓下去**，
        同時圖示與文字提前亮到選中態的亮度 ⇒ 手指還沒放開就知道按中了哪一格。
     ⚠ hover 維持 transform:none：桌面已用背景色表達 hover，再加位移會讓滑過整排時整條在跳。 */
  body:not(.auth-locked) .app-header .tab:hover { transform: none; }
  /* ⓘ 特異性必須贏過下面那條 :hover：兩者原本同為 (0,4,1)，而 :hover 寫在後面 ⇒ 它贏，
     實測按下時底是 hover 的 6% 而不是這裡宣告的值（滑鼠按住必然同時 hover）。
     多一層 .tabs 變成 (0,5,1) ⇒ 兩邊都贏，⛔ 不是靠行序硬撐。 */
  body:not(.auth-locked) .app-header .tabs .tab:active {
    transform: scale(.88);
    color: var(--text);
    /* ⛔ 這裡先後錯過兩次，兩次的理由都成立、但都只看了一半：
       ① 原本 var(--text) 12%：與選中膠囊的 18% 只差 6 個百分點 ⇒ 像兩顆都被選中。
       ② 改成 transparent：桌面靠 hover 的 6% 仍看得出來，但**真手機沒有 hover**
          ⇒ 按下的唯一訊號只剩縮小 7%，而手指正好蓋住那一顆 ⇒ 等於回到「按了沒反應」。
       ⇒ 現在用**凹陷**：壓暗的底＋內陰影。它與選中態的「提亮底＋外框＋高光」是
          **相反方向**的語言，所以既看得見、又不會被誤讀成選中。
       ⚠ 觸控裝置的 :active 我這輪驗不到（CDP 合成觸控不會設 :active）⇒ 這一項待真機確認。 */
    background: color-mix(in srgb, #000 26%, transparent);
    box-shadow: inset 0 2px 7px rgb(0 0 0 / 38%);
    transition-duration: var(--qw-dock-press-duration);
  }
  body:not(.auth-locked) .app-header .tab:active svg { opacity: 1; }
  body:not(.auth-locked) .app-header .tab:hover {
    background: color-mix(in srgb, var(--text) 6%, transparent);
  }
  body:not(.auth-locked) .app-header .tab.active {
    color: var(--text);
    border-color: color-mix(in srgb, var(--text) 10%, transparent);
    background: color-mix(in srgb, var(--text) 14%, transparent);
    box-shadow: inset 0 1px 0 rgb(255 255 255 / 45%);
  }
  body:not(.auth-locked) .ai-chat-card.is-presentation-fullscreen {
    bottom: calc(var(--qw-mobile-dock-h) + var(--qw-mobile-dock-bottom) + 12px);
    min-height: 0 !important;
  }
  body:not(.auth-locked) main {
    padding-bottom: calc(var(--qw-mobile-dock-h) + 34px + env(safe-area-inset-bottom));
  }
  body:not(.auth-locked)[data-active-panel="internal-browse"] main {
    padding-bottom: calc(var(--qw-mobile-dock-h) + 22px + env(safe-area-inset-bottom));
  }
}

/* ==========================================================================
   滑動膠囊 + 選中態圖示 + 手機頂欄解耦（2026-09-14）
   ⚠ 這個區塊必須留在 prefers-reduced-motion 那塊**之前** —— 後面那塊要負責把這裡新增的
      transition 關掉，順序反了就關不掉（同特異性時後出現的贏）。
   ========================================================================== */

:root {
  /* 曲線沿用本檔既有的 --qw-dock-spring，⛔ 不自創新的時間函式。
     ⚠ 但 duration 不沿用 --qw-dock-release-duration(600ms)：那是 dock 整體縮放「放手後回彈」的時間，
        膠囊是跟著手指走的指示物，600ms 會明顯拖在手指後面。320ms 是同一條曲線下的跟手值。 */
  --qw-tab-indicator-duration: 320ms;
}

/* 膠囊只在手機 dock 啟用。桌面 rail 的選中態是「左側一條 accent 直條」，那是既有的設計語言，
   ⛔ 不在這一輪把它換掉（那是未經授權的視覺改動，且爆炸半徑比手機大）。 */
.app-header .qw-tab-indicator { display: none; }

@media (max-width: 760px) {
  /* ---- (a) 滑動膠囊 ------------------------------------------------------ */
  /* 位置由 CSS 自己算得出來，因為手機 dock 是 grid 五格等寬（repeat(5, minmax(0,1fr))）。
     ⇒ 第一幀就落在正確位置，不需要等 JS 量完，也就不會出現「版面未算完停在左上角」那一幕。
     ⓘ nth-of-type 是刻意的（不是 nth-child）：它只數 <button>，所以在 .tabs 最前面插入的那個
        <span> 指示器**不會**把序號整體推移一格。⛔ 別改成 nth-child，那會讓五顆全部錯位一格。 */
  body:not(.auth-locked) .app-header .tabs { --qw-tab-i: 0; }
  body:not(.auth-locked) .app-header .tabs:has(> .tab:nth-of-type(1).active) { --qw-tab-i: 0; }
  body:not(.auth-locked) .app-header .tabs:has(> .tab:nth-of-type(2).active) { --qw-tab-i: 1; }
  body:not(.auth-locked) .app-header .tabs:has(> .tab:nth-of-type(3).active) { --qw-tab-i: 2; }
  body:not(.auth-locked) .app-header .tabs:has(> .tab:nth-of-type(4).active) { --qw-tab-i: 3; }
  body:not(.auth-locked) .app-header .tabs:has(> .tab:nth-of-type(5).active) { --qw-tab-i: 4; }

  body:not(.auth-locked) .app-header .qw-tab-indicator {
    display: block;
    position: absolute;
    z-index: 0;
    /* 膠囊要跟一格完全等寬等高：寬 = (內容寬 − 4 條 gap) ÷ 5、高 = 內容高。
       ⛔ 2026-09-16：原本這五個值是照 padding:6px 手算出來的常數（6/6/12/12/23）。
          padding 一改它就整條歪掉而完全不報錯 ⇒ 改成從 --qw-dock-pad / --qw-dock-item-h 算。
       ⚠ gap 的 2px 仍是手抄的（上方 .tabs 的 gap）—— 它跟 transform 那行的 2px 是同一個數字，
          兩處要一起改；沒再抽變數是因為 gap 目前沒有第三處在用。 */
    top: var(--qw-dock-pad);
    left: var(--qw-dock-pad);
    width: calc((100% - 2 * var(--qw-dock-pad) - 8px) / 5);
    height: var(--qw-dock-item-h);
    border-radius: calc(var(--qw-dock-item-h) / 2);
    pointer-events: none;
    background: color-mix(in srgb, var(--text) 18%, transparent);
    border: 1px solid color-mix(in srgb, var(--text) 10%, transparent);
    box-shadow: inset 0 1px 0 rgb(255 255 255 / 22%);
    /* translateX 的百分比是對**自己的寬度**解析，而自己的寬度就是一格寬
       ⇒ i * (100% + 2px) 剛好是「第 i 格的左緣」。 */
    transform: translateX(calc(var(--qw-tab-i) * (100% + 2px)));
    transition: transform var(--qw-tab-indicator-duration) var(--qw-dock-spring),
                width var(--qw-tab-indicator-duration) var(--qw-dock-spring);
  }

  /* app.js 的升級掛勾：按鈕若哪天變成不等寬（加第六顆／改成文字自適應寬度），上面那套等分算式就不成立。
     屆時由 app.js 量完寫入 --qw-ind-x / --qw-ind-w 並把 data-ready 翻成 true，CSS 自動改吃量測值。
     ⛔ 在 app.js 真的會寫這兩個變數之前，別把 data-ready 預設改成 true。 */
  body:not(.auth-locked) .app-header .qw-tab-indicator[data-ready="true"] {
    width: var(--qw-ind-w, calc((100% - 2 * var(--qw-dock-pad) - 8px) / 5));
    transform: translateX(var(--qw-ind-x, 0px));
  }

  /* 按鈕要壓在膠囊上面，否則膠囊會蓋住圖示與文字。 */
  body:not(.auth-locked) .app-header .tab { position: relative; z-index: 1; }

  /* 有了會滑的膠囊，選中那顆就不該再自己帶一塊底 —— 兩塊疊起來會看成「卡住的舊位置 + 新位置」。
     ⚠ 包在 @supports selector(:has(*)) 裡是**防呆**不是裝飾：萬一 :has() 不可用，上面那組
        --qw-tab-i 全部算不出來、膠囊會塌在第一格不動，這時若又把 .tab.active 的底拿掉，
        使用者就完全看不出自己在哪一頁。⇒ :has() 不可用時，這條整條不套用，舊的每顆各自亮起來留著。 */
  @supports selector(:has(*)) {
    body:not(.auth-locked) .app-header .tab.active {
      border-color: transparent;
      background: transparent;
      box-shadow: none;
    }
  }

  /* ---- (c) 系統狀態鈕脫離 header ----------------------------------------- */
  /* 那顆鈕是手機上「全部平倉／緊急停機」的唯一入口（面板從它展開）。
     ⚠ 現況它並沒有消失 —— .app-header 是 position:sticky ⇒ 整條頂欄本來就黏在螢幕上。
        真正的風險是**之後**：任何一種「捲動時收合 header」的做法都會連它一起收掉。
     ⇒ 這一步先把它跟 header 解耦（自己 fixed 在安全區內），之後 app.js 不管怎麼收 header，
        這顆鈕都還在原地。⛔ 別把它搬回 header 裡。
     ⓘ 這裡敢用 fixed，是因為上面那條 .app-header{backdrop-filter:none} 已經確保 header 不會
        變成 containing block（有 backdrop-filter 的祖先會把 fixed 子孫拉進自己的座標系）。 */
  body:not(.auth-locked) .app-header .header-status {
    position: fixed;
    z-index: 9400;
    top: max(10px, env(safe-area-inset-top));
    right: max(12px, env(safe-area-inset-right));
  }
  /* Tauri 殼在右上角有自己的視窗控制鈕，原本靠 margin-right:126px 讓開；fixed 之後改用 right 讓。 */
  html[data-host="tauri"] body:not(.auth-locked) .app-header .header-status {
    margin-right: 0;
    right: calc(126px + max(12px, env(safe-area-inset-right)));
  }
  /* ---- (d) 手機頂欄整條收掉 --------------------------------------------- */
  /* ⭐ 2026-09-14，站長第二次講同一句：「UI優化 讓他 上方不要被Logo 狀態佔空間」。
     ⛔ 0.4.33 那一輪只把狀態鈕抽出流（上面 (c) 那段），**垂直空間省下 0px** ——
        頂欄高度是 min-height:56px 撐著的，而狀態鈕只有 40px（比 46px 的 logo 矮）
        ⇒ 抽掉它不會讓頂欄矮一個像素（E1：拿 09-13 備份與現行檔逐字 diff，
        .app-header 的手機規則一個字沒動）。⇒ 站長看到的畫面幾乎零差別。
     ⇒ 這一段才是真的把 57px 還出來：頂欄在網頁版**只剩一顆不可點的 logo**
        （index.html:58 是 <div class="brand"><img>，沒有 <a>、沒有 <button>，
        全庫 grep 也沒有任何 .brand 的點擊處理）＝零功能卻佔 6.8% 的垂直空間。

     ⛔ 絕對不可以對 .app-header 用 display:none —— 底部 dock（.tabs）與狀態鈕
        （.header-status）都是它的 position:fixed **子孫**，display:none 會連它們一起消失。
        高度歸零則不會（已實測 dock 與狀態鈕位置不變）。
     ⛔ 也不可以刪 DOM：test/electron-packaging.test.js:155/:368 釘著
        <header class="app-header"> 裡必須有 class="brand" 與 brand-logo 的 src。
     ⛔ html:not([data-host="tauri"]) 這道前綴不可省：桌面殼在窄視窗時，這條頂欄
        是它唯一的可拖曳表面（-webkit-app-region: drag），收掉＝視窗拖不動。 */
  html:not([data-host="tauri"]) body:not(.auth-locked) .app-header {
    min-height: 0;
    padding: 0;
    border-bottom: 0;
    box-shadow: none;
    background: transparent;
  }
  /* ⭐ 品牌不是消失是搬家 —— 它現在是 dock 第一格（總覽）的圖示，理由見本檔開頭那段。
     ⛔ 前綴 html:not([data-host="tauri"]) 已拿掉：站長 2026-09-14 裁決「頂欄那顆收掉」
        ⇒ 桌面殼窄視窗時也不再有第二顆品牌標，原本擋著不換的理由消失。
     ⓘ 字形切換的兩條規則也一併刪了 —— 總覽現在只有一個 <use>，沒有要切的東西。 */
  body:not(.auth-locked) .app-header .brand { display: none; }
  /* ⭐ 有話要說的時候才把字露出來（站長 2026-09-14：「連線狀態放各分頁右上角」）。
     ⛔ styles.css 在 ≤900px 一律 `.system-status-text{display:none}` ⇒ 那顆鈕只剩一個圓點，
        而圓點在離線與正常時是**同一個字元**（本輪已補 offline:'○' 分開，但字仍是最清楚的）。
     ⚠ 一律顯示也不對：健康時掛一句「系統正常」浮在內容上＝又把上方佔回去了，
        正是站長要我拿掉的東西。⇒ 只在離線／連線中／異常時長出來，好的時候安靜。
     ⓘ 特異性 (0,5,2) 贏過 styles.css 那條 (0,1,0)，⛔ 不靠載入順序硬撐。 */
  html:not([data-host="tauri"]) body:not(.auth-locked) .app-header
    .system-status:is(.offline, .pending, .error) .system-status-text {
    /* ⚠ 寫 block 不是 inline：.system-status 是 inline-flex ⇒ 它的子元素是 flex item，
       而 CSS 規格會把 flex item 的 display **blockify**（寫 inline 也會 computed 成 block）。
       寫 inline 不會壞，但會讓下一個讀 computed 值的人以為規則沒生效。 */
    display: block;
    max-width: 44vw;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  /* 面板是從那顆鈕往下展開的（絕對定位、right:0 貼齊鈕的右緣）。鈕貼到螢幕右邊之後，
     268px 的面板在 375px 寬的機器上還有餘裕，但更窄的機器（320px）會被切掉 ⇒ 給它一個上限。 */
  body:not(.auth-locked) .app-header .system-status-panel {
    max-width: calc(100vw - 24px);
    min-width: 0;
    width: 268px;
  }
}

/* 通知與常駐導覽共用同一玻璃材質；只模糊按鈕背景，圖示與未讀數字維持實色。 */
body:not(.auth-locked) .app-header .system-status {
  background: rgba(8, 16, 29, .92);
}
@supports ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
  body:not(.auth-locked) .app-header .system-status {
    background: var(--qw-floating-glass-background);
    -webkit-backdrop-filter: var(--qw-floating-glass-filter);
    backdrop-filter: var(--qw-floating-glass-filter);
    box-shadow: inset 0 1px 0 rgb(255 255 255 / 22%), 0 4px 12px rgb(0 0 0 / 22%);
  }
}

/* One opaque fallback for reduced transparency; never blur the fixed owner. */
@media (prefers-reduced-transparency: reduce) {
  body:not(.auth-locked) .app-header::before,
  body:not(.auth-locked) .app-header .tabs::before,
  body:not(.auth-locked) .app-header .system-status {
    background: #101b2d;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
}

/* ---- (b) 選中態圖示：填滿 + 挖洞（⛔ 不限手機）--------------------------
   ⛔ 2026-09-16：這一整組原本關在 @media (max-width: 760px) 內 ⇒ **桌面的五顆按鈕
      選中時只有顏色與底色變化、圖示一模一樣**（站長：「桌面版五顆按鈕只有總覽有選中
      圖示變化」—— 總覽之所以看起來有變，只是因為它本來就是四個分離的實心方塊，
      顏色一變就像被填滿了；另外四顆是線條圖示，換個顏色看不出差別）。
   ⭐ 搬出來、⛔ 不複製一份到桌面區塊：複製就是製造第二份要人工同步的幾何，
      而那五張 mask 圖是逐點對齊 <symbol> 的，漂一次就對不回來。
   ⓘ 位置放在所有寬度相關的 @media **之後** ⇒ 平手時它贏，不必靠提高特異性。
   ⚠ 以下原註解逐字保留（它記著 WebKit 內層 mask 的光柵化缺陷與 mask-mode 的取捨）： */
/* ---- (b) 選中態圖示：填滿 + 挖洞 --------------------------------------- */
/* 目標是「大面積塗滿、但每一顆都還認得出是什麼」。
   填滿靠 fill + 加粗 stroke（這五顆圖示有一半的線條是開放路徑，光靠 fill 會整條消失）；
   認得出靠外層 CSS mask 在實心塊上挖回關鍵的那個洞。
   ⛔ 不另畫一套 filled path：那會變成第二份跟 <symbol> 各自漂移的幾何。
   ⛔ 洞畫在**外層 CSS mask**，不畫在 SVG 內層 <mask> —— 實測 WebKit 對內層 mask 會多做一次
      1×CSS px 的光柵化，洞緣在 iPhone 上是階梯狀方塊，而 Chrome 三種寫法都平滑
      ⇒ 只在桌面驗會完全看不到這個缺陷。
   ⚠ mask 圖用「白色＝保留、沒畫到的地方＝挖掉」，刻意**不依賴** mask-mode 的實作差異：
      luminance 下白=1、透明=0；alpha 下白=不透明、透明=0 ⇒ 兩種模式結果一致。
      （mask-mode 仍然照寫，但它壞掉時這套也不會走樣。）
   ⓘ 特異性：styles.css:8020 的 .app-header .tab svg 是 (0,2,1)；下面這條是 (0,4,2)
      且 adaptive-navigation.css 在 styles.css 之後載入 ⇒ 兩邊都贏，⛔ 不是靠載入順序硬撐。 */
body:not(.auth-locked) .app-header .tab.active svg {
  fill: currentColor;
  stroke: currentColor;
  stroke-width: 2.6;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-size: 100% 100%;
  mask-size: 100% 100%;
  mask-mode: luminance;
}
/* 總覽：填滿後就是四個分離的實心方塊，本來就讀得出是儀表板 ⇒ 不挖洞。 */

/* 工作台：實心螢幕下緣挖一條橫縫，讓它讀成「有畫面的螢幕」而不是一塊磚。 */
body:not(.auth-locked) .app-header .tab[data-tab="internal-browse"].active svg {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='white' fill-rule='evenodd' d='M0 0h24v24H0Z M6 13.9h12v1.5H6Z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='white' fill-rule='evenodd' d='M0 0h24v24H0Z M6 13.9h12v1.5H6Z'/%3E%3C/svg%3E");
}
/* Webhook：三個節點各挖一個孔 ⇒ 從三坨實心塊變回三個節點。
   ⛔⛔ 2026-09-15 修：這組洞原本在 (6,6)、(18,6)、(12,18) —— 那是**舊版自畫三圓**的幾何
      （兩顆在上、一顆在下）。圖示換成 Lucide 官方之後節點變成一顆在上、兩顆在下
      ⇒ 洞與節點**上下相反**，三個真節點全是實心、三個洞落在空白處，看起來像缺角瑕疵。
      ⚠ 更該記的是：換圖那次我在 index.html 寫了「那組洞已同步重畫」——**那句是假的**，
         我根本沒動這裡。自稱已修的註解會讓下一個讀的人當場劃掉這個懷疑。
      ⇒ 新座標取自 Lucide `webhook` 三段弧的圓心：上中 (12,5)、左下 (6,17)、右下 (18,17)。 */
body:not(.auth-locked) .app-header .tab[data-tab="cloud"].active svg {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='white' fill-rule='evenodd' d='M0 0h24v24H0Z M12 3.4a1.6 1.6 0 1 0 0 3.2a1.6 1.6 0 1 0 0-3.2Z M6 15.4a1.6 1.6 0 1 0 0 3.2a1.6 1.6 0 1 0 0-3.2Z M18 15.4a1.6 1.6 0 1 0 0 3.2a1.6 1.6 0 1 0 0-3.2Z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='white' fill-rule='evenodd' d='M0 0h24v24H0Z M12 3.4a1.6 1.6 0 1 0 0 3.2a1.6 1.6 0 1 0 0-3.2Z M6 15.4a1.6 1.6 0 1 0 0 3.2a1.6 1.6 0 1 0 0-3.2Z M18 15.4a1.6 1.6 0 1 0 0 3.2a1.6 1.6 0 1 0 0-3.2Z'/%3E%3C/svg%3E");
}
/* API：圓心挖成鑰匙孔 ⇒ 實心圓 + 齒讀回一把鑰匙。 */
body:not(.auth-locked) .app-header .tab[data-tab="api"].active svg {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='white' fill-rule='evenodd' d='M0 0h24v24H0Z M8 10.2a1.8 1.8 0 1 0 0 3.6a1.8 1.8 0 1 0 0-3.6Z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='white' fill-rule='evenodd' d='M0 0h24v24H0Z M8 10.2a1.8 1.8 0 1 0 0 3.6a1.8 1.8 0 1 0 0-3.6Z'/%3E%3C/svg%3E");
}
/* 設定：這一顆**非挖不可**。齒輪外框與中心圓是兩個獨立元素，一起填滿會糊成一坨，
   中心挖回 r=3.2 才重新讀得出是齒輪。 */
body:not(.auth-locked) .app-header .tab[data-tab="settings"].active svg {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='white' fill-rule='evenodd' d='M0 0h24v24H0Z M12 8.8a3.2 3.2 0 1 0 0 6.4a3.2 3.2 0 1 0 0-6.4Z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='white' fill-rule='evenodd' d='M0 0h24v24H0Z M12 8.8a3.2 3.2 0 1 0 0 6.4a3.2 3.2 0 1 0 0-6.4Z'/%3E%3C/svg%3E");
}


@media (prefers-reduced-motion: reduce) {
  /* 膠囊的位移也要關掉 —— 前庭不適的人看到的是一塊在螢幕上滑來滑去的實體。
     ⚠ 只關 transition 不關 transform：位置還是要對，只是瞬間到位。 */
  body:not(.auth-locked) .app-header .qw-tab-indicator { transition: none; }
  body:not(.auth-locked) .app-header .tabs,
  body:not(.auth-locked) .app-header .tabs:has(.tab:active) {
    transform: none;
    transition: none;
  }
  body:not(.auth-locked) .app-header .tab { transition: none; }
  body:not(.auth-locked) .app-header .tab:hover,
  body:not(.auth-locked) .app-header .tabs .tab:active { transform: none; }
  /* 手機 icon-only 的名稱泡泡：⚠ 只關它的淡入 transition，⛔ 不關 opacity 本身 ——
     關掉 opacity 規則的話，前庭不適的使用者就再也看不到那一顆是什麼。
     ⓘ 泡泡的 translateX(-50%) 是**定位**不是動畫，同理不能歸零（歸零會整顆往右偏半個寬度）。 */
  body:not(.auth-locked) .app-header .tab::after { transition: none; }
}

/* 桌面／網頁共用 compact rail。滑鼠離開即收起；鍵盤 Tab 焦點仍可展開。 */
@media (min-width: 761px) {
  :root {
    --qw-desktop-rail-w: 68px;
    --qw-desktop-rail-expanded-w: 236px;
    --qw-desktop-rail-content-inset: 16px;
  }
  body:not(.auth-locked) .app-header {
    width: var(--qw-desktop-rail-w);
    padding-inline: 8px;
    overflow: visible;
    background: transparent;
    transition: width 150ms ease;
  }
  /* 點擊導覽按鈕留下的一般焦點不可讓 rail 常駐展開；只有鍵盤焦點保留。 */
  body:not(.auth-locked) .app-header:is(:hover:not(:has(.window-controls:hover, .system-status-panel:hover)), :has(.system-status-panel:not([hidden]))),
  body:not(.auth-locked) .app-header:has(:focus-visible):not(:has(.window-controls:is(:hover, :focus-within))) {
    width: var(--qw-desktop-rail-expanded-w);
    box-shadow: 12px 0 32px rgba(0, 0, 0, .42);
  }
  body:not(.auth-locked) .app-header .brand { justify-content: center; padding-inline: 0; }
  body:not(.auth-locked) .app-header .brand-text,
  body:not(.auth-locked) .app-header .tab span,
  body:not(.auth-locked) .app-header .system-status-text { display: none; }
  body:not(.auth-locked) .app-header .brand-text,
  body:not(.auth-locked) .app-header .tab span,
  body:not(.auth-locked) .app-header .system-status-text {
    white-space: nowrap;
    overflow: hidden;
  }
  body:not(.auth-locked) .app-header .tabs { align-items: center; }
  body:not(.auth-locked) .app-header .tab {
    grid-template-columns: 28px;
    justify-content: center;
    justify-items: center;
    width: 52px;
    padding-inline: 0;
  }
  body:not(.auth-locked) .app-header .header-status { align-items: center; }
  body:not(.auth-locked) .app-header .system-status,
  body:not(.auth-locked) .app-header .header-update-badge {
    justify-content: center;
    width: 52px;
    padding-inline: 0;
    gap: 0;
  }
  body:not(.auth-locked) .app-header .header-update-badge { font-size: 0; }
  body:not(.auth-locked) .app-header:is(:hover:not(:has(.window-controls:hover, .system-status-panel:hover)), :has(.system-status-panel:not([hidden]))) .brand,
  body:not(.auth-locked) .app-header:has(:focus-visible):not(:has(.window-controls:is(:hover, :focus-within))) .brand {
    justify-content: flex-start;
    /* Keep the entire brand aligned with the expanded navigation content. */
    padding-inline: var(--qw-desktop-rail-content-inset) 0;
  }
  body:not(.auth-locked) .app-header:is(:hover:not(:has(.window-controls:hover, .system-status-panel:hover)), :has(.system-status-panel:not([hidden]))) .brand-text,
  body:not(.auth-locked) .app-header:has(:focus-visible):not(:has(.window-controls:is(:hover, :focus-within))) .brand-text,
  body:not(.auth-locked) .app-header:is(:hover:not(:has(.window-controls:hover, .system-status-panel:hover)), :has(.system-status-panel:not([hidden]))) .tab span,
  body:not(.auth-locked) .app-header:has(:focus-visible):not(:has(.window-controls:is(:hover, :focus-within))) .tab span,
  body:not(.auth-locked) .app-header:is(:hover:not(:has(.window-controls:hover, .system-status-panel:hover)), :has(.system-status-panel:not([hidden]))) .system-status-text,
  body:not(.auth-locked) .app-header:has(:focus-visible):not(:has(.window-controls:is(:hover, :focus-within))) .system-status-text { display: block; }
  /* Rail 寬度仍在展開時，標籤先保持隱藏；避免短暫擠成直排。 */
  body:not(.auth-locked) .app-header:is(:hover:not(:has(.window-controls:hover, .system-status-panel:hover)), :has(.system-status-panel:not([hidden]))) :is(.brand-text, .tab span, .system-status-text),
  body:not(.auth-locked) .app-header:has(:focus-visible):not(:has(.window-controls:is(:hover, :focus-within))) :is(.brand-text, .tab span, .system-status-text) {
    animation: qw-rail-label-reveal 1ms 150ms both;
  }
  body:not(.auth-locked) .app-header:is(:hover:not(:has(.window-controls:hover, .system-status-panel:hover)), :has(.system-status-panel:not([hidden]))) .tabs,
  body:not(.auth-locked) .app-header:has(:focus-visible):not(:has(.window-controls:is(:hover, :focus-within))) .tabs { align-items: stretch; }
  body:not(.auth-locked) .app-header:is(:hover:not(:has(.window-controls:hover, .system-status-panel:hover)), :has(.system-status-panel:not([hidden]))) .tab,
  body:not(.auth-locked) .app-header:has(:focus-visible):not(:has(.window-controls:is(:hover, :focus-within))) .tab {
    grid-template-columns: 28px minmax(0, 1fr);
    justify-content: start;
    justify-items: start;
    width: 100%;
    padding-inline: var(--qw-desktop-rail-content-inset);
  }
  body:not(.auth-locked) .app-header:is(:hover:not(:has(.window-controls:hover, .system-status-panel:hover)), :has(.system-status-panel:not([hidden]))) .header-status,
  body:not(.auth-locked) .app-header:has(:focus-visible):not(:has(.window-controls:is(:hover, :focus-within))) .header-status { align-items: stretch; }
  body:not(.auth-locked) .app-header:is(:hover:not(:has(.window-controls:hover, .system-status-panel:hover)), :has(.system-status-panel:not([hidden]))) .system-status,
  body:not(.auth-locked) .app-header:has(:focus-visible):not(:has(.window-controls:is(:hover, :focus-within))) .system-status,
  body:not(.auth-locked) .app-header:is(:hover:not(:has(.window-controls:hover, .system-status-panel:hover)), :has(.system-status-panel:not([hidden]))) .header-update-badge,
  body:not(.auth-locked) .app-header:has(:focus-visible):not(:has(.window-controls:is(:hover, :focus-within))) .header-update-badge {
    justify-content: flex-start;
    width: 100%;
    padding-inline: var(--qw-desktop-rail-content-inset);
    font-size: 15px;
    gap: 10px;
  }
}
@keyframes qw-rail-label-reveal {
  from { visibility: hidden; }
  to { visibility: visible; }
}
@media (min-width: 761px) and (max-width: 1100px) {
  :root { --qw-desktop-rail-expanded-w: 204px; }
}
@media (prefers-reduced-motion: reduce) {
  body:not(.auth-locked) .app-header { transition: none; }
  body:not(.auth-locked) .app-header :is(.brand-text, .tab span, .system-status-text) { animation: none; }
}

/* Fullscreen retains the same compact navigation space as the normal shell.
   Keep top/height owned by the existing host titlebar and mobile rotation rules. */
@media (min-width: 761px) {
  html[data-host] body:not(.auth-locked).workbench-fullscreen-active :is(
    .workbench-stage.is-single-fullscreen,
    .wb-focus-pane.is-fullscreen,
    .wb-grid-tile.is-fullscreen
  ) {
    left: var(--qw-desktop-rail-w);
    right: 0;
    width: calc(100% - var(--qw-desktop-rail-w));
  }
}

/* ── 2026-10-18 站長：手機常駐列 ─────────────────────────────────────────
   ① Threads／Instagram 式：往下捲隱藏、往上捲出現（app.js mobileDockAutoHide 切 body.qw-dock-hidden）。
      用獨立的 translate 屬性，⛔ 不動 transform（它被「按住整條縮放」佔用，見 .tabs 註解）。
   ② 玻璃更透明（後方文字看得到）＋邊緣一條完全不透明的實線（iOS 26／Threads 主流作法：
      ⭐ 2026-10-19 站長「還不夠透明」：填色 22%/18% → 10%/8%、模糊 8px → 3px（模糊才是擋住後方字的主因），描邊維持實色。
      半透明填色＋清楚的實色描邊，讓輪廓在任何背景上都讀得出來）。 */
@media (max-width: 760px) {
  body:not(.auth-locked) .app-header .tabs {
    transition: transform var(--qw-dock-release-duration) var(--qw-dock-spring), translate .28s cubic-bezier(.2, .8, .2, 1);
  }
  body.qw-dock-hidden:not(.auth-locked) .app-header .tabs {
    translate: 0 calc(100% + var(--qw-mobile-dock-bottom) + 20px);
  }
  body:not(.auth-locked) .app-header .tabs::before {
    background: color-mix(in srgb, var(--qw-glass-surface, #152337) 10%, transparent);
    border: 1px solid #4b568f;
  }
  body:not(.auth-locked) .app-header .tabs::after { border-color: transparent; }
  @supports ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
    body:not(.auth-locked) .app-header .tabs::before {
      background: color-mix(in srgb, var(--qw-glass-surface, #152337) 8%, transparent);
      -webkit-backdrop-filter: blur(3px) saturate(140%);
      backdrop-filter: blur(3px) saturate(140%);
    }
  }
}
@media (max-width: 760px) and (prefers-reduced-motion: reduce) {
  body:not(.auth-locked) .app-header .tabs { transition: none; }
}
