﻿/* ────────────────────────────────────────────────────────────────────────────
   編輯器的樣式。
   ──────────────────────────────────────────────────────────────────────────── */

  :root{
    --bg:#0d1a1b; --bg2:#122325; --panel:#16292b; --panel2:#1a3032;
    --line:#23403f; --line-soft:#1b3234;
    --txt:#e4efec; --dim:#7f9b98; --dimmer:#547370;
    --t1:#e0ae5a; --t2:#57b6a4; --t3:#b487c9;
    --t4:#7aa5e0; --t5:#e0788f; --t6:#a3c464;
    --warn:#d97a72;
    /* 「這顆是選中的」的底色。要夠深，白字才讀得出來（白字配 --t1 的金色只有
       1.9:1，完全不能用）。工具模式、音符長度、捲軸同步共用同一套語言。 */
    --sel:#2b6b60;
    --mono:"IBM Plex Mono",ui-monospace,"SFMono-Regular",Consolas,monospace;
    --display:"Gloock",Georgia,serif;
    /* 三條 lane 共用的那兩條捲軸有多寬。**開站時由 JS 量出來覆蓋**（見 ui.measureScrollbar），
       12px 只是量不到時的退路。這個值同時是「捲軸欄的寬度」與「原生捲軸的寬度」。 */
    --sbw:12px;
  }
  *{box-sizing:border-box}
  html,body{height:100%}
  body{
    margin:0; background:var(--bg); color:var(--txt);
    font-family:var(--sans); font-size:14px; line-height:1.6;
    display:flex; flex-direction:column;
    -webkit-font-smoothing:antialiased;
  }
  button,select,input,textarea{font:inherit;color:inherit}
  /*
    hidden 要真的有效。瀏覽器內建的 `[hidden]{display:none}` 是 UA 樣式，**任何**作者寫的
    display 都贏過它 —— 而這份 CSS 裡到處都是 `.hint{display:block}`。表現是「設了 el.hidden
    但東西還在畫面上」，而且完全不報錯。
  */
  [hidden]{display:none!important}
  ::selection{background:rgba(224,174,90,.3)}
  /* 樂譜文字區的選取要更明顯：點捲軸上的音符會把游標跳到對應的那一段，
     而那常常只有 "e4" 兩個字元 —— 30% 的底色根本看不到。 */
  textarea::selection{background:rgba(224,174,90,.6);color:#14201a}

  /* ---------- top bar ---------- */
  /* position:relative 是給 #navMenu 用的 —— 手機上它會絕對定位成一個下拉面板，
     定位脈絡必須是標題列本身（不然它會跑到頁面左上角）。 */
  /*
    `align-items:center`。**曾經是 `baseline`，那是一個沒有對象的基線** —— 標題以外的每一個
    子元素都自己寫著 `align-self:center`，於是 `h1` 是唯一參與基線對齊的項目。
  */
  header{
    position:relative;
    display:flex; align-items:center; gap:16px; flex-wrap:wrap;
    padding:14px 22px; border-bottom:1px solid var(--line);
    background:linear-gradient(180deg,var(--bg2),var(--bg));
  }
  h1{
    margin:0; font-family:var(--display); font-weight:400;
    font-size:26px; letter-spacing:.01em; line-height:1;
    color:var(--txt);
  }
  h1 em{font-style:normal;color:var(--t1)}
  /* 標題是連結但不要看起來像連結（沒有底線、沒有藍色），只在滑過時給一點回饋 */
  h1 a{color:inherit;text-decoration:none;transition:opacity .15s}
  h1 a:hover{opacity:.8}
  h1 a:focus-visible{outline:2px solid var(--t1);outline-offset:3px;border-radius:2px}
  .sub{color:var(--dimmer);font-size:12px;letter-spacing:.08em;text-transform:uppercase}

  /*
    以 app 形式開啟時整個 h1 收掉。條件跟 manifest 的 display_override 一組（standalone +
    minimal-ui），**兩個都要列**：只寫 standalone 的話，落到 minimal-ui 的那些視窗會變成
    「有視窗標題、頁內也還有標題」。

    `display:none` 而不是視覺隱藏：h1 裡面是一個連結，留在無障礙樹裡就等於留一個看不見卻能
    Tab 到的連結。SEO 不受影響：爬蟲一律以 browser 模式取頁。

     iOS 16.4 以前只認 navigator.standalone、不回報 display-mode，那些裝置上標題會留著。
  */
  @media (display-mode: standalone), (display-mode: minimal-ui) {
    h1 { display: none; }
  }
  @media (max-width: 576px) {
    .sub { display: none;}
    h1 {font-size:18px;}

                #status .legal {
                    display: none;
                }
                #status .engine {
                    display: none;
                }
  }
  header .spacer{flex:1}
  #gear,#share,#clip,#file,#aboutBtn{
    display:inline-flex; align-items:center; gap:7px; align-self:center;
    background:none; border:1px solid var(--line); border-radius:3px;
    color:var(--dim); cursor:pointer; padding:5px 11px; font-size:12px;
  }
  #gear:hover,#clip:hover,#file:hover,#share:hover,#aboutBtn:hover{border-color:var(--t2);color:var(--txt)}
  /* #share 以前會因為「沒音符」而暗掉，現在一律可按 —— 那個判斷搬進框裡了 */

  /*
    剪貼簿／木檔案／分享這一組（markup 在 Index.cshtml 的 #navMenu 裡）。**平常等於不存在**：
    `display:contents` 讓三顆按鈕直接當 `#navMenu` 的 flex 項目 —— 手機面板尤其需要這條。
  */
  #navIo{display:contents}
  /*
    只有 **app 視窗、而且是滑鼠尺寸的視窗**，這一組才搬到標題列左邊 —— 上面那條規則在 app
    視窗裡把 h1 收掉了，左半於是整片空著。

    絕對定位而不是塞回 flex 流程：這一組要對齊的是 **header 的內容左緣**（`padding-left` 的
    22px），而且不佔版面就不會參與 `flex-wrap`。垂直置中寫死 `top:50%` + `translateY(-50%)`，
    不靠「絕對定位子元素的靜態位置會繼承 flex 容器的 align-items」—— 那件事各家實作參差。

     **三個條件，三個獨立的理由：** display-mode（standalone 與 minimal-ui 都要列）、
    min-width:577px、min-height:481px。 手機斷點的 576／480 一改，這兩個數字就要跟著改。
  */
  @media (display-mode: standalone) and (min-width: 577px) and (min-height: 481px),
         (display-mode: minimal-ui) and (min-width: 577px) and (min-height: 481px) {
    #navIo{
      display:flex; align-items:center; flex-wrap:nowrap; gap:16px;
      position:absolute; left:22px; top:50%; transform:translateY(-50%);
    }
  }

  /* ---------- 標題列上的漢堡鈕與三個顯示開關 ---------- */
  /*
    **兩者預設都不存在**，只在手機斷點才出現。寫在這裡而不是只寫在斷點裡：規則的「關閉」狀態
    要有個家。28px 正方形，跟 `.panebar .iconbtn` 與 `#rollbar` 那組按鈕同一個基準。
  */
  header .ico{
    display:none; align-items:center; justify-content:center; align-self:center;
    width:28px; height:28px; padding:0; box-sizing:border-box; flex:0 0 auto;
    background:none; border:1px solid var(--line); border-radius:3px;
    color:var(--dim); cursor:pointer;
    transition:color .15s,background .15s,border-color .15s;
  }
  header .ico i{font-size:13px}
  header .ico:not(.on):hover{border-color:var(--t2);color:var(--txt)}
  /* 「開者」的樣式跟全站一致（工具模式、音符長度、循環、每軌靜音共用 --sel） */
  header .ico.toggle.on{background:var(--sel);border-color:var(--sel);color:#fff}
  header .ico.toggle.on:hover{background:#347d70;border-color:#347d70}
  /* 三顆貼著標題，所以它自己是一個小 flex，不吃 header 的 16px gap */
  #viewToggles{display:none; align-items:center; gap:6px; align-self:center}

  /* ---------- 手機版面 ----------
     一個斷點、一整組規則，**整個功能收在這一塊裡** —— PC 寬螢幕一條都套不到。

      **兩個條件，兩個獨立的理由**：

       max-width:576   標題列擠不擠得下。
       max-height:480  **手機橫放**。它是寬的（844）卻是最矮的（390），純寬度的斷點完全抓不
                       到。順帶涵蓋「PC 把視窗拉得很扁」。

     ── 576 是量出來的 ──

     `.grp` 是 `flex-wrap:nowrap`，所以橫排那一列**要嘛整條擺得下、要嘛讓整頁橫向捲動**。那
     一刻是**語言決定的**：

       繁中 448 ／ 韓文 499 ／ 英文 503 ／ **日文 556**（クリップボード 最長）

     556 + 20px（網頁字型載不到、退回系統字體的餘裕）= 576。

      **以後在 #navMenu 加鈕、或改任何一顆鈕的標籤，都要回來重量一次。**
  */
  @media (max-width: 576px), (max-height: 480px) {
    /* 上下內距從 14px 收到 8px。手機上標題列是**純導覽**（標題 + 四個開關 + 漢堡），
       沒有內容要呼吸，而省下的 12px 直接進捲軸。左右維持 22px：要省的是垂直空間。 */
    header{padding:8px 22px}

    /* 容器與按鈕都要開 —— `header .ico` 的 display:none 掛在按鈕自己身上，
       只把容器打開的話裡面還是空的。 */
    #navToggle,
    #viewToggles .ico{display:inline-flex}
    #viewToggles{display:flex}

    /*
      四顆選單鈕變成下拉面板。**沿用同一個 .grp**，不另外做一個面板。絕對定位而不是把內容往
      下推：手機的垂直空間就是這整件事要省的東西。定位脈絡是 `header{position:relative}`。
    */
    #navMenu{
      display:none; position:absolute; top:100%; right:12px; z-index:65;
      flex-direction:column; align-items:stretch; gap:8px;
      margin-top:4px; padding:8px; min-width:168px;
      background:var(--panel); border:1px solid var(--line); border-radius:3px;
      box-shadow:0 18px 40px rgba(0,0,0,.55);
    }
    header.navopen #navMenu{display:flex}
    /*
      `align-self:stretch` 是必要的：那四顆自己寫著 `align-self:center`，在直排的面板裡那會讓
      它們縮成內容寬度。尺寸放大到 `padding:9px 11px` 配 14px。

       這一段的斷點有 `max-height:480px` 那一條，所以「PC 把視窗拉得很扁」時也會拿到手指
      尺寸的按鈕。
    */
    #navMenu button{
      align-self:stretch; justify-content:flex-start;
      padding:9px 12px; font-size:14px; gap:9px;
    }

    /*
      ── 淡出／淡入 ──

      160ms 的淡出 + 往自己那一側滑 6px，讓「是這一塊走了」看得出來。

      **只有工具列與狀態列做。** 鋼琴軸與編輯器那兩塊的 `main{flex:…!important}` 是瞬間生效
      的，淡出的那 160ms 裡 `main` 已經換了 flex —— 會看到「先跳一半 → 才淡掉 → 再跳一次」。

      **`display` 也在轉場清單裡**（`transition-behavior:allow-discrete`）：不然 `display:
      none` 會在第一幀就生效、`opacity` 沒有機會跑。舊瀏覽器認不得它，行為退回瞬間切換。

      `transition-behavior` 寫成獨立宣告而不是塞進 `transition` 簡寫：簡寫裡出現不認識的關鍵
      字會讓**整條**宣告被丟掉。
    */
    body.anim #rollbar .hideable,
    body.anim #status{
      transition:opacity .16s ease, transform .16s ease, display .16s;
      transition-behavior:allow-discrete;
    }
    /* 進場的起點。**掛在 `body.anim` 之下**是為了擋掉開站時的那一次 —— `@starting-style` 對
       「元素第一次被算樣式」也會生效。`.anim` 由 JS 在第一幀之後才掛（見 ui.initHeaderNav）。 */
    @starting-style{
      body.anim #rollbar .hideable{opacity:0;transform:translateY(-6px)}
      body.anim #status{opacity:0;transform:translateY(6px)}
    }

    /* ── 開關 1：工具列 ──
       播放組留著（`.hideable` 只掛在後三組與提示上）。`.kbd` 走同一組規則；兩個位元分開記，
       打字時自動收起來不會把使用者手動按的那一位元弄髒。 */
    body:is(.no-tools,.kbd) #rollbar .hideable{
      display:none; opacity:0; transform:translateY(-6px);
    }

    /* ── 開關 2：鋼琴軸（只給使用者手動按的那顆） ── */
    body.no-roll #stage{display:none}
    body.no-roll #splitter{display:none}
    /* `main` 要跟著改成 `flex:1 1 auto`：`#stage` 是唯一 `flex:1` 的那個，藏掉之後
       沒有人會去吃那塊空間，畫面下半會整片空著。 */
    body.no-roll main{flex:1 1 auto!important}

    /* ── `.kbd`：軟鍵盤頂上來時讓出高度，但**捲軸留著** ──
       （由 ui.initKeyboardHide 的 focusin/focusout 掛上與拿掉。）

       打字的人**要一邊看捲軸上的結果**，所以收掉的只有「打字時真的用不到」的三條：工具列後
       三組（`.hideable`）、每個分頁的樂器列（`.panebar`）、狀態列（`#status`），加起來約
       100px。**播放組、分頁列與捲軸全部留著。** `main` 這裡**不動**：`#stage` 還在，它仍然
       是那個 `flex:1`。

        **`.kbd` 的斷點被 JS 抄了一份**（`config.COMPACT_MQ`，給 `tracks.selectTrack` 當
       「要不要把焦點搶進文字區」的閘門）。改這組數字要兩邊一起改。 */
    body.kbd .panebar{display:none}

    /* ── 開關 3：編輯器 ──
       **`!important` 是必要的，不是偷懶**：`main` 的高度是 `#splitter` 寫進 inline
       `style.flexBasis` 的，而 author stylesheet 的 !important 壓得過普通的 inline 宣告。
       class 一移除，inline 值就自己接回去。

       分隔線一起藏：拖曳結束會把 `main.offsetHeight` 存進 localStorage，那時量到的是「沒有
       文字區」的高度。

       樂器列（`.panebar`）**留著**：上面的「顯示／只看這一份」直接控制捲軸畫哪幾軌。 */
    body.no-editor .lanetabs,
    body.no-editor .lanes{display:none}
    body.no-editor main{flex:0 0 auto!important}
    body.no-editor #splitter{display:none}

    /* ── 開關 4：狀態列 ──
       `.kbd` 同上。它的高度直接進捲軸，一走 `minMainH()` 也跟著縮（那個下限就是分頁列 + 狀態
       列量出來的）。 */
    body:is(.no-status,.kbd) #status{
      display:none; opacity:0; transform:translateY(6px);
    }

    /* 會動的東西一律問一次。這裡尤其該問：這兩塊是**資訊**不是裝飾，
       而使用者按那顆開關的意圖是「現在就讓它不見」。 */
    @media (prefers-reduced-motion:reduce){
      body.anim #rollbar .hideable,
      body.anim #status{transition:none}
    }

    /* 狀態列本身還要再擠一點，但那條規則**不能放在這裡** —— 它跟 `.status` 的基礎規則同
       specificity，而基礎規則在這個檔案的後面。（往下找「再擠一點」那一段。） */
  }

  /* 只給輔助技術看的文字。圖示按鈕還是要有名字，不然螢幕閱讀器只會唸出空白。 */
  .sr{
    position:absolute; width:1px; height:1px; overflow:hidden;
    clip-path:inset(50%); white-space:nowrap;
  }

  /* ---------- 鋼琴捲軸 ---------- */
  /* 捲軸吃掉剩下的高度，<main> 固定 300 —— 4K@150% 之下捲軸大約 1000px，
     84 列剛好全部看得到。水平捲軸是必要的，曲子都很長。 */
  /* 工具列分三組（播放／編輯／狀態）＋ 右邊的操作提示，窄視窗時整組換行。
     高度不能寫死 —— 換行要長高，而 #stage 是 flex:1 1 auto 會把差額吸收掉。flex:0 0 auto 是
     必要的：預設的 flex-shrink:1 會在垂直空間不夠時把工具列壓扁。 */
  #rollbar{
    --btnh:28px;
    display:flex; align-items:center; flex-wrap:wrap;
    gap:6px 20px;              /* 換行後的列間 6px；組間 20px（原本是一根 .sep 細線） */
    flex:0 0 auto; min-height:36px; box-sizing:border-box;
    padding:4px 14px; border-bottom:1px solid var(--line); background:var(--bg2);
  }
  /* 一組。組內不換行 —— 理由見 HTML 那邊的說明（flex-wrap 只會在子元素之間斷，
     所以「保證一組不被拆開」只能靠容器，CSS 沒有別的辦水法）。 */
  .grp{display:flex; align-items:center; flex-wrap:nowrap; flex:0 0 auto}
  #rollbar .grp{gap:8px}
  /* header 的 align-items 是 baseline，所以整組要自己置中（原本是每顆按鈕各自
     align-self:center，現在包起來了，那個責任上移到組身上）。 */
  header .grp{gap:16px; align-self:center}
  /* 工具列裡的按鈕高度一律相同。五組（.play / .tiny / .tool / #lens / .seg）若不明訂
     高度就由內容的 line box 決定 —— 裝 FontAwesome glyph 的 #toolSelect 因此比裝等寬數字的
     音符長度鈕高一截。訂死高度 + flex 置中，高度從此與內容無關。 */
  #rollbar button{
    display:inline-flex; align-items:center; justify-content:center;
    height:var(--btnh); box-sizing:border-box; flex:0 0 auto;
  }
  #rollbar .lbl{
    font-size:11px; letter-spacing:.12em; text-transform:uppercase; color:var(--dimmer);
  }
  /*
    音符長度那顆圖示 —— **它是 <button>，但刻意沒有按鈕的框。**

    #lens 那六顆是「互斥單選」，用邊框 + 選中底色表達；這一顆是「切換」（附點的開關），
    用**圖形本身**表達 —— 附點就畫在符號上。給它一個框會讓兩種語意看起來是同一種，而
    那正是這裡要避免的。語意層由 aria-pressed 負責，不靠外觀。

    高度／inline-flex 由上面 `#rollbar button` 那條給，所以它跟工具列其他按鈕自動齊平。
  */
  #lensLbl{
    background:none; border:0; padding:0px;
    color:var(--dimmer); cursor:pointer; transition:color .15s;
  }
  /*
    svg 是 14×18。**字要變大靠的是裁掉符號自己的留白**（`_NoteIcons` 的 viewBox 收成
    筆畫的聯集邊界 `4.4 1.2 18.2 24`），不是把這個框放大 —— 放大過一次，實測退回來了：
    那一格變得比旁邊的數字鈕還搶眼，而它只是一個狀態顯示。

    **寬度 14 是算出來的**：筆畫的長寬比是 18.2/24 = 0.758，框比它胖的話 `meet` 會卡在
    高度上、寬度那邊一定剩白。14/18 = 0.778 剛好貼著那個比例，於是字畫成 13.65 × 18，
    兩個方向都吃滿。改高度的話寬度要跟著重算。
  */
  #lensLbl svg{display:block;width:14px;height:18px}
  #lensLbl > i{display:none}
  #lensLbl:not(:disabled):hover{color:#fff}
  /*
    只選取模式：換成一圈虛線框 + 通用的 ♫，按鈕 disabled。

    **寬度要跟 svg 一模一樣（14px），高度不必** —— 會讓工具列抖的只有寬度，垂直方向
    由 `#rollbar button` 的 `height:var(--btnh)` 定死了，兩種形態都撐不開它。所以這個框
    可以拉到 `--btnh` 跟 `#lens` 那六顆一樣高（圓角也一樣 3px），看起來就是同一排東西
    的一格，而 svg 保持 16×18 不動。
    虛線的節奏對齊畫布上那個 `setLineDash([3,3])`（見 pianoroll.dashedBox）。
  */
  #lensLbl:disabled{color:var(--dimmer);cursor:default}
  #lensLbl.sel svg{display:none}
  #lensLbl.sel > i{
    display:flex; align-items:center; justify-content:center; box-sizing:border-box;
    width:14px; height:var(--btnh); font-size:10px; line-height:1;
    border:1px dashed currentColor; border-radius:3px;
  }
  /*
    手機上把音符長度那六顆的 ♪ 標籤藏掉。**沿用標題列那個 576px，而且這次不是巧合**。

     **省下來的只有大約 20px** —— 這一條不治「#rollbar 的橫向溢出」。用 id 而不是
    `#rollbar .lbl`：#rollMode 也是 `.lbl`，而它是狀態字。
  */
  @media (max-width: 576px) {
    #lensLbl{display:none}
  }
  /* 播放中：跟導播線同一個金色，一眼看得出「現在是在放，不是在編」 */
  #rollMode.playing{color:var(--t1)}

  /* 工具模式（箭頭）是獨立一顆，音符長度六顆連成一段。
     兩者共用「選中」的樣式，因為它們互斥 —— 同一時間只有一個生效。 */
  #lens{display:flex;gap:0}
  #lens button,#rollbar button.tool{
    width:30px; padding:4px 0; background:var(--panel); border:1px solid var(--line);
    color:var(--dimmer); font-family:var(--mono); font-size:11px;
    cursor:pointer; flex:0 0 auto; transition:color .15s,background .15s;
  }
  #rollbar button.tool{width:30px;font-size:12px;border-radius:3px}
  /* 六顆連成一段：共用邊框，只有最後一顆畫右邊 */
  #lens button{border-right-width:0}
  #lens button:first-child{border-radius:3px 0 0 3px}
  #lens button:last-child{border-right-width:1px;border-radius:0 3px 3px 0}
  /* :not(.on) 是必要的：`:hover` 那條的 specificity 比 `.on` 那條高（多一個
     pseudo-class），不排除的話滑鼠停在選中的按鈕上時白字會被改回 --txt。 */
  #lens button:not(:disabled):not(.on):hover,
  #rollbar button.tool:not(:disabled):not(.on):hover{color:var(--txt)}

  /* 選中：底色 + 白字。不能只靠邊框 —— 共邊的模型下中間那顆的右邊界是由右邊鄰居的左邊框畫
     的，只改自己的 border-color 會看起來「右框線被吃掉」。 */
  #lens button.on,#rollbar button.tool.on,#rollbar button.toggle.on{
    background:var(--sel); color:#fff; border-color:var(--sel);
  }
  #lens button.on{position:relative;z-index:1;border-right-width:1px;margin-right:-1px}
  #lens button.on:last-child{margin-right:0}
  #lens button:disabled,#rollbar button.tool:disabled{opacity:.35;cursor:not-allowed}

  /* 縮放：兩個 stepper（不是四顆方鈕），順帶省 80px 的工具列寬度。

      **高度是 var(--btnh)，跟工具列其他按鈕齊平**：#rollbar 的 min-height 是 36px、上下
     padding 各 4px，內容區剛好 28px。代價是一個半格只有 13px 高（::before 補 3px）。 */
  #zoom{display:flex;gap:6px;flex:0 0 auto}
  #zoom .spin{
    display:flex; align-items:center; gap:1px; flex:0 0 auto;
    width:36px; height:var(--btnh); box-sizing:border-box; padding:0 2px 0 3px;
    background:var(--panel); border:1px solid var(--line); border-radius:3px;
  }
  #zoom .spin i{font-size:9px;color:var(--dimmer);flex:1 1 auto;text-align:center}
  /* ± 疊成一欄。13px = (28 − 上下邊框 2) ÷ 2，兩個半格剛好填滿外框內側。 */
  #zoom .ax{display:flex;flex-direction:column;flex:0 0 auto}
  /* `#rollbar button{height:var(--btnh)}` 會把這兩個半格各自撐成 28px，
     所以這條要多一個 class 才壓得過去（1,1,1 > 1,0,1）。 */
  #zoom .ax button{
    position:relative; width:16px; height:13px; min-height:0; padding:0;
    border:0; background:transparent; color:var(--dimmer);
    font-family:var(--mono); font-size:11px; line-height:1; cursor:pointer;
    display:flex; align-items:center; justify-content:center; transition:color .15s;
  }
  /* 沒有邊框，所以感應區得自己講清楚 —— 左右外擴到外框的 padding 上。
     上下不能擴：會蓋到另一半，變成按 ＋ 有時候會縮小。 */
  #zoom .ax button::before{content:"";position:absolute;inset:0 -3px}
  #zoom .ax button:not(:disabled):hover{color:var(--txt)}
  /* disabled 不用 #lens 那套 opacity:.35 —— 那是給有邊框的方鈕的，套在沒有邊框的 11px 字上
     會幾乎看不見，而這兩顆的 disabled 是「已經到底了」唯一的提示。 */
  #zoom .ax button:disabled{color:var(--line);cursor:not-allowed}
  /* 連成一段的互斥選擇（選中的那顆有底色），跟 #lens 同一套語言。不共用 #lens 的規則是因為
     那六顆固定 30px 寬，塞不下兩個中文字。
      **寫成 class 而不是掛在 #rangeSel 上**：#panBox 的三段（停用／左右聲道／3D 空間）
     用同一個外觀，而它不在 #rollbar 底下 —— 也因此沒有那條 `#rollbar button{height}`，
     高度由 padding 決定。 */
  .seg{display:flex;gap:0;flex:0 0 auto}
  .seg button{
    padding:4px 10px; background:var(--panel); border:1px solid var(--line);
    color:var(--dimmer); font-size:11px; cursor:pointer; white-space:nowrap;
    transition:color .15s,background .15s;
  }
  .seg button:first-child{border-radius:3px 0 0 3px;border-right-width:0}
  .seg button:last-child{border-radius:0 3px 3px 0}
  .seg button:not(.on):not([aria-disabled="true"]):hover{color:var(--txt)}
  .seg button.on{
    background:var(--sel); color:#fff; border-color:var(--sel);
    position:relative; z-index:1; border-right-width:1px; margin-right:-1px;
  }
  .seg button.on:last-child{margin-right:0}
  /* 「部份」不能直接點。不用 :disabled 的理由見 HTML 那邊的說明。
     暗掉但游標維持箭頭 —— not-allowed 會讓人以為是壞了，其實是「從別的地方進來」。 */
  .seg button[aria-disabled="true"]{cursor:default}
  .seg button[aria-disabled="true"]:not(.on){opacity:.45}

  /* 「這一軌不能用捲軸編輯」的原因 */
  #rollbar .warnmsg{color:var(--warn);font-size:11px;display:none}
  #rollbar .warnmsg.on{display:inline}
  /*
     **只有紅色音符那句可以點**，唯讀的原因與拖曳中的破壞警告不行（三句共用這一個位置，
    見 pianoroll 的 `whyNot || effectMsg() || badNote`）。點得動的時候才給底線與手指 ——
    看起來能點但點不動比完全不能點更糟。

    `button` 而不是加 listener 的 `span`：鍵盤要到得了，而且螢幕閱讀器要唸得出它是可按的。
    外觀全部歸零，讓它看起來還是那句紅字。
  */
  #rollbar button.warnmsg{
    padding:0; border:0; background:none; font:inherit; text-align:left;
    cursor:default; text-decoration:none;
  }
  #rollbar button.warnmsg.clickable{
    cursor:pointer; text-decoration:underline; text-decoration-style:dotted;
    text-underline-offset:2px;
  }
  #rollbar button.warnmsg.clickable:hover{text-decoration-style:solid}
  /* 非標準時值那一格：一句話 + 一顆小鈕。**整組照抄狀態列的 #tempowarn**（見那裡）——
     站上「一句警告配一顆立刻修好它的鈕」只該有一種長相。差別只有字級跟著工具列走。 */
  #rollNonstd{display:inline-flex;align-items:center;gap:8px;color:var(--warn);font-size:11px}
  #rollNonstd button.tiny{
    background:none;border:1px solid var(--warn);border-radius:3px;
    color:var(--warn);font-size:11px;padding:2px 8px;cursor:pointer;white-space:nowrap;
  }
  #rollNonstd button.tiny:hover{background:var(--warn);color:#14201a}
  #rollNonstd button.tiny:focus-visible{outline:2px solid var(--t1);outline-offset:1px}
  #rollbar .cur{font-size:12px;letter-spacing:.04em}
  /* 操作提示：靠右，空間不夠就自己截斷。**`flex-basis:0` 是這裡的關鍵，不是 min-width:0**
     —— flex 的分行是用「假設主尺寸」決定的，而 `flex-basis:auto` 的假設主尺寸就是內容寬度。

      第二條 `@media (max-width:1180px)` 必須帶 `:not(.keep)`。 */
  #rollbar .hint{
    flex:1 1 0; min-width:0;
    overflow:hidden; text-overflow:ellipsis; white-space:nowrap; text-align:right;
    color:var(--dimmer); font-size:11px;
  }
  #rollbar button.tiny{
    display:inline-flex; align-items:center; gap:6px;
    background:none; border:1px solid var(--line); border-radius:3px;
    color:var(--dim); font-size:11px; padding:4px 9px; cursor:pointer;
    flex:0 0 auto; white-space:nowrap;
  }
  #rollbar button.tiny:not(:disabled):not(.on):hover{border-color:var(--t2);color:var(--txt)}
  #rollbar button.toggle.on:not(:disabled):hover{background:#347d70;border-color:#347d70}
  #rollbar button.tiny:disabled{opacity:.4;cursor:not-allowed}
  /* 純圖示的動作鈕（復原／重做）。.tiny 拿掉文字之後要收成方形，而 11px 的字級對
     一個沒有文字陪襯的圖示來說太小 —— 圖示要自己撐起可辨識度。 */
  #rollbar button.tiny.ico{width:var(--btnh);padding:0}
  #rollbar button.tiny.ico i{font-size:13px}
  /* 演奏是主要動作，在工具列裡也要看起來像主要動作 */
  #rollbar button.play{
    background:var(--t1); border:1px solid var(--t1); border-radius:3px;
    color:#14201a; font-weight:700; font-size:12px; padding:4px 14px;
    cursor:pointer; flex:0 0 auto; white-space:nowrap;
  }
  #rollbar button.play:not(:disabled):hover{background:#eebc6c;border-color:#eebc6c}
  #rollbar button.play:disabled{opacity:.4;cursor:not-allowed}

  /* 捲動容器。#rollpad 是空的，只負責撐出整首歌的尺寸；canvas 用 sticky
     釘在可視區左上角，捲動時由 pianoroll.js 重畫。 */
  #rollwrap{flex:1 1 auto;min-height:0;position:relative;display:flex;flex-direction:column}
  #stage{flex:1 1 auto;min-height:0;overflow:auto;background:var(--bg);position:relative}
  /*
     **拿掉瀏覽器的預設 focus ring。** `#stage` 是 `tabindex="-1"`，聚焦是**程式做的**
    （`ui.focusRoll`：切分頁、點捲簾、點工具列都會走到），使用者按 Tab **永遠走不到它**。
    所以那圈框不是導覽提示，只是每點一次工具列就在捲簾上緣閃一條白線 —— Chrome 的預設
    ring 在深色底上畫的就是白的，而 #stage 佔滿 #rollwrap，看得到的正是它的上緣。

     **這不是為了好看而關掉可及性。** 判準是「Tab 到不了的元素，它的 focus ring 對鍵盤
    使用者沒有意義」。站上真的走 Tab 的那些（`#splitter`、按鈕、`.pill`）各自都有明寫的
    `:focus-visible` 樣式，一個都沒動。
  */
  #stage:focus{outline:none}

  /* 段落標記色點疊在原生水平捲軸上；只有色點本身接事件。 */
  #markdots{
    position:absolute;left:0;right:0;bottom:0;height:var(--hbar,8px);
    overflow:hidden;pointer-events:none;z-index:3;
  }
  #markdots .dot{
    position:absolute;top:0;bottom:0;width:8px;border:0;padding:0;border-radius:0;
    pointer-events:auto;cursor:pointer;
  }
  #markdots .dot:hover{box-shadow:inset 0 0 0 1px #fff}

  /* 膠囊跟著內容水平移動，但垂直固十定在可視區底部。 */
  #flagsLayer{
    position:absolute;left:var(--gut,0px);right:0;bottom:var(--hbar,0px);
    height:30px;overflow:hidden;pointer-events:none;z-index:2;
  }
  #flagsStrip{position:absolute;top:0;bottom:0;left:0;will-change:transform}
  #flagsStrip .pill{
    position:absolute;bottom:5px;max-width:150px;
    padding:3px 10px 3px 8px;border:0;border-radius:0 6px 6px 0;
    font-family:var(--sans);font-size:11px;font-weight:500;line-height:1.4;color:#fff;
    white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
    pointer-events:auto;cursor:pointer;user-select:none;touch-action:manipulation;
    outline:0;transition:transform .12s,box-shadow .12s;
  }
  #flagsStrip .pill:hover{transform:translateY(-2px)}
  #flagsStrip .pill:focus-visible{outline:2px solid #fff;outline-offset:1px}
  #flagsStrip .pill.tab{padding:3px 0;min-width:8px}

  /* ---------- 剪貼片段的縮圖 ---------- */
  /*
    ⚠️ **只在有真指標的裝置上顯示。** 判準是「拖得準不準」而不是「螢幕窄不窄」—— 手指拖
    180×100 的方塊落到某一個 tick 上非常難操作，而觸控筆電有觸控板、平板接上滑鼠也拖得準。
    所以這裡問 `pointer: fine`，**不是** config.js 的 `COMPACT_MQ`：那組斷點會讓平板照樣顯
    示（iPad 寬 810px），卻擋不住手指。同一個立場見下面 `.joy` 放在斷點外面的理由。

    掛在 #rollwrap 上而不是 #stage 內：後者是捲動容器，放進去會跟著內容捲走。
  */
  #rollFrag{display:none}

  @media (hover: hover) and (pointer: fine){
    #rollFrag{
      position:absolute; z-index:4;
      /*  **兩邊都是「讓開捲軸再留 10px」。** 兩個變數都是**量出來的**，不是寫死的：
         `--hbar` 是 #stage 水平捲軸實際吃掉的高度（各平台不同，macOS 的 overlay scrollbar
         量到 0，見 ui.syncFlagBounds），`--sbw` 是原生捲軸的寬度（見 ui.measureScrollbar）。
         寫死 12px 會在某些平台上壓到捲軸、在別的平台上多留一條縫。

         代價知情：面板下緣落在膠囊那一條的**上半部**，所以最右邊 180px 裡的段落標記膠囊有
         一小截點不到（膠囊本身在 --hbar+5 起算、約 23px 高）。要連膠囊一起完全讓開就把 10px
         換成 38px。 */
      bottom:calc(var(--hbar,8px) + 10px);
      right:calc(var(--sbw,12px) + 10px);
      width:180px; height:100px;
      border:1px solid var(--frag,#888); border-radius:6px;
      background:var(--panel); box-shadow:0 4px 14px rgba(0,0,0,.45);
      /* 靜置 .88：底下被遮住的譜會隱約透上來，於是縮圖裡那些 2～3px 的小方塊會跟背景的真
         音符混在一起看。乾淨的解法是完全不透明，但那就換成 100% 遮擋 —— 選前者，因為
         **縮圖是暫時的、被遮的譜是永久的**。 */
      opacity:.88;
      overflow:hidden; cursor:grab;
      touch-action:none; user-select:none; -webkit-user-select:none;
    }
    #rollFrag.on{display:block}
    #rollFrag:hover{opacity:1}
    /* 拖曳中用 `copy` 而不是 `grabbing`：放手是**貼一份進去**，而縮圖自己留在原地（貼完
       不清掉，同一段要能貼進好幾個分頁）。`grabbing` 講的是「這個東西被我拿走了」，那對
       這個手勢是錯的。靜置仍然是 `grab` —— 那一顆回答的是「這塊可以撿起來」。 */
    #rollFrag.dragging{cursor:copy}

    /* 播放中整塊惰性，只剩 X 可按 —— Ctrl+V 在播放中本來就不作用，拖曳貼上必須一樣。
       刻意不加「播放中不能貼」那種新提示：工具列的 #rollNote 本來就在輪流顯示唯讀原因，
       而看得見的灰掉狀態已經說完了。 */
    #rollFrag.off{cursor:default}
    #rollFrag.off canvas,#rollFrag.off .play{opacity:.35}

    #rollFrag canvas{display:block;pointer-events:none}

    /* 有音符落在右界之外 → 右緣淡出。不放「…」也不放數量（見 rollfrag 的 draw）。 */
    #rollFrag.cut::after{
      content:""; position:absolute; top:0; bottom:0; right:0; width:34px;
      pointer-events:none; background:linear-gradient(to right,transparent,var(--panel));
    }

    /* 來源分頁號碼。**不是控制項，只是標籤**，所以一直維持低調（不隨 hover 變）。 */
    #rollFrag .src{
      position:absolute; top:2px; left:5px; pointer-events:none;
      font-family:var(--mono); font-size:10px; line-height:1;
      color:var(--frag,#888); opacity:.75;
    }

    #rollFrag .play,#rollFrag .x{
      position:absolute; padding:0; border:0; cursor:pointer;
      display:flex; align-items:center; justify-content:center;
      color:var(--txt); background:rgba(6,14,15,.55);
      transition:opacity .12s,background .12s;
    }
    /* 圓鈕直徑刻意不大：**嚴格命中判定**下「東西的正中間」被按鈕佔走，而人抓東西習慣抓中
       間 —— 34px 在 180×100 裡還剩很大一圈可以抓。 */
    #rollFrag .play{
      top:50%; left:50%; transform:translate(-50%,-50%);
      width:34px; height:34px; border-radius:50%; font-size:13px;
      /* 未 hover 半透明：看得到底下的音符輪廓。34px 的圓只佔約 5% 面積，擋掉的是中央音高
         帶上的幾個音。既永遠看得見（可發現性）又不長期糊住畫面。 */
      opacity:.45;
    }
    #rollFrag:hover .play{opacity:1;background:rgba(6,14,15,.8)}
    #rollFrag .play:disabled{cursor:default}
    #rollFrag.playing .play{opacity:1;background:var(--frag,#888)}

    #rollFrag .x{
      top:0; right:0; width:20px; height:20px; border-radius:0 5px 0 5px;
      font-size:11px; opacity:.45;
    }
    #rollFrag:hover .x{opacity:1}
    #rollFrag .x:hover{background:var(--warn);color:#fff}

    /* 檢視選單關掉捲軸時 #stage 是 display:none，#rollwrap 會塌成 0 高 —— 絕對定位的面板
       會跑到 main 上面去。 */
    body.no-roll #rollFrag{display:none}
  }

  #markBox .card{width:min(340px,92vw)}
  #markRow{display:flex;align-items:center;gap:8px}
  #markBox #markText{
    flex:0 0 auto;width:21ch;min-width:0;padding:9px 11px;
    font-family:inherit;font-size:13px;color:var(--txt);background:var(--bg);
    border:1px solid var(--line);border-radius:3px;
  }
  #markBox #markText:focus-visible{outline:2px solid var(--t1);outline-offset:1px}
  #markLeft{flex:0 0 auto;color:var(--dim);font-size:12px;font-variant-numeric:tabular-nums}
  #markLeft.over{color:var(--warn)}
  @media (prefers-reduced-motion:reduce){
    #flagsStrip .pill{transition:none}
    #flagsStrip .pill:hover{transform:none}
  }
  #rollpad{position:relative}
  /*
    `touch-action:none`：捲軸上的觸控**一個內建手勢都不要跑**，全部交給 pianoroll.js 接管。

     **`pointerdown` 裡的 `preventDefault()` 取代不了它**（同 #splitter 那段）。

    它買到的是「繪圖模式下單指按住可以拖著虛線框走」—— 那個手勢跟原生的單指捲動是同一個。
    捲動因此改成雙指（pianoroll 的 panMove）。

    **兩個模式都設**：`touch-action` 是元素級的靜態屬性，做不到「這一下才決定要不要攔」。
    代價是選取模式原生的慣性捲動沒了。**滑鼠完全不受影響**。
  */
  #roll{position:sticky;top:0;left:0;display:block;touch-action:none}

  /* ---------- 觸控用的虛擬遙桿面板 ---------- */
  /*
    只在觸控裝置、且捲軸上有選取時由 rolljoy.js 建出來（PC 永遠看不到它）。`position:fixed`
    而不是塞進 #stage：那是捲動容器，放進去會跟著內容捲走。幾何由 pianoroll 的 resize() 餵。
  */
  #rollJoy{
    position:fixed; z-index:5; left:0; right:0;
    display:flex; flex-direction:column; gap:4px;
    padding:6px 14px 12px;
    background:rgba(6,14,15,.82);
    border-top:1px solid var(--line);
    touch-action:none; user-select:none; -webkit-user-select:none;
  }

  /*
    遙桿模式：**MML 編輯區暫時退場**，讓 `#stage` 把空間吃回來。

     **用獨立的 class，不是去 toggle `no-editor`** —— `.joy` 移除之後 `no-editor` 還在原
    位。規則跟 `no-editor` 逐條相同，但**放在斷點外面**（判準是「用手指」不是「螢幕窄」）。
  */
  body.joy .lanetabs,
  body.joy .lanes{display:none}
  body.joy main{flex:0 0 auto!important}
  body.joy #splitter{display:none}
  /* 「放開會刪掉 N 個音」。**高度永遠保留** —— 一出現就把整排按鍵往下推的話，
     手指瞄準的位置會在最需要穩定的時候跳掉。 */
  #rollJoy .warn{
    min-height:16px; font-size:12px; line-height:16px;
    color:var(--dim); text-align:center;
  }
  #rollJoy .warn.on{ color:var(--warn) }
  #rollJoy .ctl{ display:flex; align-items:flex-end; justify-content:space-between; gap:16px }
  #rollJoy .right{ display:flex; flex-direction:column; align-items:flex-end; gap:11px }
  #rollJoy .keys{ display:flex; gap:25px }
  /* 第三排「取消選擇 …… 刪除」：撐滿整欄、兩端對齊。撐滿是 `align-self:stretch` 而不是
     `width:100%` —— .right 是 `align-items:flex-end`，那會把這一排縮成內容寬。 */
  #rollJoy .keys.exits{ align-self:stretch; justify-content:space-between }

  #rollJoy .key{
    min-width:64px; height:34px; padding:0 12px;
    font:inherit; font-size:13px; color:var(--txt);
    background:var(--panel2); border:1px solid var(--line);
    border-radius:8px; cursor:pointer;
  }
  #rollJoy .key i{ margin-right:6px; opacity:.8 }
  #rollJoy .key.on{ background:var(--sel); color:#fff; border-color:transparent }
  #rollJoy .key.on i{ opacity:1 }
  /* 刪除鍵：直接填滿警示色，不是站上其他 `.danger` 那種「平常只有紅框、hover 才填滿」——
     那一招的前提是有滑鼠可以 hover，而這個面板只有手指。配色照抄 `.act.danger:hover`。 */
  #rollJoy .key.del{ background:var(--warn); color:#14201a; border-color:transparent }

  #rollJoy .pad{ position:relative; touch-action:none }
  /* 停用（多選時的寬度桿）。單純灰掉、不加說明 —— 這個編輯器不支援一次改多個音的
     長度，PC 也一樣，而那是一句話講得完、不必寫在按鈕旁邊的規則。 */
  #rollJoy .pad.off{ opacity:.3; pointer-events:none }
  #rollJoy .knob{
    position:absolute; left:50%; top:50%; margin:-23px 0 0 -23px;
    width:46px; height:46px; border-radius:50%;
    background:#cfd6e4; box-shadow:0 4px 14px rgba(0,0,0,.55);
    transition:transform .12s ease-out;      /* 放手彈回中心 */
  }
  #rollJoy .pad.active .knob{ transition:none }   /* 推的時候不能有延遲 */

  /* 圓盤，可以往任何方向推 —— 它驅動的是一支**虛擬滑鼠**，不是一組方向鍵
     （曾經是十字＋軸鎖定，實測退回來了，理由見 rolljoy.js 檔頭）。 */
  #rollJoy .joy{
    width:132px; height:132px; border-radius:50%;
    background:radial-gradient(circle,rgba(255,255,255,.06),rgba(255,255,255,.02));
    border:1px solid rgba(207,214,228,.18);
  }

  #rollJoy .size{
    width:158px; height:52px; border-radius:26px;
    background:radial-gradient(circle,rgba(255,255,255,.06),rgba(255,255,255,.02));
    border:1px solid rgba(207,214,228,.18);
  }
  #rollJoy .size::before,#rollJoy .size::after{
    position:absolute; top:50%; transform:translateY(-50%);
    font-size:15px; opacity:.45; pointer-events:none;
  }
  #rollJoy .size::before{ content:"－"; left:10px }
  #rollJoy .size::after { content:"＋"; right:10px }

  /* ---------- 分隔線 ---------- */
  /* 捲軸與樂譜區之間的 3px 灰白線，按住可以調整兩區的高度。3px 太細不好抓，用 ::before 把
     感應區上下各擴 3px。只擴 3px 是因為上面緊貼著 #stage 的水平捲軸。 */
  /*
    `touch-action:none`：從這條線上開始的觸控**不要跑任何內建手勢**。

     **`pointerdown` 裡的 `preventDefault()` 取代不了它** —— 瀏覽器判定「這一下是不是手
    勢」是在合成器執行緒上做的，發生在 JS 拿到事件之前。

    它擋的**不是**捲動（`#splitter` 往上找不到任何可捲動的祖先），買到的是：擋掉雙擊／雙指
    縮放、消掉手勢仲裁的觀望期、以及保險。

    **它不解決的兩件事**：靶還是 9px；矮螢幕上 `maxMainH()` 會夾成「上限＝下限」、行程是 0
    —— 那是 `minRollH()`（1 個八度 = 162px）吃掉了全部空間。
  */
  #splitter{
    flex:0 0 3px; background:#9db2af; cursor:row-resize;
    position:relative; z-index:2; user-select:none; touch-action:none;
  }
  #splitter::before{content:"";position:absolute;left:0;right:0;top:-3px;bottom:-3px}
  #splitter:hover,#splitter.dragging{background:#dce8e5}
  #splitter:focus-visible{outline:2px solid var(--t1);outline-offset:1px}
  /* 拖曳中整頁都保持 row-resize，游標跑到別的元素上時才不會變回箭頭 */
  body.splitting,body.splitting *{cursor:row-resize!important;user-select:none}

  /* ---------- body ---------- */
  /* 設定都收進抽屜了，main 只剩樂譜文字區。這裡的 300px 只是初始值，
     實際高度由分隔線決定（ui.js 會寫成 inline flex-basis）。 */
  main{flex:0 0 300px;display:flex;min-height:0}

  /* ---------- 抽屜（從右邊滑入，浮在最上面） ----------
     兩個：#settings 與 #aboutPanel，互斥開合，共用 #scrim（見 ui.js 的 initDrawers）。

      用 `.drawer` 而不是列兩個 ID：外框、head、關閉鈕、body、內文段落、底部版權**每一條
     都是共用的**。`position:fixed` 要求它們是 body 的直接子元素。 */
  .drawer{
    position:fixed; inset:0 0 0 auto; width:min(420px,92vw); z-index:70;
    background:var(--panel); border-left:1px solid var(--line);
    display:flex; flex-direction:column; overflow-y:auto;
    transform:translateX(100%); transition:transform .22s ease;
    box-shadow:-18px 0 50px rgba(0,0,0,.55);
  }
  .drawer.on{transform:translateX(0)}
  .drawer .head{
    display:flex; align-items:center; gap:10px; flex:0 0 auto;
    padding:14px 18px; border-bottom:1px solid var(--line); background:var(--bg2);
    position:sticky; top:0; z-index:1;
  }
  .drawer .head h2{
    margin:0; flex:1; font-family:var(--display); font-weight:400; font-size:17px;
  }
  #settingsClose,#aboutClose{
    background:none; border:1px solid var(--line); border-radius:3px;
    color:var(--dim); cursor:pointer; padding:3px 8px; font-size:13px; line-height:1.3;
  }
  #settingsClose:hover,#aboutClose:hover{border-color:var(--warn);color:var(--txt)}
  .drawer .body{display:flex;flex-direction:column;gap:20px;padding:18px}
  /* 抽屜裡竹的內文段落（站台說明、說明頁清單、特別感謝、使用技術、法律），所以自己帶
     行高與連結樣式。使用技術那一塊的 <ul> 也掛了這個 class —— 見下面的 .drawer .tech */
  .drawer .thanks{margin:0;color:var(--dim);font-size:12px;line-height:1.8}
  .drawer .thanks b{color:var(--txt);font-weight:500}
  /* 抽屜裡的連結：說明頁那六條、關於綠茶、使用技術那四條、服務條款、隱私權政策。
     它們的共同點是**都會換網址**，所以共用一套「底線在 hover 才出現」的連結長相。 */
  .drawer .thanks a{
    color:var(--t2); text-decoration:none;
    border-bottom:1px solid transparent; transition:border-color .15s;
  }
  .drawer .thanks a:hover{border-bottom-color:var(--t2)}
  .drawer .thanks a:focus-visible{outline:2px solid var(--t1);outline-offset:2px;border-radius:2px}

  /* 使用技術的清單。<ul> 同時掛了 .thanks，所以上面那五條規則整組已經生效。

     margin-bottom 是用來蓋掉 .thanks 的 margin:0 的（同權重、後者勝，所以這條**必須**排在
     上面那組後面）。

      選擇器是 .drawer 而不是 #settings：這一塊住在關於抽屜。 */
  .drawer .tech{
    list-style:none; padding:0;
    display:flex; flex-direction:column; gap:8px;
    margin-bottom:10px;
  }
  /*
    「載入示範樂譜」—— 這個抽屜裡**唯一一個不換網址的入口**，所以它刻意**不**長得像上面那些
    連結：有框線，讀起來是「一顆會做事的鈕」。

     **刻意比站上其他鈕小一號。** 標題列那批（`padding:5px 11px` / 12px）與 `.act`
    （`padding:9px 11px` / 14px）是給「自己站一列」的鈕用的，而這一顆住在一段 12px 散文的
    行內。

    高度**訂死 20px、padding 上下歸零**：`.thanks` 的行框是 12 × 1.8 = 21.6px，鈕只要超過它
    就會把那一行推開（實測日文那段 64.78px，把鈕 display:none 之後還是 64.78px）。

     **`vertical-align` 一定要是 baseline。** 原本寫 `-2px`，那把鈕推到基線**下方**，行框
    就得長高去容納它（64.78 → 66.69px）。要調位置就改 height（18/19/20px 都是零位移，21px
    開始撐開）。

    `font-family:inherit` 一定要有（button **不繼承**字型），但 font-size 明寫 11px ——
    不能用 `font:inherit`，那會連 12px 與 1.8 的行高一起帶進來。
  */
  /*  **兩個選擇器共用一套樣式是刻意的**：`.thanks button` 與 `.copy button` 是抽屜裡唯二
     按下去**不會把你帶走**的入口。那個區別要靠框線說，所以兩顆一定要長得一樣。 */
  .drawer .thanks button,.drawer .copy button{
    display:inline-flex; align-items:center; vertical-align:baseline;
    height:20px; margin-left:10px; padding:0 8px;
    background:none; border:1px solid var(--line); border-radius:3px;
    color:var(--t2); font-family:inherit; font-size:11px; line-height:1;
    cursor:pointer; transition:border-color .15s,color .15s;
  }
  .drawer .thanks button:hover,.drawer .copy button:hover{border-color:var(--t2);color:var(--txt)}
  .drawer .thanks button:focus-visible,.drawer .copy button:focus-visible{outline:2px solid var(--t1);outline-offset:2px}
  /* `.copy` 有 `letter-spacing:.05em`（那是給版權那一行的），鈕上要收回來 ——
     11px 的字再加字距會比旁邊的「載入示範樂譜」寬一截，兩顆就不像同一種東西了。 */
  .drawer .copy button{letter-spacing:normal}
  /*  `hidden` 要贏過上面的 `display:inline-flex` —— 屬性選擇器的權重贏不了那組後代選擇
     器，所以要連著一起寫。這條服務的是 #pwaUpdateBtn；漏掉的話它會**開站就出現而且永遠關不
     掉**。 */
  .drawer .copy button[hidden]{display:none}
  /*
    服務條款與隱私權政策中間那一點。間距用 margin 而不是 gap（這裡是一段 inline 的散文）。

     顏色**刻意不沿用** `.status .legal span` 的 `--line`（#23403f）—— 那個色在 `--panel`
    上實質看不見，而分隔點看不見的話兩個入口會被讀成一句話。
     分隔點與兩邊的入口**不能有空白或換行**（見 _About.cshtml）。
  */
  .drawer .thanks .sep{color:var(--dimmer);margin:0 7px}

  /* 版權靠底。margin-top:auto 在 flex 欄裡會把剩下的空間全吃掉。規則掛在 .drawer 上 —— 它講
     的是「抽屜的底怎麼長」，不是「關於的底怎麼長」。 */
  .drawer .copy{
    margin-top:auto; flex:0 0 auto;
    padding:14px 18px calc(18px + env(safe-area-inset-bottom));
    border-top:1px solid var(--line-soft);
    color:var(--dimmer); font-size:11px; letter-spacing:.05em;
  }

  /* 有下拉的那幾格改成**一行**：標籤在左、下拉在右、說明另起一行跨滿。抽屜從 340px 加寬到
     420px，一行才擺得下「MML 格式化換行」加一個下拉。

     用 :has(> select) 而不是加一個 class，是為了不動到音色庫與樂器定義檔那兩格（.filebtn）
     與刪除帳號那格（button.act）。 */
  #settings .group:has(> select){
    display:grid; grid-template-columns:1fr auto; align-items:center;
    gap:6px 14px;
  }
  #settings .group:has(> select) > label{margin:0}
  /*
    高度**明寫 28px，而且上下 padding 歸零** —— 兩件事是綁在一起的，不能只做一半。

    成因是繼承：`button,select,input,textarea{font:inherit}` 是 `font` 簡寫，它連
    **line-height 一起帶過來** —— 於是 select 裡那一行文字本身就是 14 × 1.6 = 22.4px，加上
    原本的 6+6 padding 與 2px 框線 = 36.4px。「只把 padding 調小」永遠到不了 28。

    height + padding:0 而不是「padding:2px + line-height:1.2」：select 的內部排版是 UA 決定
    的（Chrome 與 Firefox 對選定文字的垂直置中做法不同）。

    line-height 仍然明寫 1.2：內容框只有 26px，繼承來的 22.4 沒有餘裕。
  */
  #settings .group:has(> select) > select{
    width:auto; min-width:130px; max-width:190px;
    height:28px; padding:0 10px; line-height:1.2; justify-self:end;
  }
  /* 說明自己一行、跨滿兩欄 —— 它們是完整句子，擠在右欄會變成一行三個字 */
  #settings .group:has(> select) > .hint{grid-column:1 / -1;margin:0}

  #scrim{
    position:fixed; inset:0; z-index:69; background:rgba(6,14,15,.55);
    opacity:0; pointer-events:none; transition:opacity .22s;
  }
  #scrim.on{opacity:1;pointer-events:auto}

  /* ---------- 使用者區塊 ---------- */
  #userBox{
    display:flex; align-items:center; gap:11px;
    padding:12px 13px; background:var(--panel2);
    border:1px solid var(--line); border-radius:4px;
  }
  #userBox > i{font-size:26px;color:var(--dimmer);flex:0 0 auto}
  #userBox.on > i{color:var(--t2)}
  #userBox .who{display:flex;flex-direction:column;min-width:0;flex:1}
  #userBox .who b{font-weight:500;font-size:13px;line-height:1.35}
  /* email 會很長，而抽屜只有 420px —— 溢出就切掉，不要把版面撐爆 */
  #userBox .who em{
    font-style:normal; color:var(--dimmer); font-size:11px; line-height:1.35;
    overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
  }
  #userBox .who em:empty{display:none}
  #userBox .act.auth{
    width:auto; flex:0 0 auto; padding:6px 11px; font-size:12px;
    text-align:center; text-decoration:none; color:var(--dim); white-space:nowrap;
  }
  #userBox .act.auth:hover{border-color:var(--t2);color:var(--txt)}
  #userBox .act.auth[hidden]{display:none}
  /* 伺服器還沒設定 Google 的 Client ID 時，按了也只會 404 —— 直接說清楚 */
  #userBox .act.auth.off{opacity:.4;pointer-events:none}

  .group{display:flex;flex-direction:column;gap:8px}
  .group > label,.legend{
    font-size:11px; letter-spacing:.12em; text-transform:uppercase;
    color:var(--dimmer);
  }
  select,.filebtn,button.act{
    width:100%; background:var(--panel2); border:1px solid var(--line);
    border-radius:3px; padding:9px 11px; text-align:left; cursor:pointer;
    transition:border-color .15s,background .15s;
  }
  select:hover,.filebtn:hover,button.act:not(:disabled):hover{border-color:var(--t2)}
  select:focus-visible,.filebtn:focus-within,button:focus-visible,textarea:focus-visible{
    outline:2px solid var(--t1); outline-offset:1px;
  }
  .filebtn{display:block;position:relative}
  .filebtn input{position:absolute;inset:0;opacity:0;cursor:pointer;width:100%}
  .hint{display:block;color:var(--dimmer);font-size:11px;margin-top:2px}
  /* 說明裡的重點。抽屜本來沒有這條規則，於是那幾句只有粗體、顏色跟周圍一樣暗 —— 而每一個對
     話框（#optBox / #fileBox / #shareBox）都把它提亮一階。 */
  #settings .hint b{color:var(--dim);font-weight:500}
  button.act{text-align:center;font-weight:500}
  button.act:disabled{opacity:.4;cursor:not-allowed}
  /* 連結做的按鈕沒有 disabled 可用。離線時的兩顆登入鈕（#shareLoginBtn /
     #saveWebLoginBtn）靠這一條真的按不下去 —— 同 `#userBox .act.auth.off`。 */
  a.act.off{opacity:.4;pointer-events:none}
  .row{display:grid;grid-template-columns:1fr 1fr;gap:8px}


  /* ---------- editor ---------- */
  section.editor{flex:1;display:flex;flex-direction:column;min-width:0;background:var(--bg)}
  /* 分頁列的定位父層。`#tabs` 自己是捲動容器，所以左右捲動鈕不能掛在它裡面，要掛在這一層
     上。它沒有自己的高度 —— 鈕是絕對定位的，所以 `ui.minMainH` 量 offsetHeight 仍然日是對的。 */
  .tabsbar{position:relative; flex:0 0 auto; min-width:0}
  .tabs{
    display:flex; gap:0; flex:0 0 auto;
    border-bottom:1px solid var(--line);
    background:var(--bg2); overflow-x:auto; scrollbar-width:none;
  }
  .tabs::-webkit-scrollbar{display:none}
  /*
    **分頁列永遠是一行，多了就橫向捲動。** 換行（`.tabs.wrap`）的代價是**分頁列一多就吃掉一
    列樂譜區的高度**，而分隔線的下限就是量 #tabs 的 offsetHeight 算的（見 ui.minMainH）。
  */

  /*
    分頁列的左右捲動鈕。**只在有精準指標的裝置上出現** —— 判準用 `(hover:hover) and
    (pointer:fine)` 而不是螢幕寬度。

    `bottom:1px` 不是隨手寫的：蓋掉 `.tabs` 的 `border-bottom` 會讓分界線斷開 22px。
  */
  .tabnav{display:none}
  @media (hover:hover) and (pointer:fine){
    .tabnav{
      position:absolute; top:0; bottom:1px; width:22px; z-index:2;
      display:none; align-items:center; justify-content:center;
      background:var(--bg2); border:0; padding:0; cursor:pointer;
      color:var(--dimmer); transition:color .15s;
    }
    .tabnav.on{display:flex}
    .tabnav:hover{color:var(--txt)}
    .tabnav.prev{left:0}
    .tabnav.next{right:0}
    /* 三角形用 border 畫，不用字也不用圖示字型：省掉一條語言檔的字與一個 fa 的 class。
       `currentColor` 讓它跟著 :hover 走。 */
    .tabnav::before{
      content:""; width:0; height:0;
      border-top:4px solid transparent; border-bottom:4px solid transparent;
    }
    .tabnav.prev::before{border-right:5px solid currentColor}
    .tabnav.next::before{border-left:5px solid currentColor}
  }
  .tabs button{
    display:flex; align-items:center; gap:7px; flex:0 0 auto;
    padding:9px 18px 9px 12px; font-size:12px; letter-spacing:.06em; white-space:nowrap;
    color:var(--dimmer); background:none; border:0;
    border-right:1px solid var(--line); cursor:pointer;
    transition:color .15s,background .15s;
    position:relative;      /* 拖曳的插入線是絕對定位的子元素 */
  }
  .tabs button:hover {color:var(--txt);background:var(--panel)}
  .tabs button.on{color:var(--txt);background:var(--bg);box-shadow:inset 0 -2px 0 var(--tab)}
  .tabs button i{width:9px;height:9px;border-radius:50%;flex:0 0 auto;background:var(--tab)}
  .tabs button.hidden{display:none}
  /*
    這一軌是靜音的（class 由 tracks.syncMutes 掛）。**兩個標記都是純視覺屬性，一個像素的版面
    都不動** —— 分頁列的寬度一變，橫向移動滑鼠時整排會跟著抖。

    inset 的 box-shadow 當邊框用（不是 border）：border 會吃掉 9px 的內容框。
  */
  .tabs button.muted .nm{text-decoration:line-through;text-decoration-thickness:1px}

  /*
    圓點現在是**鬼影開關**（鋼琴捲軸上要不要畫這一份譜的音符），不是靜音的第二層 —— 分頁列
    一個像素都不能多，而圓點本來就在那裡。空心 = 不畫。

    inset 的 box-shadow 當邊框（不是 border）：border 會吃掉 9px 的內容框。
  */
  .tabs button i{cursor:pointer}
  .tabs button i.off{background:transparent;box-shadow:inset 0 0 0 1.5px var(--tab)}

  /* 使用者自己改過的名字用正常色，預設的「音軌 N」淡一級 —— 一眼看得出哪幾個
     已經指派給人了。同樣只改顏色，不動版面。 */
  .tabs button.named .nm{color:var(--txt)}
  /* 改名中。**寬度會跟著字變**，那是唯一的例外，而它是可接受的：使用者此刻正盯著
     這一個分頁打字，整排跟著動是他預期得到的。 */
  .tabs .nm.editing{
    outline:1px solid var(--t1); outline-offset:2px; border-radius:2px;
    color:var(--txt); cursor:text; min-width:1.5em;
  }
  /* 歌唱譜的分頁。只有一條軌，而且複製出來是 `MML@,,,xxxx;` —— 值得標一下。
     用軌名前面一個小記號（::before 不佔 flex 位置，寬度變化只有一個字元寬）。 */
  .tabs button.vocal .nm::before{content:"♪ ";color:var(--t2)}
  /* 分頁上的樂器名稱：「主旋律(長笛)」。負 margin 吃掉 flex 的 7px gap，讓它貼著軌名 */
  .tabs .inst{margin-left:-7px;color:var(--dimmer);font-size:11px}
  .tabs button.on .inst{color:var(--dim)}
  .tabs .inst:empty{display:none}
  /* 移除分頁的 ✕。**絕對定位，完全不佔版面** —— 15 個分頁各留一個 ✕ 的位置會把分頁列擠
     爆，而用 display 切換會讓分頁在滑鼠移入的瞬間變寬。

     底色要跟著分頁的狀態走。**當前軌也顯示**，不只 hover：觸控裝置沒有 hover。 */
  .tabs .x{
    position:absolute; right:2px; top:50%; transform:translateY(-50%);
    padding:2px 4px; border-radius:3px;
    color:var(--dimmer); font-size:11px; line-height:1;
    background:var(--panel);
    opacity:0; pointer-events:none; transition:opacity .12s;
  }
  .tabs button:hover .x ,
  .tabs button.on .x
  {opacity:1;pointer-events:auto}
  .tabs button.on .x{background:var(--bg)}
  .tabs .x:hover{background:var(--warn);color:#14201a}
  /* 只剩最低軌數了 —— 一個都不能再砍。整批關掉比逐個 tab 判斷簡單，
     而且條件本來就是全域的（見 tracks.syncTabs）。 */
  .tabs.noremove .x{display:none}
  /* 拖曳中一個都不要出現：選插入位置時滑鼠會經過好幾個分頁，每經過一個閃一個 ✕
     只是干擾，而那正是使用者在盯插入線的時候。 */
  .tabs.dragging .x{opacity:0;pointer-events:none}
  .tabs .add{color:var(--t2);letter-spacing:.04em}

  /* 拖曳分頁改樂譜順序。分頁標題是「位置的名字」，搬動的是 MML 與樂器。 */
  .tabs button:not(.add){cursor:grab}
  .tabs button.dragging{opacity:.4;cursor:grabbing}
  /*
    插入線：**畫在兩個分頁之間的縫**，不是整格亮起來。游標在分頁左半 → 線在左緣，右半 → 線
    在右緣（見 tracks.initTabDrag）。

    不做「後面的分頁往後退一格」那種位移預覽：分頁一位移，游標底下的元素就換人 → 算出別的插
    入點 → 再位移一次 → 抖動迴圈。
  */
  .tabs button.dropL::before,
  .tabs button.dropR::after{
    content:""; position:absolute; top:0; bottom:0; width:2px;
    background:var(--t2); box-shadow:0 0 6px var(--t2);
    pointer-events:none;
  }
  .tabs button.dropL::before{left:-1px}
  .tabs button.dropR::after{right:-1px}

  .panes{flex:1;display:flex;min-height:0;min-width:0}
  .pane{flex:1;display:none;flex-direction:column;min-height:0;min-width:0}
  .pane.on{display:flex}
  /* 每個分頁置頂的樂器列 */
  .panebar{
    display:flex; align-items:center; gap:10px; flex-wrap:wrap;
    padding:9px 22px; border-bottom:1px solid var(--line-soft); background:var(--bg2);
  }
  .panebar > .lbl{
    font-size:11px; letter-spacing:.12em; text-transform:uppercase; color:var(--dimmer);
  }
  /*
    樂器下拉：**讓它自己貼合內容**（`width:auto` 的 select 會照最寬的那個選項自己縮）。
    min-width 蓋掉開站時的一次跳動；max-width 留 220px 給沒有 .def 的 GM 包那種長名字。
  */
  .panebar select{
    flex:0 0 auto; width:auto; min-width:96px; max-width:220px;
    padding:6px 9px; font-size:12px;
  }
  /*
    每軌的靜音鈕。純圖示 —— 可辨識性靠 title 與 aria-label 補。

     **樣式要自己寫全，不能靠 .tiny / .toggle。** 那兩個 class 的規則全部掛在
    `#rollbar button.tiny` 之下，選不到 panebar 裡的按鈕。
  */
  /* 樂器列上的正方形圖示鈕（靜音、顯示、solo、複製）。**共用一份** —— 它們並排在
     同一列，大小差一個像素都看得出來，而各寫一份就是等著漂移。 */
  .panebar .iconbtn{
    flex:0 0 auto; display:inline-flex; align-items:center; justify-content:center;
    /* **高度訂死、不讓內容的 line box 決定**（同 #rollbar 那組按鈕）：裝 FontAwesome glyph
       的按鈕會比裝文字的高一截，而那個高度會隨字型與 FontAwesome 版本漂移。 */
    width:28px; height:28px; padding:0; box-sizing:border-box;
    background:none; border:1px solid var(--line); border-radius:3px;
    color:var(--dim); cursor:pointer;
    transition:color .15s,background .15s,border-color .15s;
  }
  /* 沒有文字陪襯的圖示要自己撐起可辨識度 —— 同 #rollbar button.tiny.ico 的判斷 */
  .panebar .iconbtn i{font-size:13px}
  .panebar .iconbtn:not(.on):hover{border-color:var(--t2);color:var(--txt)}
  /* 按下狀態**三顆共用**（靜音、顯示、solo）。一般化到 `.iconbtn` 是刻意的：各寫一份就是等
     著漂移。`.pane-copy` 是動作不是 toggle，永遠不會有 `.on`。 */
  .panebar .iconbtn.on{background:var(--sel);border-color:var(--sel);color:#fff}
  .panebar .iconbtn.on:hover{background:#347d70;border-color:#347d70}
  /* 「複製」推到這一列的最右邊 —— 它是這一列唯一的**動作**（其他都是設定），
     而把動作跟設定分開站是這一列讀得懂的關鍵。 */
  .panebar .pane-copy{margin-left:auto}

  /* 模式下拉。比樂器下拉窄 —— 它只有兩個選項，而且字很短。 */
  .panebar .trk-mode{min-width:auto}

  /*
    靜音／獨奏／顯示／只看那四顆包成一組（`.grp` 已經是 `flex-wrap:nowrap`）。**理由是換
    行**：沒包起來的時候那四顆會被斷成 2+2，「哪兩顆管耳朵、哪兩顆管眼睛」的配對當場斷掉。
  */
  .panebar .grp{gap:6px}

  /* 音符數／字數搬到每一條 lane 自己的標題列了（`.panebar .meta` 因此退役）：三條 lane 的上
     限不一樣（1600/1200/900），共用一個計數器沒辦法表達「和音2 超標了」。 */
  .lanebar .meta{font-family:var(--mono);font-size:11px;color:var(--dimmer)}
  .lanebar .meta.full{color:var(--warn)}   /* 到字數上限了 */

  /* 狀態列上的速度一致性提示。橘字 + 一顆修好它的按鈕。
     `.tiny` 那套只在 #rollbar 底下有定義，所以這裡自己給一份最小的。 */
  #tempowarn{display:inline-flex;align-items:center;gap:8px;color:var(--warn)}
  #tempowarn button.tiny{
    background:none;border:1px solid var(--warn);border-radius:3px;
    color:var(--warn);font-size:11px;padding:2px 8px;cursor:pointer;white-space:nowrap;
  }
  #tempowarn button.tiny:hover{background:var(--warn);color:#14201a}

  /* 分頁上竹的速度警告：**絕對定位、不佔版面**（同 ✕ 的理由，見那段）。右上角一個
     小三角，而且**不是 hover 才出現** —— 它要在不切過去的情況下也看得到，那是它
     存在的全部理由。 */
  .tabs button.tempobad::after{
    content:"▲"; position:absolute; right:3px; top:1px;
    font-size:8px; line-height:1; color:var(--warn); pointer-events:none;
  }

  /*
    排版屬性由 textarea 與語法上色層**共用同一條規則** —— 任何一個排版屬性只在一邊改掉，那一
    行之後就整片錯位，而它不會噴錯。

    連字必須關掉：`<<` `>>` 在帶連字的等寬字型裡會合併成單一字形，兩層寬度就不一樣。--mono
    的堆疊裡沒有帶連字的字型，但使用者本機可能裝了同名的變體。
  */
  textarea,.mml-hl{
    font-family:var(--mono); font-size:13.5px; line-height:1.85;
    /* 左右只留 10px：三欄並排之後一條 lane 只有三分之一寬，22px 兩邊就吃掉 44px。改這裡就
       好，`#pasteBox textarea` 自己有一份 padding。 */
    padding:10px 10px; letter-spacing:.02em; tab-size:2;
    /*
      軟換行（不開格式化換行時）**每個字元都是斷點**。

      少了 word-break 的話瀏覽器會優先用「合法斷點」，而 MML 裡唯一符合的是連字號 —— 於是
      `c-8` 被切成 `c-` / `8`，而 `-` 在 MML 裡是降記號。

       這一行**必須留在這條共用規則裡**：兩層折在不同位置的話，那一行之後整片錯色。
      word-break 是繼承屬性，掛在這裡才跟 textarea 綁在同一條規則上。

      `overflow-wrap:break-word` 是萬一某個瀏覽器不認 textarea 上的 word-break 時的退路。
    */
    white-space:pre-wrap; overflow-wrap:break-word; word-break:break-all;
    font-variant-ligatures:none;
    font-feature-settings:"liga" 0,"clig" 0,"calt" 0,"dlig" 0;
    font-kerning:none; font-synthesis:none;
  }
  textarea{flex:1; width:100%; resize:none; border:0; background:transparent}
  /* 小節尺 + textarea 並排。.pane 本身是縱向的，所以要多一層橫向容器。width:auto 是必要的
     —— textarea 的 width:100% 會拿整列的寬度去算，把尺的寬度擠出容器。 */
  /* lane：一個分頁有四個（旋律／和音1／和音2／歌唱），模式決定顯示哪些。預設不顯示，.on
     才出現，不必動 inline style（那會蓋掉 body.wrapmml 的規則）。

     **三條等分**（flex:1 + flex-basis:0）：三軌對位時眼睛要在固定的位置找同一條 lane。 */
  /* 三條 lane 並排的容器。**水平**而不是垂直堆疊，理由是對位：三條共用同一個「幾小節一
     行」設定，所以**同一個垂直位置就是同一個小節**。

     min-width:0 少了會讓 flex 子項被裡面的 pre 撐開而不肯縮（預設 min-width 是 auto）——
     症狀是右邊兩欄被推出畫面。 */
  /* 外層是**縱向**的：`.lanesrow`（三條 lane + 縱向捲軸欄）在上，橫向捲軸在下。「三欄並
     排」那件事整個搬到 `.lanesrow` 上了，**底下那些 media query 也跟著改成問它**（見
     ui.sharedScrollOn）。 */
  /*
    左右與下方的留白**給在容器上，不是給在文字上**。`.tarow` 有框線，沒有這一層的話最左邊那
    條的左框線會落在 x=0。8px 是為了對上 `.lanesrow` 的 `gap:8px`。

    放在 `.lanes`（外層）而不是 `.lanesrow`：這樣底下那條共用的橫向捲軸也一起內縮。
  */
  .lanes{flex:1;display:flex;flex-direction:column;min-height:0;min-width:0;padding:0 8px 8px}
  .lanesrow{flex:1;display:flex;gap:8px;min-height:0;min-width:0}

  .lane{display:none}
  /* flex:1 1 0 —— basis 是 0 而不是 auto，三欄才會**真的等寬**。auto 的話寬度會
     跟著內容多寡跑，於是每貼一次譜三欄的寬度就重新分配一次。 */
  .lane.on{flex:1 1 0;display:flex;flex-direction:column;min-height:0;min-width:0}

  /* lane 的標題列。**永遠佔著這一行**（就算字數是空的），不然三條 lane 的文字起點
     會因為有沒有計數而錯開一行，而那正是對位時最需要對齊的東西。 */
  .lanebar{
    display:flex;gap:10px;align-items:baseline;
    font-size:11px;color:var(--dimmer);
    padding:2px 2px 1px;min-height:15px;
  }
  .lanebar .nm{color:var(--dim);font-weight:600;letter-spacing:.02em}
  /* 計數靠右：軌名在左、數字在右，兩端對齊。三條並排時右緣剛好貼著各自編輯框的
     右邊界，所以三個數字排成一直線 —— 貼著軌名跟著字長浮動的話那條線就散了。 */
  .lanebar .meta{margin-left:auto}
  /* 焦點那一條的軌名亮起來。只改顏色、不改字重與大小 —— 版面一個像素都不動，
     理由跟分頁列那條（見 tracks.syncMutes）一樣：焦點會頻繁換，版面跟著動就會抖。 */
  .lane.focus .lanebar .nm{color:var(--t1)}

  /*
    一條 lane 的編輯框（小節尺 + 文字）。

    **框線永遠在，只換顏色**：「選中才加 border」會讓焦點那一條的內容盒少 2px，每次換焦點
    整個版面抖一下。

     **焦點那一條的框線不能是 `transparent`。** 症狀是剛進站時第一個編輯框整個沒有框 ——
    那時它有 `.focus` 但**還沒有 DOM 焦點**，兩種框都沒有。base 就是灰框，`.focus` 只是把它
    調亮。

    不加圓角：`.barnum` 是方角而且有自己的底色，圓角會讓它的角探出來，而唯一的解法是在父層
    裁切 —— 那會把 textarea 的焦點框一起裁掉。
  */
  .tarow{
    flex:1; display:flex; min-height:0; min-width:0;
    border:1px solid var(--line);
    transition:border-color .15s;
  }
  .lane.focus .tarow{border-color:var(--dimmer)}

  /* 分頁內的 lane 頁籤。**桌機不顯示** —— 三條都看得到，不需要切換。裡面除了三顆按鈕還有三
     個計數鏡像（手機用，見 tracks.buildTab）。 */
  .lanetabs{display:none}
  .lanetabs button.hidden,.lanetabs .meta.hidden{display:none}

  /* 窄螢幕（平板）退回縱向堆疊。三欄各剩三分之一寬時一行放不到十個音符，橫向捲軸
     會比文字本身還顯眼 —— 那時「並排好對位」的好處已經被寬度吃掉了。三條都還看得到，
     只是變成上下排列。 */
  @media (max-width: 900px) {
    .lanesrow{flex-direction:column;gap:4px}
  }

  /*
    手機：**一次只顯示一條 lane**，用上面那排頁籤切換 —— 編輯區只有兩三百像素高。斷點用 640px
    而不是沿用標題列那個 576px：那一條管的是標題列會不會擠爆。
  */
  @media (max-width: 640px) {
    .lanetabs{
      display:flex; gap:4px; flex:0 0 auto; align-items:center;
      padding:4px 12px 0; overflow-x:auto;
    }
    /*
      **選中的那顆沒有底線，另外兩顆有。** 這是紙上分頁夾的樣子：選中的那一頁跟下面的內容是
      連在一起的。
    */
    .lanetabs button{
      flex:0 0 auto; padding:4px 10px;
      background:none; border:1px solid var(--line); border-radius:3px 3px 0 0;
      color:var(--dimmer); font-size:11px; cursor:pointer; white-space:nowrap;
    }
    /* 只改底線那一邊 —— 不能再用 `border-color` 簡寫，那會把底線也一起設回來 */
    .lanetabs button.on{color:var(--txt);background:var(--bg2);border-bottom-color:transparent}

    /*
      音符數／字數跟頁籤**同一列、靠右**（元素見 tracks.buildTab），省下一整行。
      `margin-left:auto` 掛在 `.meta` 上：三個鏡像裡只有焦點那一條沒有 `.hidden`。
    */
    .lanetabs .meta{
      margin-left:auto; padding-left:8px;
      font-family:var(--mono); font-size:11px; color:var(--dimmer); white-space:nowrap;
    }
    .lanetabs .meta.full{color:var(--warn)}
    /* 計數搬到頁籤那一列了，這一行整個收掉。
       它在桌機「永遠佔著一行」的理由是**三條要對齊**（見 .lanebar 的說明），
       而手機一次只顯示一條 —— 沒有東西要跟它對齊，那條理由不成立。 */
    .lanebar{display:none}

    /* 只有焦點那一條露出來。`.on` 仍然表示「這個模式用得到」（計字、輸出、解析都看
       它），所以這裡疊一層 `.focus` 而不是去改 `.on` 的語意。 */
    .lanesrow{flex-direction:column;gap:0}
    .lane.on{display:none}
    .lane.on.focus{display:flex}
  }
  .tarow textarea{width:auto;min-width:0}

  /* ---------- 三條 lane 共用的捲軸 ----------
     三欄並排時三條 lane 是**一起捲動**的：三條 textarea 各自照舊捲動、只是藏掉自己的捲軸，
     另外放兩個借原生捲軸的元件當那個唯一看得見的把手，scrollTop / scrollLeft 由 ui.js 三邊
     互相對齊。

     捲動**留在 textarea 身上**：上色層的寬度是拿 `ta.clientWidth` 去設的（見
     ui.syncLaneMetrics）。

     `.shared` 由 JS 掛（`.lanesrow` 真的是橫向、看得見的 lane 有兩條以上、內容真的溢出）。
     **手機、平板、歌唱模式一律沒有這一套**。 */
  .vscroll,.hscroll{display:none}
  .lanes.shared .vscroll{display:flex;flex-direction:column;flex:0 0 auto}
  /* 上緣墊片用**空的 `.lanebar`**，不寫死高度 —— 那一行的高度是 CSS 算出來的，寫死的數字遲
     早跟它對不上，而對不上的表現是捲軸的行程比文字短一截。 */
  .lanes.shared .vscroll .lanebar{flex:0 0 auto}
  .lanes.shared .vscroll-track{flex:1;width:var(--sbw);overflow-y:scroll;overflow-x:hidden}
  /* 只在不換行模式出現。軟換行時文字本來就折在框內，沒有橫向溢出可捲。條件沿用 `.barnum`
     那一條（body.wrapmml），不新增第二種判斷。 */
  body.wrapmml .lanes.shared .hscroll{
    display:block; flex:0 0 auto; height:var(--sbw);
    overflow-x:scroll; overflow-y:hidden;
    /* 右邊讓開縱向捲軸那一欄，兩條捲軸交會處留一個空的角。讓開的是**捲軸欄 + 它前面那個
       gap**（`.lanesrow` 的 `gap:8px`），只扣 --sbw 的話右緣會多伸出 8px。 */
    margin-right:calc(var(--sbw) + 8px);
  }
  /* 撐出行程的墊片。高／寬由 ui.js 算（見 syncLaneMetrics），另一邊給 1px ——
     0 的話有些瀏覽器會把它當空盒子、不產生行程。 */
  .vscroll-in{width:1px}
  .hscroll-in{height:1px}
  /* 三條自己的捲軸藏起來。**只在 `.shared` 之下** —— 沒有共用捲軸的那些版型還得靠它們捲。
     `scrollbar-width` 不分軸向，所以這一條也把 wrapmml 的橫向捲軸一起藏了，而那正是上面那條
     `.hscroll` 存在的理由。 */
  .lanes.shared .tarow textarea{scrollbar-width:none}
  .lanes.shared .tarow textarea::-webkit-scrollbar{display:none}
  /* ---------- 捲軸樣式（全站共用） ----------
     G1：明寫樣式，不用原生預設。overlay 捲軸的平台在 `overflow:scroll` 的元素上寬度是 0、
     thumb 只在捲動時浮現。

      **兩套屬性是互斥的，不是互補的。** Chrome 121 之後，元素上只要設了非預設的
     `scrollbar-width` 或 `scrollbar-color`，底下那幾條 `::-webkit-scrollbar` 就會被**整組
     忽略** —— 所以凹槽色（--bg2）現在寫在 `scrollbar-color` 的**第二個值**裡。webkit 那幾條
     留著當舊弓引擎的退路。

     生效範圍是**明列**的七個，不是 `*`。刻意不收的兩個：`.lanetabs`（跟 `.tabs` 同性質）與
     `#shareBox .card`（那條 overflow 是最後一道保險）。

      抽屜那一格寫 `.drawer` 而不是 `#settings`：這一組有五條選擇器清單，而抽屜有兩個。

     `.tarow textarea` **不必寫 `:not(.shared)`** —— 上面那兩條 `.lanes.shared .tarow
     textarea`（0,2,1）權重高過這裡的（0,1,1）。

      **每一條選擇器清單裡的每一項都要自己帶後綴。** 這裡有五條清單、每條七到十項，而
     `A::-webkit-scrollbar, B, C::-webkit-scrollbar` 是合法 CSS —— 漏掉的那一項會**套到元素
     本身**，不會報錯、也不會讓整條規則失效。實際發生過：`#shareBox .list` 四條全部掉了後綴，
     於是 `width:var(--sbw)` 套到了清單上，分享框裡那張表被壓成一條 12px 寬的直線（8 筆資料
     還在、`共 8 筆` 也還在，所以看起來像資料壞掉而不像 CSS）。改這一組時逐項對後綴，
     不要只看行尾。 */
  .vscroll-track,.hscroll,.drawer,#shareBox .list,#issueBox .list,#saveBox .list,#midiBox .list,#instBox .list,.modal .card,.tarow textarea{
    scrollbar-width:thin; scrollbar-color:var(--line) var(--bg2);
  }

  #stage {
      scrollbar-width:unset; scrollbar-color:var(--line) var(--bg2);
  }
  .vscroll-track:hover,.hscroll:hover,#stage:hover,.drawer:hover,
  #shareBox .list:hover,#issueBox .list:hover,#saveBox .list:hover,#midiBox .list:hover,#instBox .list:hover,.modal .card:hover,.tarow textarea:hover{
    scrollbar-color:var(--dimmer) var(--bg2);
  }
  .vscroll-track::-webkit-scrollbar,.hscroll::-webkit-scrollbar,
  #stage::-webkit-scrollbar,.drawer::-webkit-scrollbar,
  #shareBox .list::-webkit-scrollbar,#issueBox .list::-webkit-scrollbar,#saveBox .list::-webkit-scrollbar,#midiBox .list::-webkit-scrollbar,
  #instBox .list::-webkit-scrollbar,
  .tarow textarea::-webkit-scrollbar{
    width:var(--sbw); height:var(--sbw); background:var(--bg2);
  }
  .vscroll-track::-webkit-scrollbar-thumb,.hscroll::-webkit-scrollbar-thumb,
  #stage::-webkit-scrollbar-thumb,.drawer::-webkit-scrollbar-thumb,
  #shareBox .list::-webkit-scrollbar-thumb,#issueBox .list::-webkit-scrollbar-thumb,#saveBox .list::-webkit-scrollbar-thumb,#midiBox .list::-webkit-scrollbar-thumb,
  #instBox .list::-webkit-scrollbar-thumb,
  .tarow textarea::-webkit-scrollbar-thumb{
    background:var(--line); border-radius:6px; border:3px solid var(--bg2);
  }
  .vscroll-track:hover::-webkit-scrollbar-thumb,.hscroll:hover::-webkit-scrollbar-thumb,
  #stage:hover::-webkit-scrollbar-thumb,.drawer:hover::-webkit-scrollbar-thumb,
  #shareBox .list:hover::-webkit-scrollbar-thumb,#issueBox .list:hover::-webkit-scrollbar-thumb,#saveBox .list:hover::-webkit-scrollbar-thumb,#midiBox .list:hover::-webkit-scrollbar-thumb,
  #instBox .list:hover::-webkit-scrollbar-thumb,
  .tarow textarea:hover::-webkit-scrollbar-thumb{
    background:var(--dimmer);
  }
  /* 兩條捲軸交會的角。只有 #stage 與不換行模式下的 textarea 會有。透明＝透出容器自己的背
     景；不寫的話 Chromium 會拿它自己的預設底色去畫，在深色主題上是一塊亮方塊。 */
  #stage::-webkit-scrollbar-corner,.tarow textarea::-webkit-scrollbar-corner{
    background:transparent;
  }

  /*
    MML 格式化換行開啟時（body.wrapmml，由 ui.js 切）有兩件事一起發生：

    1. textarea 改成 white-space:pre —— 這是小節尺能對齊的**前提**。代價是密集譜會出現橫向
       捲軸（實測 2 小節/行是一行 165 字；每 1 小節是 83 字）。
    2. 小節尺出現。
  */
  .barnum{display:none}
  body.wrapmml .barnum{
    display:block; flex:0 0 auto; width:46px; overflow:hidden;
    background:var(--bg2); border-right:1px solid var(--line-soft); user-select:none;
  }
  /* 字級、行高、上內距三個值都要跟 textarea 一模一樣 —— 「一行對一列」全靠它們。
     改 textarea 的排版時這裡要跟著改，不然尺會整體錯位。 */
  .barnum-in{
    padding:11px 9px 0 0; text-align:right; color:var(--dimmer); white-space:pre;
    font-family:var(--mono); font-size:13.5px; line-height:1.85; letter-spacing:.02em;
  }
  /* 不換行模式：兩層一起改成 pre，只有 textarea 加橫向捲軸（它才是捲動的那一個）。
     上色層跟著它的 scrollLeft 被 translate 推（見 ui.syncTextLayerScroll）。 */
  body.wrapmml .tarow textarea,body.wrapmml .mml-hl{white-space:pre}
  body.wrapmml .tarow textarea{overflow-x:auto}

  /* ---------- 語法上色層 ----------
     三層疊法：.hlclip 裁切、.mml-hl 被 translate 推著跟 textarea 捲、textarea 在最上面收
     輸入。textarea 的文字設成透明，畫面上的字全部是上色層畫的。

     裁切放在 .hlclip 而不是 .taclip 上：textarea 的焦點框是 outline + offset:1px，畫在自己
     的框外面，父層一裁就沒了。

      span 上只能放**不影響排版**的屬性：color / background / text-decoration。font-weight
     與 font-style 不行 —— 它們會改變 advance width（synthetic bold 尤其嚴重）。

     **opacity 雖然不影響排版，但也別用**：它是對整個 span 做合成、發生在顏色決定之後，於是
     .tk-sel 的 color 蓋不掉它。要「淡一階」就用實色。 */
  .taclip{flex:1;position:relative;display:flex;min-width:0}
  .hlclip{position:absolute;inset:0;overflow:hidden;pointer-events:none;z-index:0}
  .mml-hl{
    position:absolute; top:0; left:0; margin:0; border:0;
    color:var(--txt); overflow:visible;
  }
  /* textarea 要在上色層之上，不然它畫的 caret 會被上色層的字蓋住 */
  .taclip textarea{position:relative;z-index:1}
  /* 防呆：pre 裡的 span 一律不准帶自己的字型度量 */
  .mml-hl span{
    font:inherit;letter-spacing:inherit;word-spacing:inherit;
    font-variant-ligatures:inherit;font-feature-settings:inherit;font-kerning:inherit;
  }
  /* 文字交給上色層。caret 仍然是 textarea 畫的 —— 只有它知道游標在哪。
     caret-color 明寫成 --txt：不寫的話它跟著 color 走，而 color 現在是透明的。 */
  .tarow textarea{color:transparent;-webkit-text-fill-color:transparent;caret-color:var(--txt)}
  /* 選取底色也由上色層畫（.tk-sel），所以原生選取要讓位；兩邊都畫會疊成兩層底色。
     text-fill-color 也要明寫：全域的 textarea::selection 帶著 color:#14201a，而「元素的
     text-fill-color 蓋不蓋得住 ::selection 的 color」是個瀏覽器細節。 */
  .tarow textarea::selection{background:transparent;-webkit-text-fill-color:transparent}

  /*
    退回原生的兩種情形：hl-off（那一條 lane 太長，見 ui.MAX_HL_CHARS）與 hl-composing（IME
    組字中）。兩者都要把文字、選取底色一起還回去。

     **hl-off 掛在 .lane 上，不是 body 上** —— 「太長」是**每條各自**的判斷。
  */
  .lane.hl-off .tarow textarea,body.hl-composing .tarow textarea{
    color:inherit;-webkit-text-fill-color:currentColor;
  }
  .lane.hl-off .tarow textarea::selection,body.hl-composing .tarow textarea::selection{
    background:rgba(224,174,90,.6);color:#14201a;-webkit-text-fill-color:#14201a;
  }
  .lane.hl-off .mml-hl,body.hl-composing .mml-hl{visibility:hidden}

  /*
    MML token 的色盤。**刻意不放進 :root** —— 放進去會讓它看起來跟 --t1..--t6 是同一組東西，
    而那六個是「第 1～6 軌」的意思。

    分色的規則只有一條（詳見 mml-highlight.js 的 R）：**t l o v 是宣告，整個 token 一起上
    色；音符與 r 後面的數字是音長，一律白色。**
  */
  .mml-hl{
    --tk-t:#56c8d8;      /* 速度 */
    --tk-l:#6ecf9c;      /* 音長，& 連結同色 */
    --tk-o:#8ab4f8;      /* 八度，< > 移調同色 */
    --tk-v:#ffd479;      /* 音量 */
    --tk-n:#f7a1c4;      /* 絕對音高 —— 它的數字是半音數，跟誰都不一樣 */
    --tk-quiet:#6b7280;  /* 休止 r p，@n 樂器同色 */
  }
  .tk-t{color:var(--tk-t)}
  .tk-l{color:var(--tk-l)}
  .tk-o{color:var(--tk-o)}
  .tk-v{color:var(--tk-v)}
  .tk-n{color:var(--tk-n)}
  .tk-quiet{color:var(--tk-quiet)}
  .tk-punct{color:var(--dim)}                 /* , ; [ ] */
  .tk-dead{color:var(--dimmer)}               /* 註解、MML@ 之前、; 之後 */
  /* 唯一的紅色：parser 看不懂的字（與 mml.warn.badChar 同一個判定）。
     波浪線不吃 skip-ink —— MML 全是有下伸部的小寫字母，跳過去會斷成虛線。 */
  .tk-bad{
    color:var(--warn); text-decoration:underline wavy var(--warn);
    text-decoration-skip-ink:none; text-underline-offset:3px;
  }
  /* 超過該 lane 遊戲設定字數的那一段。跟樂器列右邊那個會變紅的字數是同一套顏色語言，
     它只說「從這裡開始貼不進遊戲」，不解釋 —— 會直接編 MML 的人自己看得懂。 */
  .tk-over{background:rgba(217,122,114,.16)}
  /* 選取（播放高亮也是它）。**寫在最後** —— 它要蓋掉上面每一種 token 的顏色，
     而它們的優先權都是一個 class，靠順序決勝。 */
  .tk-sel{background:rgba(224,174,90,.6);color:#14201a}
  /* 同一件事的另一條路。支援 CSS Custom Highlight API 時選取**不烘進 HTML**，改用它畫
     （見 ui.HL_API）—— 播放中每響一個音就要換一次選取，而我們同時有好幾條 lane，烘進去
     等於每個音都把每一條的上千個 span 砍掉重建。兩份顏色必須一模一樣：換一台瀏覽器不該
     換一個顏色。
     `::highlight()` 只吃 color / background-color / text-decoration / text-shadow，而這裡
     要的剛好只有前兩個 —— 上面那條的 `background` 簡寫在這裡是無效宣告，要寫全名。 */
  ::highlight(mml-sel){background-color:rgba(224,174,90,.6);color:#14201a}

  /* ---------- 小節刻度 ----------
     繪圖層裡的行內小節尺：一個小三角指著小節的分界，旁邊帶小節號。

     形狀是「**零寬零高**的 inline-block，內容全部絕對定位」，三個性質缺一不可：零寬（兩層的
     字元一個對一個，被推掉一格就整片錯色）、零高（不可能撐高行盒）、絕對定位的內容（完全不
     參與行內排版）。

     座標：**0 是基線**，正的往下。標記能站的地方只有行與行之間那條縫，13.5px 字 / 1.85 行高
     （= 25px 行盒）算出來是：

         +2.7px   這一行的下伸部底（g p 的腳）
         ┄┄┄┄┄  可用 11.5px  ┄┄┄┄┄
         +14.2px  下一行的字頂

     所有數字都夾在這中間，**一個 pixel 都不進字身**。

     `<i>` `<b>` 而不是 `<span>`：上面那條「span 不准帶自己的字型度量」的防呆規則是寫給 span
     的，而刻度的數字**必須**帶自己的字級。 */
  .bm{position:relative;display:inline-block;width:0;height:0;
      vertical-align:baseline;overflow:visible}
  /* 三角：純 CSS border（左右透明、底邊上色 = 尖端朝上），指著上面那道字元縫。
     translateX(-50%) 置中，就不必知道它多寬。
     `top` 要在整組 Y 之外再加一個**只給三角的**微調 —— 那是光學對齊不是幾何對齊，所以是兩
     個變數而不是一個。 */
  .bm i{
    position:absolute; left:0; transform:translateX(-50%);
    width:0; height:0; top:calc(var(--bm-top) + var(--bm-tri-dy));
    border-left:calc(var(--bm-tri-w) / 2) solid transparent;
    border-right:calc(var(--bm-tri-w) / 2) solid transparent;
    border-bottom:var(--bm-tri-h) solid var(--bm-tri-c);
  }
  /* 小節號在三角竹的右邊、跟它齊高（不是掛在它下面 —— 那條縫只有 11.5px，
     上下疊會兩個都變小）。 */
  .bm b{
    position:absolute; left:calc(var(--bm-tri-w) / 2 + var(--bm-gap));
    top:var(--bm-top); font-family:var(--mono); font-weight:600;
    font-size:var(--bm-size); line-height:1; letter-spacing:0;
    white-space:pre; color:var(--bm-num);
  }
  /* 小節線落在一個長音中途：文字裡沒有那道縫，所以整組右移半個字寬。**顏色不變**。
     數字要再多讓 2px：這裡的三角已經吃掉半個字寬，沿用一般的縫寬會貼到字。 */
  .bm.cross i{left:.42em}
  .bm.cross b{left:calc(.42em + var(--bm-tri-w) / 2 + var(--bm-gap) + 2px)}
  .mml-hl{
    --bm-tri-w:4px;      /* 三角底邊寬 */
    --bm-tri-h:4.5px;    /* 三角高，細長 */
    --bm-top:2px;        /* 整組 Y（基線下方）。三角再加 --bm-tri-dy */
    --bm-tri-dy:2px;
    --bm-gap:1.5px;      /* 三角與數字之間的縫 */
    --bm-size:7.5px;
    /* 寫死 rgba 而不是 color-mix(--dim)：這個檔案的慣例是把透明度寫進顏色裡（見 #rollbusy），
       而且刻度比號碼再淡一階。 */
    --bm-tri-c:rgba(127,155,152,.45);
    --bm-num:rgba(127,155,152,.8);
  }

  /* 對齊除錯。**不進 UI**，在 devtools 手動加 body.hl-debug 才會生效：把 textarea 的字露出
     來（半透明紅）。這是「所有字元的角色聯集 === 原文」那條不變式的人工檢查。 */
  body.hl-debug .tarow textarea{-webkit-text-fill-color:rgba(255,60,60,.55)}
  /* flex:0 0 auto —— 樂譜區的下限是量 .tabs + .status 的自然高度算出來的，
     它們被壓縮的話會變成「越量越小」的迴圈。拖到最小時被壓成 0 的是 .panes。 */
  /* `gap` 的兩個值必須分開給。`gap:18px` 等於**行距也是 18px**，而那對一個 11.5px 的單行
     資訊列來說是巨大的（兩行總高 62px，其中 18px 純粹是空的）。行距 4px 就夠。 */
  .status{
    display:flex; gap:4px 18px; flex-wrap:wrap; align-items:center; flex:0 0 auto;
    border-top:1px solid var(--line); background:var(--bg2);
    padding:9px 22px; font-family:var(--mono); font-size:11.5px; color:var(--dim);
  }
  /*
    手機上再擠一點。**必須放在 `.status` 基礎規則之後** —— 同 specificity，靠順序決勝負。
    上下內距 9→5，列距 18→12（列距縮小同時降低換行機率）。左右內距維持 22px。
  */
  @media (max-width: 576px), (max-height: 480px) {
    .status{padding:5px 22px; column-gap:12px}
  }
  .status .k{color:var(--dimmer)}
  .status .bad{color:var(--warn)}
  /* 暫存狀態靠右 —— 它是背景發生的事，不需要跟音符數搶注意力 */
  /* 暫存狀態把後面的東西一起推到右邊 —— 它們都是背景資訊，不跟音符數搶注意力 */
  .status .store{margin-left:auto;color:var(--dimmer)}
  .status .engine{color:var(--dimmer)}
  /* 條款連結。暗到不搶注意力，但在 browser 模式一直在畫面上（Google 審核要看得到） */
  .status .legal{display:inline-flex;gap:8px;align-items:center}
  .status .legal a{color:var(--dimmer);text-decoration:none}
  .status .legal a:hover{color:var(--t2);text-decoration:underline}

  /*
    app 視窗裡收掉 —— 那裡不是網頁，兩條連結掛在右下角太顯眼。入口仍在「關於」抽屜的
    法律那一組（見 _About.cshtml）。

     **不影響 OAuth 審核**：那條要求是「首頁上看得到」，審核者以 browser 模式取頁。
    條件跟 h1 那條一組，standalone 與 minimal-ui 兩個都要列。
  */
  @media (display-mode: standalone), (display-mode: minimal-ui) {
    .status .legal{display:none}
  }

  /*
    這裡原本有一組 #about 的 <details>/<summary> 樣式，服務一個從未渲染過的頁尾摺疊區塊。那
    段散文現在住在「關於」抽屜裡（.drawer .thanks 那一段），摺疊那一層不再存在。
  */
  .status .legal span{color:var(--line)}
  .pill{
    display:inline-flex;align-items:center;gap:6px;
  }
  .pill i{width:7px;height:7px;border-radius:50%;display:inline-block}

  /* ---------- 鋼琴捲軸的右鍵選單 ---------- */
  /*
    z-index 55 落在 #log（50）與 .modal（60）之間：toast 不該蓋住剛打開的選單，而對話框開著
    時選單本來就不該存在（見 rollmenu 的 onOutside）。

     **列一律單行。** 兩行式大約 311px 高，而手機橫放只有 390px。
  */
  #rollMenu{
    position:fixed; z-index:55; padding:3px;
    min-width:300px; max-width:min(360px,94vw);
    background:var(--panel); border:1px solid var(--line); border-radius:3px;
    box-shadow:0 14px 44px rgba(0,0,0,.55);
    font-size:12px;
  }
  /* 標頭寫「第 N 小節」—— 下面六列共用這一個位置，四列各自重複一次只是雜訊 */
  #rollMenu .head{
    padding:6px 10px 5px; color:var(--dimmer); font-size:11px; letter-spacing:.12em;
  }
  #rollMenu hr{margin:3px 0;border:0;border-top:1px solid var(--line-soft)}
  #rollMenu .row{display:flex;align-items:stretch}

  /* 可執行區。左緣 2px 色條是 hover 的主要訊號 —— 站上「hover 換 border-color」那一招
     在這裡用不上（列沒有邊框），而純粹換底色不夠明確。 */
  #rollMenu .act{
    flex:1 1 auto; min-width:0; display:flex; align-items:center; gap:8px;
    padding:7px 10px 7px 8px; border:0; border-left:2px solid transparent;
    border-radius:2px; background:none; color:var(--dim);
    text-align:left; cursor:pointer; line-height:1.35;
    transition:background .12s,color .12s,border-color .12s;
  }
  #rollMenu .act b{flex:0 0 auto;font-weight:400;white-space:nowrap}
  /* 位置說明，停用時換成原因。可能比空間長 —— **切掉而不是換行**，換行就破了單行式 */
  #rollMenu .act span{
    flex:1 1 auto; min-width:0; text-align:right; color:var(--dimmer); font-size:11px;
    overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
  }
  /*
    高亮只有兩種來源：**滑鼠移過去**與**鍵盤選到那一列**。「選單一開啟不能有任何一列亮著」是
    靠 rollmenu 開啟時把焦點放在**選單本體**而不是第一列。

     **所以用 `:focus`，不要「改良」成 `:focus-visible`。** 後者看的是瀏覽器的啟發式判斷，
    實測第一列照樣會亮。
  */
  #rollMenu .act:not(:disabled):hover,
  #rollMenu .act:not(:disabled):focus{
    background:var(--panel2); color:var(--txt); border-left-color:var(--t2); outline:none;
  }
  /*
    破壞性的列（刪除小節、剪下音符、刪除音符）：**滑過去整列鋪滿警告色**（2px 的色條在滑鼠
    往下掃時分辨不出來）。**平常一個紅字都沒有。** 配色照抄 `.act.danger:hover`。
  */
  #rollMenu .row.danger .act:not(:disabled):hover,
  #rollMenu .row.danger .act:not(:disabled):focus{
    background:var(--warn); color:#fff; border-left-color:var(--warn);
  }
  /* 灰字在紅底上要跟著翻深，不然 --dimmer 壓在 --warn 上幾乎讀不到 */
  #rollMenu .row.danger .act:not(:disabled):hover span,
  #rollMenu .row.danger .act:not(:disabled):focus span{color:#14201a;opacity:.75}
  /* 選單本體自己吃焦點（tabindex=-1，見 rollmenu.open），不要畫瀏覽器預設的外框 */
  #rollMenu:focus{outline:none}
  #rollMenu .row.off .act{opacity:.4;cursor:default}

  /* 步進區。**自己的底色與邊框**就是那條「看得見的分界」——
     下面兩列是破壞性的，而「想把 1 調成 2、手滑點到旁邊、它直接刪了一小節」
     是把箭頭疊在按鈕裡的經典壞法。 */
  #rollMenu .spin{
    flex:0 0 auto; display:flex; align-items:center;
    margin:2px 2px 2px 4px; padding:0 2px;
    background:var(--bg2); border:1px solid var(--line); border-radius:3px;
  }
  #rollMenu .spin i{
    min-width:20px; text-align:center; font-style:normal;
    font-family:var(--mono); font-size:12px; color:var(--txt);
  }
  /* 28×28，**刻意不照抄 #zoom .spin 的 13px 半格**：那個尺寸是「跟工具列同高」逼出來的已知
     取捨，而選單沒有那個限制。手機是長按開這個選單的，13px 的箭頭在觸控上等於只剩「執行」
     按得到 —— 而那半邊會刪掉小節。 */
  #rollMenu .spin button{
    width:28px; height:28px; padding:0; border:0; background:transparent;
    color:var(--dimmer); font-family:var(--mono); font-size:11px; line-height:1;
    cursor:pointer; display:flex; align-items:center; justify-content:center;
    transition:color .15s;
  }
  #rollMenu .spin button:not(:disabled):hover{color:var(--txt)}
  /* 到頂／到底就暗掉。上限是**動態的**（跟著剩餘小節數變），所以這是「不能再多了」
     唯一的提示 —— 跟 #zoom 那兩顆同一個理由。 */
  #rollMenu .spin button:disabled{color:var(--line);cursor:not-allowed}

  /* ---------- toast / log ---------- */
  #log{
    position:fixed; left:50%; bottom:22px; transform:translateX(-50%);
    max-width:min(680px,92vw); background:#1d2e2c; color:var(--txt);
    border:1px solid var(--line); border-left:3px solid var(--warn);
    padding:12px 16px; border-radius:3px; font-size:13px;
    box-shadow:0 10px 40px rgba(0,0,0,.5); display:none; z-index:50;
    /* 它釘在下緣、往上長，所以太長的訊息會**從上面長出畫面**（合併的結果摘要、
       解析失敗的原因都可能很長）。跟對話框同一條規則：高過畫面就自己捲。 */
    max-height:calc(100vh - 44px);
    max-height:calc(100dvh - 44px);
    overflow-y:auto; overscroll-behavior:contain;
  }
  #log code{font-family:var(--mono);font-size:12px;color:var(--t1)}

  #log button{background:none;border:0;color:var(--dim);cursor:pointer;float:right;margin-left:12px}

  /* ---------- 對話框 ---------- */
  /*  新增對話框時 markup 要掛 `class="modal"` —— 漏掉的後果是它**開站就顯示在畫面上**
     而且完全沒有樣式。**JS 也靠它**：捲軸的鍵盤問「有沒有 .modal.on」來決定要不要停手，所以
     漏掉還會多一個症狀 —— 那個框開著時 Delete 會刪到背後的音符。 */
  .modal{
    position:fixed; inset:0; z-index:60; display:none;
    place-items:center; background:rgba(6,14,15,.72);
  }
  .modal.on{display:grid}
  /*
     **卡片永遠不准高卜過畫面。** `.modal` 是 `place-items:center`，被置中的東西溢出時**上下
    兩端都會被裁掉、而且捲不到**（實測 `#mergeBox` 約 865px，手機橫放只有 390px）。

    `100dvh` 而不是 `100vh`：後者是**網址列收起來時**的高度。
  */
  .modal .card{
    width:min(620px,92vw); background:var(--panel); border:1px solid var(--line);
    border-radius:4px; padding:18px; display:flex; flex-direction:column; gap:12px;
    box-shadow:0 20px 60px rgba(0,0,0,.6);
    max-height:calc(100vh - 24px);
    max-height:calc(100dvh - 24px);
    overflow-y:auto;
    /* 捲到底之後不要把捲動接力給後面的頁面（手機上很容易一路把編輯器捲走） */
    overscroll-behavior:contain;
  }
  .modal h2{margin:0;font-family:var(--display);font-weight:400;font-size:19px}
  .modal p{margin:0;color:var(--dim);font-size:12px}
  #delBox .card{width:min(420px,92vw)}
  /* 500 而不是 420：兩欄各要裝一段說明小字，跟 #mergeBox 加寬到 500 是同一個理由
     （420 會讓每一行都折）。 */
  #clipBox .card{width:min(500px,92vw)}
  /* .hint 在側欄是區塊級的說明，這裡沿用同一個樣式 */
  #clipBox .hint{margin:0}

  /* 兩個群組左右分開：radio 其實只管貼上，混在一起看起來像在管三顆按鈕。
     **不重用 .row** —— 那個 class 這個檔案裡有 9 處在用。 */
  #clipCols{display:grid;grid-template-columns:1fr 1fr;gap:18px}
  #clipCols > div{display:flex;flex-direction:column;gap:8px;min-width:0}
  /* 直線用 .sect + .sect 那條橫線的同一個 token，只是轉了 90 度。
     padding-left 跟 gap 一起把兩欄真正拉開。 */
  #clipCols > div + div{border-left:1px solid var(--line-soft);padding-left:18px}
  /* 按鈕與它的說明綁成一組（gap 比欄的 8px 小），這樣「說明屬於上面那一顆」是間距在說。
     （button.act 本身已經是 width:100%。） */
  #clipCols .cbtn{display:flex;flex-direction:column;gap:3px}
  #clipCols small{color:var(--dimmer);font-size:11px;line-height:1.45}
  /* 貼上按鈕推到左欄底部，跟右欄第二顆按鈕的底緣對齊。 */
  #clipCols .grow{margin-top:auto}

  /*
    窄螢幕收成上下。460 是從**這張卡片自己**推出來的：card 是 min(500px,92vw)，92vw 掉到
    420px 以下時兩欄各剩不到 190px。460×0.92 ≈ 423。

    不沿用 576 也不沿用 640 —— 那兩個管的是別的事。
  */
  @media (max-width: 460px) {
    #clipCols{grid-template-columns:1fr;gap:15px}
    #clipCols > div + div{
      border-left:0; padding-left:0;
      border-top:1px solid var(--line-soft); padding-top:15px;
    }
    /* 單欄時「推到底部」沒有意義（沒有另一欄要對齊），留著只會多一段空白。 */
    #clipCols .grow{margin-top:0}
  }
  #delBox p b{color:var(--warn)}
  /* 危險動作用警示色。不是「不能按」，是「按了就回不去」 */
  .act.danger,#dangerZone .act{border-color:var(--warn);color:var(--warn)}
  .act.danger:hover,#dangerZone .act:hover{background:var(--warn);color:#fff}
  #dangerZone[hidden]{display:none}

  /* ---------- 版本更新紀錄 ---------- */
  /* 單顆關閉鈕。`.row` 是兩欄 grid 而且有 9 處在用（不能改），所以這裡自己壓成一欄
     —— 同 #pasteBox[data-mode="out"] 的作法。 */
  #changelogBox .row{grid-template-columns:1fr}

  /*
     **這一組 <details>/<summary> 樣式是重建的，不是還原。** 站上原本那組 #about 的 details
    樣式在散文搬進「關於」抽屜時整組刪掉了，而這裡的條件完全不同：這一組是為了對抗
    `.modal .card` 的高度上限（`100dvh - 24px`，手機橫放只有 390px）。

    記號自己畫、不用原生 marker —— 同「所有捲軸的樣式：明寫，不用原生預設」那條規則。
    `list-style:none` 與 `::-webkit-details-marker` 兩條都要（Safari 到現在還吃後者）。

     **這裡不能畫 Unicode 三角形 `▸`／`▾`。** 目標（四個瀏覽器同一個字形）是對的，但
    Unicode 字元的字形是**字型**決定的，而這個站的 `--sans` 有四套 —— 那個記號在四個語言下
    長得不一樣，而 CJK 字型畫幾何符號常常是全寬。

    改用站上既有的 Font Awesome webfont（自架 woff2），走 `::before` 而不是在 markup 裡放
    `<i class="fa-chevron-down">`：後者要改 11 處 `<summary>`、每處補 `aria-hidden`。
  */
  .changelog{
    /* 記號的寬度。**內容縮排是從這個值算出來的**（見下面 details > *:not(summary)）—— 兩個數
       字必須一致，所以讓 calc 去算。同 .mml-hl 的 --bm-* 那組作法。 */
    --mark:13px;
    display:flex; flex-direction:column;
  }
  /* 分隔線用 border-top 而不是 border-bottom：最後一版底下已經有關閉鈕那一排，
     再加一條線會變成兩條平行線夾著一段空白。第一條靠 :first-child 收掉。 */
  .changelog details{border-top:1px solid var(--line-soft)}
  .changelog details:first-child{border-top:0}
  .changelog summary{
    display:flex; align-items:baseline; gap:8px;
    list-style:none; cursor:pointer; padding:9px 0;
    color:var(--t2); font-size:12px;
  }
  .changelog summary::-webkit-details-marker{display:none}
  /*
     **一定要是「一個字形轉 180°」，不是「開一個字形、關另一個字形」。** `content` 不是可
    轉場的屬性 —— 改成 `content:"\f077"`（fa-chevron-up）靜態畫面完全正確，但**動畫會靜靜
    地死掉**。

    上下箭頭（∨ 收 / ∧ 開）而不是右／下：右轉下的視覺差只是同一個小三角轉 90°，掃過十一行
    分不出哪個是開的。12px 是站上既有的數字（summary 本文 12px、li 12px）。

     `--dim` 而不是 `--dimmer`：這個箭頭是**傳達狀態的 UI 元件**，WCAG 1.4.11 的門檻是
    3:1，而 `--dimmer` 在 `--panel` 上只有 **2.93:1**（`--dim` 是 5.08:1）。不用 `--t2`：那
    是站上的互動／選中色。同一行的日期 `.when` **刻意留在 --dimmer**。

    `text-align:center` + `line-height:1` 讓字形在 13px 的盒子裡置中（rotate 是繞盒子中心
    轉的）；`align-self:center` 而 summary 是 baseline。
  */
  .changelog summary::before{
    content:"\f078";                                  /* fa-chevron-down */
    font-family:"Font Awesome 7 Free"; font-weight:900;
    flex:0 0 auto; width:var(--mark); text-align:center;
    font-size:12px; line-height:1; align-self:center;
    color:var(--dim);
    transition:transform .16s ease, color .15s;
  }
  .changelog details[open] > summary::before{transform:rotate(180deg)}
  .changelog summary:hover{color:var(--txt)}
  .changelog summary:hover::before{color:var(--t2)}
  .changelog summary:focus-visible{outline:2px solid var(--t1);outline-offset:2px}
  /* 版本號走等寬字 —— 十個 v1.0.x 疊在一起，比例字型會讓小數點對不齊。 */
  .changelog summary b{flex:0 0 auto;font-family:var(--mono);font-weight:400;color:var(--t1)}
  .changelog .when{flex:0 0 auto;font-family:var(--mono);font-size:11px;color:var(--dimmer)}

  /*
    ── 展開／收合的動畫 ──

     **`transition-behavior:allow-discrete` 是「關閉時也有動畫」的唯一來源。** 原生
    <details> 一關，內容就不再 render（UA 在 ::details-content 上掛 `content-visibility:
    hidden`），動畫追不到 —— 拿掉它，收合會變回瞬間消失。`content-visibility` 必須列在
    transition 清單裡才吃得到。

     **沒有高度展開動畫，而且是刻意的。** 真正的 `height:0 → auto` 要
    `interpolate-size:allow-keywords`，而它到 2026/08 **只有 Chromium**。`::details-content`
    本身是 Baseline，所以做在它上面的 opacity + transform 三引擎都拿得到。

    `scaleY` 而不是家規的 `translateY(6px)`：高度是**瞬間**跳完的，而這一塊是原地展開。

     **全部走 transition 不走 @keyframes，是為了免費拿到 reduced-motion**：這個檔案結尾的
    全域規則只有 `*{transition:none!important}`，不管 animation。
  */
  .changelog details::details-content{
    opacity:0; transform:scaleY(.97); transform-origin:top center;
    transition:opacity .16s ease, transform .16s ease, content-visibility .16s;
    transition-behavior:allow-discrete;
  }
  .changelog details[open]::details-content{opacity:1;transform:scaleY(1)}

  /* 內容縮排對齊 summary 的文字（記號 --mark + gap 8px），展開之後標題與內容是一條線。用
     calc 從 --mark 算出來，不寫死。 */
  .changelog details > *:not(summary){margin:0 0 9px calc(var(--mark) + 8px)}
  /*  這個 17px 跟上面的 --mark **無關**，不要跟著改。它是 <li> 項目符號自己的縮排，跟記號
     寬度沒有因果關係（數字曾經相同純粹是巧合）。 */
  .changelog ul,.changelog ol{padding-left:17px;display:flex;flex-direction:column;gap:4px}
  .changelog li{color:var(--dim);font-size:12px;line-height:1.55}
  /* 粗體是「這一條是這一版的重點」，用 --t2 而不是 --txt：一版裡有三四條重點，
     全部用最亮的白會讓整段沒有層次。 */
  .changelog b{font-weight:500;color:var(--t2)}
  .changelog code{font-family:var(--mono);font-size:11px;color:var(--t1)}

  /*
    唯一一條 bug 修復。左緣的警示色條是它跟上面「新增了什麼」那份清單的**唯一**區別。用
    border-left 而不是換底色：站上「不可逆／要注意」一律是 --warn 的邊框，底色留給 --sel。
  */
  .changelog .fix{
    display:flex; flex-direction:column; gap:6px;
    padding:10px 12px; border:1px solid var(--line);
    border-left:2px solid var(--warn); border-radius:3px;
  }
  .changelog .fix > b{color:var(--warn)}
  .changelog .fix ul,.changelog .fix ol,.changelog .fix p{margin:0}

  /* ---------- 移調對話框 ---------- */
  #transBox .card{width:min(420px,92vw)}
  #transBox .tgroup{display:flex;flex-direction:column;gap:7px}

  /* ---------- 合併音軌對話框 ---------- */
  /* 跟壓縮優化同寬（不是移調竹的 420）：五個方式各有一段說明，420 會每一行都折。
     範圍那一排、方式那五顆、字數行、警語行**全部共用優化的規則** —— 見下面「壓縮優化對話
     框」那一段的選擇器清單。 */
  #mergeBox .card{width:min(500px,92vw)}
  #mergeBox .tgroup{display:flex;flex-direction:column;gap:7px}
  /* 「和弦1（64 個音符）→ [和弦2 ▾]」。箭頭撐開兩邊，讀起來是一個流向。 */
  #mergeFlow{display:flex;align-items:center;gap:10px}
  #mergeFlow .from{font-size:12px;color:var(--txt);white-space:nowrap}
  #mergeFlow i{color:var(--dimmer);font-size:11px;flex:0 0 auto}
  /* 下拉吃掉剩下的寬度 —— 軌名長度不一（「主旋律」vs「輔助9」），
     固定寬度會在某些選項上留一段空白。 */
  #mergeTo{
    flex:1 1 auto; min-width:0;
    background:var(--panel); color:var(--txt);
    border:1px solid var(--line); border-radius:3px;
    font-size:12px; padding:5px 8px; cursor:pointer;
    width:200px;
  }
  #mergeTo:focus-visible{outline:2px solid var(--sel);outline-offset:1px}
  /* 範圍：四顆連成一段，跟工具列的「全部／部份」同一套語言 —— 全部樂譜／目前樂譜／目前音軌
     ／已選擇音符（見 ui.scopeTargets）。`flex:1` 讓它們均分寬度。 */
  #transScope{display:flex}
  #transScope button{
    flex:1; padding:6px 4px; background:var(--panel2); border:1px solid var(--line);
    border-right-width:0; color:var(--dim); font-size:12px; cursor:pointer;
  }
  #transScope button:first-child{border-radius:3px 0 0 3px}
  #transScope button:last-child{border-right-width:1px;border-radius:0 3px 3px 0}
  #transScope button.on{background:var(--sel);color:#fff;border-color:var(--sel)}
  #transScope button:disabled{opacity:.35;cursor:not-allowed}
  /* 24 顆：兩欄各 12 列。grid-auto-flow:column 讓「升」填滿左欄才換右欄 */
  #transKeys{
    display:grid; grid-template-columns:1fr 1fr; grid-template-rows:repeat(12,auto);
    grid-auto-flow:column; gap:3px;
  }
  #transKeys button{
    padding:4px 7px; background:var(--panel2); border:1px solid var(--line);
    border-radius:3px; color:var(--dim); font-size:12px; cursor:pointer;
    text-align:left; white-space:nowrap;
  }
  #transKeys button:not(.on):hover{border-color:var(--t2);color:var(--txt)}
  #transKeys button.on{background:var(--sel);color:#fff;border-color:var(--sel)}
  #transKeys button em{font-style:normal;opacity:.75;font-size:11px}
  /* 擋下來的原因寫在這裡（音域超出、有唯讀軌）。留著高度避免按鈕跳動。 */
  #transNote{min-height:1.5em;color:var(--warn)!important;font-size:12px}

  /* ---------- 速度／拍速對話框 ---------- */
  #tempoBox .card{width:min(460px,92vw)}
  #tempoBox .tgroup{display:flex;flex-direction:column;gap:7px}
  /* 速度基準的下拉 + 同步鈕，各佔一半。`flex:1 1 50%` 而不是 select 給固定寬：框寬是
     min(460px,92vw)。`min-width:0` 是 select 需要的 —— 它的固有寬度來自最長的那個選項。 */
  #tempoMasterRow{display:flex;align-items:stretch;gap:8px}
  #tempoMasterRow select{flex:1 1 50%;min-width:0}
  /* 沿用 #tempoTry 的語彙（細框、次級色、hover 才亮），但**沒有 .on** —— 它是瞬時動作不是
     狀態。`justify-content:center` 是因為它跟左邊的 select 等寬。 */
  #tempoSyncAll{
    flex:1 1 50%; display:inline-flex; align-items:center; justify-content:center; gap:6px;
    background:none; border:1px solid var(--line); border-radius:3px;
    color:var(--dim); font-size:11px; padding:5px 9px; cursor:pointer; white-space:nowrap;
  }
  #tempoSyncAll i{font-size:10px}
  #tempoSyncAll:hover{border-color:var(--t2);color:var(--txt)}
  #tempoSyncAll:focus-visible{outline:2px solid var(--t1);outline-offset:1px}
  /* 數字框 + 滑桿 + 試聽鈕一列。滑桿吃掉剩下的寬度。 */
  #tempoRow{display:flex;align-items:center;gap:10px}
  #tempoNum{
    flex:0 0 auto; width:76px; text-align:center;
    background:var(--panel2); color:var(--txt); border:1px solid var(--line);
    border-radius:3px; padding:6px 8px; font-family:var(--mono); font-size:13px;
  }
  #tempoNum:focus-visible{outline:2px solid var(--t1);outline-offset:1px}
  /* min-width:0 是必要的：range 的預設固有寬度會把它撐出容器 */
  #tempoSlider{flex:1 1 auto;min-width:0;accent-color:var(--t2);cursor:pointer}
  /* 試聽鈕：按下去是「正在響」，所以用 .on 的實心底表示狀態而不是瞬時動作 */
  #tempoTry{
    flex:0 0 auto; display:inline-flex; align-items:center; gap:6px;
    background:none; border:1px solid var(--line); border-radius:3px;
    color:var(--dim); font-size:11px; padding:5px 9px; cursor:pointer; white-space:nowrap;
  }
  #tempoTry:not(:disabled):not(.on):hover{border-color:var(--t2);color:var(--txt)}
  #tempoTry.on{background:var(--sel);border-color:var(--sel);color:#fff}
  #tempoTry:disabled{opacity:.4;cursor:not-allowed}
  /* 「原本是 T113」「第 3、5 軌需要切段」這類事前告知。**不是警告**，所以不用警示色
     —— 它是使用者按下確認之前該讀完的資訊，跟 #transNote 的「擋下來的原因」不同。 */
  #tempoInfo{min-height:1.5em;font-size:12px;color:var(--dim)}
  #tempoInfo b{color:var(--txt);font-weight:500}
  #tempoNote{min-height:1.5em;color:var(--warn)!important;font-size:12px}
  /* 移除鈕跟取消／執行同一列，但推到最左邊。用 flex 而不是 grid 的欄：移除鈕**平常是隱藏
     的**，而 display:none 會讓它從 grid 退出、後面兩顆整排位移。 */
  #tempoBox .row{display:flex;gap:8px;align-items:center}
  #tempoBox .row .act{flex:0 0 auto;width:auto;padding:9px 16px}
  #tempoCancel{margin-left:auto}
  #tempoDel{border-color:var(--warn);color:var(--warn)}
  #tempoDel:hover{background:var(--warn);color:#14201a}
  #tempoDel[hidden]{display:none}

  /* ---------- 力度／音量對話框 ---------- */
  #velBox .card{width:min(420px,92vw)}
  #velBox .tgroup{display:flex;flex-direction:column;gap:7px}
  /* 範圍裡現在的力度分布。等寬字 —— 它是數字，要能一眼比大小。 */
  #velRange{font-family:var(--mono);font-size:12px;color:var(--dim)}
  #velRange b{color:var(--txt);font-weight:500}
  /* 20 顆：兩欄各 10 列，跟 #transKeys 同一個排法（左欄填滿才換右欄） */
  #velKeys{
    display:grid; grid-template-columns:1fr 1fr; grid-template-rows:repeat(10,auto);
    grid-auto-flow:column; gap:3px;
  }
  #velKeys button{
    padding:4px 7px; background:var(--panel2); border:1px solid var(--line);
    border-radius:3px; color:var(--dim); font-size:12px; cursor:pointer;
    text-align:left; white-space:nowrap;
  }
  #velKeys button:not(.on):hover{border-color:var(--t2);color:var(--txt)}
  #velKeys button.on{background:var(--sel);color:#fff;border-color:var(--sel)}
  /* 會被壓平的音數。用警示色但不是擋下來 —— 使用者可以明知會壓平還是要壓
     （「全部推到最大聲」是合法的意圖），所以它是告知不是阻止。 */
  #velNote{min-height:1.5em;color:var(--warn)!important;font-size:12px}

  /* ---------- 壓縮優化對話框 ---------- */
  /* 比移調寬：三個規則各有一段說明，420px 會每一行都折 */
  #optBox .card{width:min(500px,92vw)}
  #optBox .tgroup{display:flex;flex-direction:column;gap:7px}
  /* 範圍那一排跟移調完全同一套語言，共用規則（見 #transScope 那一段）。
      #mergeScope 與 #tempoWhere 只有兩顆，沒有「目前樂譜」—— 合併的單位是一條 lane，速度
     是全曲共用的。它們共用樣式但不共用選項。 */
  #optScope,#mergeScope,#velScope,#tempoWhere{display:flex}
  #optScope button,#mergeScope button,#velScope button,#tempoWhere button{
    flex:1; padding:6px 4px; background:var(--panel2); border:1px solid var(--line);
    border-right-width:0; color:var(--dim); font-size:12px; cursor:pointer;
  }
  #optScope button:first-child,#mergeScope button:first-child,#velScope button:first-child,#tempoWhere button:first-child{border-radius:3px 0 0 3px}
  #optScope button:last-child,#mergeScope button:last-child,#velScope button:last-child,#tempoWhere button:last-child{border-right-width:1px;border-radius:0 3px 3px 0}
  #optScope button.on,#mergeScope button.on,#velScope button.on,#tempoWhere button.on{background:var(--sel);color:#fff;border-color:var(--sel)}
  #optScope button:disabled,#mergeScope button:disabled,#velScope button:disabled,#tempoWhere button:disabled{opacity:.35;cursor:not-allowed}
  /* 四個方式直排。每顆是「標題 + 說明 + 省字數」三塊，說明會折行，所以不能用
     移調那種一行一顆的緊密網格 */
  #optRules,#mergeWays{display:flex;flex-direction:column;gap:5px}
  /* 夾在無損與三個有損之間的警語。它是**分界線**，不只是一段字 —— 上面那顆不改變聲音，下面
     三顆會。（放在 radiogroup 裡面，月用 aria-describedby 掛給下面三顆，見 HTML。） */
  #optRules .hint,#mergeWays .hint{
    margin:7px 2px 4px; padding-top:9px; border-top:1px solid var(--line-soft);
    line-height:1.65;
  }
  #optRules button,#mergeWays button{
    display:grid; grid-template-columns:1fr auto; grid-template-areas:"t n" "d n";
    gap:2px 10px; align-items:center; text-align:left;
    padding:8px 11px; background:var(--panel2); border:1px solid var(--line);
    border-radius:3px; color:var(--dim); font-size:12px; cursor:pointer;
  }
  #optRules button:not(.on):not(:disabled):hover,#mergeWays button:not(.on):not(:disabled):hover{border-color:var(--t2)}
  #optRules button.on,#mergeWays button.on{background:var(--sel);color:#fff;border-color:var(--sel)}
  /* 省 0 字的方式不給按 —— 開著讓人點了才發現沒用很煩（跟移調的「已選擇音符」同理） */
  #optRules button:disabled,#mergeWays button:disabled{opacity:.4;cursor:not-allowed}
  #optRules .t,#mergeWays .t{grid-area:t;color:var(--txt);font-size:12.5px}
  #optRules button.on .t,#mergeWays button.on .t{color:#fff}
  #optRules .d,#mergeWays .d{grid-area:d;color:var(--dimmer);font-size:11.5px;line-height:1.5}
  #optRules button.on .d,#mergeWays button.on .d{color:rgba(255,255,255,.8)}
  /* 說明裡的重點（無損那顆的「一個音都不動」）。跟 .hint b 同一套語言 */
  #optRules .d b,#mergeWays .d b{color:var(--dim);font-weight:500}
  #optRules button.on .d b,#mergeWays button.on .d b{color:#fff}
  /* 省下的字數：這是使用者唯一真正在比的東西，所以給它等寬字與最強的對比。white-space:pre
     而不是 nowrap —— 兩行是 ui.js 用 \n 串起來的，nowrap 會把換行吃掉。 */
  #optRules .n,#mergeWays .n{
    grid-area:n; font-family:var(--mono); font-size:12px; color:var(--t2);
    white-space:pre; text-align:right; line-height:1.45;
  }
  #optRules button.on .n,#mergeWays button.on .n{color:#fff}
  #optRules button:disabled .n,#mergeWays button:disabled .n{color:var(--dimmer)}
  #optBox .hint b,#mergeBox .hint b{color:var(--dim);font-weight:500}
  /* 擋下來的原因／執行後的結果。留著高度避免按鈕跳動，同 #transNote */
  #optNote,#mergeNote{min-height:1.5em;color:var(--warn)!important;font-size:12px}
  /* 字數／上限。等寬字，因為它是一組會變動的數字，等寬才不會左右跳動。
     超過上限時整行變紅 —— 跟樂器列右邊那個字數同一套顏色語言。 */
  #optChars,#mergeChars{min-height:1.5em;font-family:var(--mono);font-size:12px}
  #optChars b,#mergeChars b{color:var(--t2);font-weight:500}
  #optChars.over b,#optChars.over,#mergeChars.over b,#mergeChars.over{color:var(--warn)!important}
  /* 重組壓縮的三行報告要**逐行**標紅：每條 lane 跟自己的上限比，同一次結果裡可能一條超、兩
     條沒超。上面那條規則是「容器一紅就全部紅」，所以那一行改用這個 per-row 的類別。 */
  #optChars b.bad{color:var(--warn)!important}
  #pasteBox textarea{
    height:170px; flex:none; padding:11px 13px; font-size:12.5px; line-height:1.7;
    background:var(--bg); border:1px solid var(--line); border-radius:3px;
  }
  /* 「拿出去」模式沒有東西要載入，只留一顆關閉 */
  #pasteBox[data-mode="out"] .row{grid-template-columns:1fr}
  #pasteBox[data-mode="out"] #pasteOk{display:none}

  /* ---------- 檔案：匯入 / 匯出 ---------- */
  #fileBox .card{width:min(460px,92vw) ; padding:20px 20px 40px 20px ;}
  #fileBox .sect{display:flex;flex-direction:column;gap:9px}
  /* 兩個區塊之間畫一條線。它們是兩個方向的動作，不該看起來像同一組按鈕 */
  #fileBox .sect + .sect{border-top:1px solid var(--line-soft);padding-top:15px}
  /* 拖放：整張卡片都是放置區（框裡任何地方都接得住），邊框亮起來當回饋 */
  #fileBox .card.drop{border-color:var(--t2)}
  #fileBox .hint b{color:var(--dim);font-weight:500}
  /* 讀檔失敗的原因。留在框裡而不是走 toast —— 使用者要能立刻再選一個檔案 */
  #fileBox #fileErr{color:var(--warn)}
  #fileBox #fileErr[hidden]{display:none}
  /* 檔名 + 格式一列：合起來就是最後的檔名，所以它們並排而不是分行 */
  #fileBox .fname{display:flex;gap:8px}
  #fileBox #expName{
    flex:1; min-width:0; padding:8px 10px; font-size:12.5px; font-family:inherit;
    color:var(--txt); background:var(--panel2);
    border:1px solid var(--line); border-radius:3px;
  }
  #fileBox #expName:focus-visible{outline:2px solid var(--t1);outline-offset:1px}
  /* select 的共用規則是 width:100%，這裡要讓它只佔自己需要的寬 */
  #fileBox #expFmt{flex:0 0 auto;width:auto;padding:8px 10px;text-align:center}
  /* 清空：共用規則是 width:100%，滿版的紅框按鈕在這個框裡太搶戲 —— 它是最少用、而且最不該
     被誤觸的那一顆。（.sect 是 flex column，所以置中用 align-self。） */
  #fileBox #clearAll{width:auto;align-self:center;padding:8px 20px}

  /* ---------- 選擇要匯入的音軌 ---------- */
  #midiBox .card{width:min(740px,94vw)}
  #midiBox #midiName{color:var(--dimmer);font-family:var(--mono);font-size:11.5px}
  /* 16 個 channel 的清單會很長，捲在自己的框裡，對話框本身不長高 */
  #midiBox .list{
    max-height:min(50vh,400px); overflow:auto;
    border:1px solid var(--line); border-radius:3px; background:var(--bg);
  }
  #midiBox table{width:100%;border-collapse:collapse;font-size:12px}
  /* 表頭釘住：捲到第 12 列時還要看得出哪一欄是音符數 */
  #midiBox th{
    position:sticky; top:0; z-index:1; background:var(--bg2);
    font-size:11px; letter-spacing:.1em; text-transform:uppercase; color:var(--dimmer);
    font-weight:400; text-align:left; padding:7px 9px;
    border-bottom:1px solid var(--line); white-space:nowrap;
  }
  #midiBox td{padding:4px 9px;border-bottom:1px solid var(--line-soft);color:var(--dim)}
  #midiBox tbody tr:last-child td{border-bottom:0}
  /* 整列都是勾選的靶（見 filebox.js）。user-select:none 是因為列變成可點之後，
     雙擊選字會順手把它勾起來又取消 —— 頻道名沒有人需要複製走。 */
  #midiBox tbody tr{cursor:pointer;user-select:none}
  /* 四階要分得開：底色 → 停在上面 → 勾起來 → 勾起來又停在上面。
     列本身是點擊目標，「我現在會點到哪一列」必須一眼看得出來。 */
  #midiBox tbody tr:hover td{background:var(--panel);color:var(--txt)}
  #midiBox tbody tr.on td{background:var(--panel2);color:var(--txt)}
  #midiBox tbody tr.on:hover td{background:var(--line)}
  #midiBox td.n{font-family:var(--mono);color:var(--dimmer);text-align:right}
  #midiBox td.name{color:var(--txt);word-break:break-all}
  #midiBox td.cnt{font-family:var(--mono);text-align:right;white-space:nowrap}
  #midiBox select{width:auto;padding:3px 6px;font-size:12px}
  #midiBox input[type=checkbox]{accent-color:var(--t2);cursor:pointer;display:block}
  /* 打擊組：音高是鼓的編號不是音階，採出來一定是噪音。標出來、預設不勾。
     同一套樣式也給「有旋律 / 純伴奏 / 唯讀 / 超字數」那幾個小標籤用。 */
  #midiBox .drum{
    margin-left:7px; padding:1px 5px; border:1px solid var(--line);
    border-radius:2px; color:var(--dimmer); font-size:10.5px; white-space:nowrap;
  }
  /* 「有旋律」用強調色：一份 16 列的清單要能一眼掃出哪幾列有主旋律。
     「純伴奏」維持暗色 —— 兩個都上色就等於都沒上色。 */
  #midiBox .drum.lead{border-color:var(--t2);color:var(--t2)}
  #midiBox .foot{display:flex;align-items:center;gap:10px}
  #midiBox .foot .count{flex:1;font-size:12px;color:var(--dim)}
  /* 超額不擋操作，只擋匯入 —— 紅字說得出「超了多少」，灰掉的 checkbox 說不出 */
  #midiBox .foot .count.over{color:var(--warn)}

  /* ---------- 新採譜 / 追加採譜 ---------- */
  /* 方向兩邊不一樣，所以 display 那一行**不共用**：#midiMode 在一張 620px 寬的卡片上橫排
     剛好，#pasteMode 在剪貼簿框裡只佔半欄。label 與 small 的長相繼續共用。 */
  #midiMode{display:flex;align-items:center;gap:14px;flex-wrap:wrap}
  /* 直排。align-items 不置中 —— 每個 label 自己是一個「標題 + 說明」的區塊，
     置中會讓 radio 圓點跑到兩行文字的中間去。 */
  #pasteMode{display:flex;flex-direction:column;gap:8px}
  #pasteMode label,
  #midiMode label{
    display:flex; align-items:center; gap:6px;
    font-size:12.5px; color:var(--txt); cursor:pointer; user-select:none;
  }
  /* 直排時一個 label 是兩行（名稱在上、說明在下）。**radio 與名稱必須自己包一層 `.opt`**
     —— 裸文字節點會變成獨立的 flex item 自己佔一行，radio 就孤零零留在上一行。 */
  #pasteMode label{flex-direction:column;align-items:flex-start;gap:2px}
  #pasteMode .opt{display:flex;align-items:center;gap:6px}
  #pasteMode input,
  #midiMode input{accent-color:var(--t2);cursor:pointer}
  #pasteMode small,
  #midiMode small{color:var(--dimmer);font-size:11px}
  /* 說明縮排到跟名稱的左緣對齊（radio 13px + gap 6px）。 */
  #pasteMode small{padding-left:19px;line-height:1.45}
  #midiMode .hint{flex:1 0 100%;color:var(--dim);font-size:11.5px}
  /* 選了「新採譜」而且目前真的有東西時的警告。留在框裡而不日是走 toast ——
     toast 會自己消失，而這則訊息要在他按下匯入之前都看得到。 */
  #midiMode .hint.warn{color:var(--warn)}

  /* ---------- 採譜中 ---------- */
  /* 蓋住整張卡片。position:absolute 需要 .card 是定位脈絡 —— 只有這裡用得到，
     所以就地宣告，不動共用的 .card 規則。 */
  #midiBox .card{position:relative}
  /*
     忙碌中把卡片的捲動關掉。共用的 `.modal .card` 是 `overflow-y:auto`，而**絕對定位的子
    元素在捲動容器裡是跟著內容捲的** —— 使用者一捲，遮罩就滑走、底下的清單露出來還按得到。

    關掉捲動比把遮罩改成 fixed 好：fixed 會讓它蓋住整個畫面、疊在背幕上變成一片全黑。
  */
  #midiBox .card:has(#midiBusy:not([hidden])){overflow:hidden}
  #midiBox #midiBusy{
    position:absolute; inset:0; z-index:2; border-radius:4px;
    display:flex; flex-direction:column; align-items:center; justify-content:center; gap:14px;
    /* 寫死 rgba 而不是 color-mix：這個檔案其他地方（例如對話框的背幕）都是這樣寫的 */
    background:rgba(6,14,15,.86);
    /* 延遲淡入：算得快的時候它來不及出現就被移掉了，不需要任何判斷邏輯。
       只動 opacity，所以這一段也在合成器執行緒上。 */
    opacity:0; animation:midiBusyIn .2s .15s forwards;
  }
  #midiBox #midiBusy[hidden]{display:none}
  #midiBox #midiBusy p{color:var(--dim);font-size:12.5px;letter-spacing:.08em}
  #midiBox #midiBusy .notes{display:flex;gap:10px}
  #midiBox #midiBusy .notes span{
    display:block; font-size:22px; line-height:1; color:var(--t2);
    /* **只能是 transform 與 opacity。** 換成 top/margin 這個動畫就會綁回主執行緒，
       而主執行緒正在跑聲部分離 —— 音符會凍在半空中，看起來像網頁當掉了。 */
    animation:midiBusyHop .9s ease-in-out infinite;
  }
  #midiBox #midiBusy .notes span:nth-child(2){animation-delay:.12s}
  #midiBox #midiBusy .notes span:nth-child(3){animation-delay:.24s}
  #midiBox #midiBusy .notes span:nth-child(4){animation-delay:.36s}
  #midiBox #midiBusy .notes span:nth-child(5){animation-delay:.48s}
  @keyframes midiBusyIn{to{opacity:1}}
  @keyframes midiBusyHop{
    0%,100%{transform:translateY(0) scale(1);opacity:.45}
    45%    {transform:translateY(-11px) scale(1.12);opacity:1}
  }
  /* 會動的東西一律尊重這個偏好。停下來的遮罩仍然看得出「在忙」（它蓋住了卡片）。 */
  @media (prefers-reduced-motion:reduce){
    #midiBox #midiBusy .notes span{animation:none;opacity:.8}
    #midiBox #midiBusy{animation:none;opacity:1}
  }
  #midiBox .foot .act{width:auto;padding:7px 18px;flex:0 0 auto}
  /* ---------- 匯入五線譜：整理圖片 ---------- */
  /*
    卡片寬度跟 #midiBox 一樣，不是巧合：這兩個框是同一趟流程的前後兩步
    （整理圖片 → 選聲部），寬度跳一下會讓人以為換到別的地方去了。
  */
  #omrBox .card{width:min(740px,94vw);position:relative}
  #omrBox .list{
    max-height:min(50vh,400px); overflow:auto;
    border:1px solid var(--line); border-radius:3px; background:var(--bg);
  }
  #omrBox .empty{
    padding:26px 16px; display:flex; flex-direction:column;
    align-items:center; gap:12px; text-align:center;
  }
  #omrBox .empty[hidden]{display:none}
  #omrBox .empty p{margin:0;color:var(--dimmer);font-size:12px}
  #omrBox .empty .act{width:auto;padding:7px 18px}
  /*
    auto-fill 而不是固定欄數：卡片在手機上是 94vw，寫死 4 欄的話每張縮圖會窄到
    看不出是哪一頁 —— 而「看得出是哪一頁」正是這個框存在的唯一理由。
  */
  #omrBox .grid{
    display:grid; gap:10px; padding:10px;
    grid-template-columns:repeat(auto-fill,minmax(132px,1fr));
  }
  #omrBox .grid[hidden]{display:none}

  /*
    一張圖。`touch-action:none` 在手機上**非它不可** —— 不設的話瀏覽器會先把手勢判成頁面捲
    動，然後發 pointercancel 把拖曳砍掉。症狀是「按住拖，畫面就是不動」，而且沒有錯誤。
  */
  #omrBox .page{
    position:relative; touch-action:none;
    user-select:none; -webkit-user-select:none;
    border:1px solid var(--line); border-radius:3px; background:var(--bg2);
    padding:6px; display:flex; flex-direction:column; gap:5px;
  }
  #omrBox .page img{
    display:block; width:100%; height:104px; object-fit:contain;
    background:#fff; border-radius:2px;
    /* 縮圖不能被拖出去變成瀏覽器的原生圖片拖曳 —— 那會蓋掉站上自己的拖曳 */
    -webkit-user-drag:none; user-drag:none; pointer-events:none;
  }
  #omrBox .page .no{
    position:absolute; top:9px; left:9px; z-index:1;
    min-width:17px; padding:1px 4px; border-radius:2px;
    background:rgba(6,14,15,.78); color:var(--t2);
    font-family:var(--mono); font-size:11px; line-height:1.4; text-align:center;
  }
  /* 檔名是次要的：使用者排的是**頁碼**，檔名只在「這張到底是哪張」時才要看 */
  #omrBox .page .nm{
    color:var(--dimmer); font-size:10.5px; line-height:1.3;
    overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
  }
  #omrBox .page .ops{display:flex;gap:4px}
  #omrBox .page .ops button{
    flex:1 1 auto; padding:2px 0; font-size:11px; line-height:1.5;
    border:1px solid var(--line); border-radius:2px;
    background:transparent; color:var(--dimmer); cursor:pointer;
  }
  #omrBox .page .ops button:not(:disabled):hover{border-color:var(--t2);color:var(--txt)}
  #omrBox .page .ops button:disabled{opacity:.35;cursor:not-allowed}
  #omrBox .page .ops .del:not(:disabled):hover{border-color:var(--warn);color:var(--warn)}

  /* 拖著走的那一張。**只降透明度，不動版面** —— 見下面插入線的註解。 */
  #omrBox .page.dragging{opacity:.4}
  /*
    插入線。**絕對定位、不佔空間**，因為一位移就會踩到拖曳重排的經典災難：游標底下的元素換人
    → 算出別的插入點 → 再位移一次 → 抖動迴圈。（tracks.js 的分頁重排踩過同一個坑。）
  */
  #omrBox .page.dropL::before,#omrBox .page.dropR::after{
    content:""; position:absolute; top:2px; bottom:2px; width:2px;
    background:var(--t2); border-radius:1px;
  }
  #omrBox .page.dropL::before{left:-6px}
  #omrBox .page.dropR::after{right:-6px}

  /* 完成之後縮圖是唯讀的：不能拖、不能改順序、不能刪 */
  #omrBox .grid.done .page{touch-action:auto;cursor:default}
  #omrBox .grid.done .page .ops{display:none}

  /*
    一份 PDF：**一張檔案卡，不是縮圖格** —— 上面那整套的存在理由是「看得出這是第幾頁」，而
    PDF 的頁序在檔案裡面。

    `touch-action:auto` 要蓋回去：`.page` 為了拖曳設了 `none`，留著只會讓卡片區域**吃掉手機
    上的頁面捲動**（症狀是框內滑不動，而且沒有錯誤）。
  */
  #omrBox .grid.pdf{grid-template-columns:1fr}
  #omrBox .page.pdf{
    touch-action:auto; cursor:default;
    flex-direction:row; align-items:center; gap:9px; padding:9px 10px;
  }
  #omrBox .page.pdf .no{position:static; flex:0 0 auto}
  #omrBox .page.pdf .nm{flex:1 1 auto; font-size:12px}
  #omrBox .page.pdf .ops{flex:0 0 auto}
  #omrBox .page.pdf .ops button{padding:2px 9px}

  #omrBox .foot{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
  #omrBox .foot .count{flex:1;font-size:12px;color:var(--dim)}
  #omrBox .foot .count.over{color:var(--warn)}
  #omrBox .foot .act{width:auto;padding:7px 18px;flex:0 0 auto}
  #omrBox .foot .act[hidden]{display:none}
  #omrBox #omrErr{color:var(--warn)}

  /*
    辨識中的遮罩。**動畫只能動 transform 與 opacity**（同 #midiBusy）。這裡主執行緒其實是閒的
    （在等網路），但照同一條規矩寫沒有代價。
  */
  #omrBox #omrBusy{
    position:absolute; inset:0; z-index:2; border-radius:4px;
    display:flex; flex-direction:column; align-items:center; justify-content:center; gap:14px;
    background:rgba(6,14,15,.86);
    opacity:0; animation:midiBusyIn .2s .15s forwards;
  }
  #omrBox #omrBusy[hidden]{display:none}
  /* 這行字一直在變（排隊位置、第幾頁），所以要留得住兩行而不是被切掉 */
  #omrBox #omrBusy p{
    margin:0; max-width:82%; text-align:center;
    color:var(--dim); font-size:12.5px; letter-spacing:.08em;
  }
  #omrBox #omrBusy .notes{display:flex;gap:10px}
  #omrBox #omrBusy .notes span{
    display:block; font-size:22px; line-height:1; color:var(--t2);
    animation:midiBusyHop .9s ease-in-out infinite;
  }
  #omrBox #omrBusy .notes span:nth-child(2){animation-delay:.12s}
  #omrBox #omrBusy .notes span:nth-child(3){animation-delay:.24s}
  #omrBox #omrBusy .notes span:nth-child(4){animation-delay:.36s}
  #omrBox #omrBusy .notes span:nth-child(5){animation-delay:.48s}
  @media (prefers-reduced-motion:reduce){
    #omrBox #omrBusy .notes span{animation:none;opacity:.8}
    #omrBox #omrBusy{animation:none;opacity:1}
  }

  /* ---------- 分享 ---------- */
  /*
    矮螢幕的處理：10 列的清單讓卡片長到約 860px，而 768p 的筆電只有 768。讓**清單**縮，而不
    是讓卡片捲 —— 卡片捲的話置底竹的分頁工具列會掉到捲動摺線下面。
  */
  /* max-height / overflow-y 已經提到共用的 `.modal .card` 上了（十二個框都需要，
     不是這一個特有的）—— 這裡只剩寬度。 */
  #shareBox .card{width:min(660px,94vw)}
  #shareBox #shareBody{
    display:flex; flex-direction:column; gap:12px;
    /* min-height:0 是 flex 子項能真的縮下去的前提（預設 min-height:auto 會擋住） */
    flex:1 1 auto; min-height:0;
  }
  /*
    這一條不能省：上面的 display:flex **會蓋掉 hidden 屬性的 display:none**（CSS 宣告的優先
    順序高於 HTML 屬性的預設樣式）。少了它，未登入時整個分享內容還是畫在登入提示下面 ——
    而且 element.hidden 讀起來是 true，所以只檢查屬性的測試會過。
  */
  #shareBox #shareBody[hidden]{display:none}
  #shareBox .sect{display:flex;flex-direction:column;gap:9px}
  /* 清單所在的那一區要一起讓路，縮才傳得到 .list */
  #shareBox #shareBody .sect:last-child{flex:1 1 auto;min-height:0}
  #shareBox .sect + .sect{border-top:1px solid var(--line-soft);padding-top:15px}
  #shareBox .sect[hidden]{display:none}
  #shareBox .hint b,#shareBox p b{color:var(--dim);font-weight:500}
  /* 登入鈕是一個 <a>，要長得跟 .act 一樣（.act 的選擇器只吃 button） */
  #shareBox a.act{
    width:100%; background:var(--panel2); border:1px solid var(--line);
    border-radius:3px; padding:9px 11px; text-align:center; font-weight:500;
    color:var(--dim); text-decoration:none; cursor:pointer;
  }
  #shareBox a.act:hover{border-color:var(--t2);color:var(--txt)}

  /*
    壓縮結果要**預留高度**。不佔位的話底下整個已分享清單會往下跳一段。兩行：一行壓縮字數、
    一行截斷警告（沒截斷時那行是空的）。
  */
  #shareBox .result{min-height:40px;display:flex;flex-direction:column;gap:3px}
  #shareBox #shareStat{font-family:var(--mono);font-size:12px}
  #shareBox #shareStat.saved{color:var(--t2)}
  #shareBox #shareCut{color:var(--warn)}
  #shareBox #shareErr{color:var(--warn)}
  #shareBox #shareErr[hidden]{display:none}
  /* 輔助軌不會被分享的事前警告。用 --warn 而不是 --dimmer：它講的是「送出去的
     東西跟你看到的不一樣」，跟 #shareCut 同一類，不該長得像一般說明文字。 */
  /* `#shareDrop`（「其他分頁不會被送出去」的橘字警告）退役了 —— 它在「一條連結 =
     一整場」做出來之後就變成假的，見 sharebox.js 裡那段「退役」的說明。 */

  #shareBox .urlrow{display:flex;gap:8px}
  #shareBox .urlrow input{
    flex:1; min-width:0; padding:9px 11px; background:var(--bg);
    border:1px solid var(--line); border-radius:3px;
    font-family:var(--mono); font-size:12px; color:var(--txt);
  }
  #shareBox .urlrow input::placeholder{color:var(--dimmer);font-family:var(--sans)}
  #shareBox .urlrow .act{width:auto;flex:0 0 auto;padding:9px 16px}

  /*
    清單高度是**固定**的 —— 最後一頁只有 3 筆時，置底的分頁工具列不能往上跳。514px = 表頭 32
    + 10 列 × 48.2。height 而不是 min-height：後者會贏過 max-height，矮螢幕上就壓不下來。
  */
  #shareBox .list{
    flex:0 1 514px; min-height:150px; overflow:auto;
    border:1px solid var(--line); border-radius:3px; background:var(--bg);
  }
  #shareBox table,#issueBox table{width:100%;border-collapse:collapse;font-size:12px}
  #shareBox th,#issueBox th{
    position:sticky; top:0; z-index:1; background:var(--bg2);
    font-size:11px; letter-spacing:.1em; text-transform:uppercase; color:var(--dimmer);
    font-weight:400; text-align:left; padding:7px 9px;
    border-bottom:1px solid var(--line); white-space:nowrap;
  }
  /* 上下 9px = 列高 42px。這一列有一顆不可復原的「刪除」，靶大一點是安全問題，
     不只是美觀問題。 */
  #shareBox td,#issueBox td{padding:9px;border-bottom:1px solid var(--line-soft);color:var(--dim)}
  #shareBox tbody tr:last-child td,#issueBox tbody tr:last-child td{border-bottom:0}
  #shareBox tbody tr:hover td,#issueBox tbody tr:hover td{background:var(--panel)}
  /* 剛產生（或剛重複命中）的那一列。指給使用者看「你要的就是這個」 */
  #shareBox tbody tr.hit td,#issueBox tbody tr.hit td{background:var(--panel2);color:var(--txt)}
  #shareBox td.n,#issueBox td.n{font-family:var(--mono);color:var(--dimmer);text-align:right;width:1%}
  #shareBox td.link{font-family:var(--mono);color:var(--txt);white-space:nowrap;width:1%}
  #shareBox td.sum{white-space:nowrap;width:1%}
  #shareBox td.when{font-family:var(--mono);white-space:nowrap;width:1%}
  #shareBox td.ops{white-space:nowrap;width:1%}
  #shareBox td.empty,#issueBox td.empty{
    color:var(--dimmer); text-align:center; padding:28px 9px; border-bottom:0;
  }
  /* 「製作影片」是 <a> 不是 <button>（中鍵、Ctrl+點、長按都要能開新分頁），所以這一組選擇器
     同時吃兩種標籤 —— 少了 a 那一半，那顆會變成一個沒有框、字級也不同的裸連結。 */
  #shareBox td.ops button,
  #shareBox td.ops a{
    display:inline-block; text-decoration:none; line-height:normal;
    background:none; border:1px solid var(--line); border-radius:3px;
    color:var(--dim); font-size:12px; padding:4px 9px; cursor:pointer;
    margin-right:4px;
  }
  #shareBox td.ops button:last-child,
  #shareBox td.ops a:last-child{margin-right:0}
  #shareBox td.ops button:hover,
  #shareBox td.ops a:hover{border-color:var(--t2);color:var(--txt)}
  #shareBox td.ops button.del:hover{border-color:var(--warn);color:var(--warn)}

  /* 置底工具列 */
  #shareBox .foot,#issueBox .foot{display:flex;align-items:center;gap:8px}
  #shareBox .foot .count,#issueBox .foot .count{flex:1;font-size:12px;color:var(--dimmer)}
  #shareBox .foot .pg,#issueBox .foot .pg{
    background:none; border:1px solid var(--line); border-radius:3px;
    color:var(--dim); font-size:14px; line-height:1; padding:5px 11px; cursor:pointer;
  }
  #shareBox .foot .pg:not(:disabled):hover,#issueBox .foot .pg:not(:disabled):hover{border-color:var(--t2);color:var(--txt)}
  #shareBox .foot .pg:disabled,#issueBox .foot .pg:disabled{opacity:.3;cursor:default}
  #shareBox .foot .pgno,#issueBox .foot .pgno{
    font-family:var(--mono); font-size:12px; color:var(--dim);
    min-width:58px; text-align:center;
  }

  /*
     **清單框比分享框寬。** 「問題」那一欄是一整句話（「4 個音的時值寫不出來」），620px
    之下每一列都會折成兩行，10 列就變成 20 行 —— 而這張表的用途是**掃**，折行會讓眼睛
    沒有一條可以順下來的邊。

    `.list` 的高度不寫死：這裡的列高比分享框矮（沒有不可復原的動作，靶不必那麼大），
    交給 `.card` 的 max-height 去夾。
  */
  #issueBox .card{width:min(720px,94vw)}
  #issueBox .list{
    flex:0 1 auto; min-height:120px; max-height:52vh; overflow:auto;
    border:1px solid var(--line); border-radius:3px; background:var(--bg);
  }
  /* 樂譜名可能很長（使用者自己取的），讓它吃掉剩下的寬度並在必要時截斷 —— 其餘四欄
     全部 `width:1%` + `nowrap`，是這張表唯一該伸縮的一欄。 */
  #issueBox td.where{color:var(--txt);max-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  #issueBox td.bar{font-family:var(--mono);white-space:nowrap;width:1%;text-align:right}
  #issueBox td.what{white-space:nowrap;width:1%}
  #issueBox td.ops{white-space:nowrap;width:1%}
  #issueBox td.ops button{
    background:none; border:1px solid var(--line); border-radius:3px;
    color:var(--dim); font-size:12px; padding:4px 9px; cursor:pointer;
  }
  #issueBox td.ops button:hover{border-color:var(--t2);color:var(--txt)}
  /* 沒量到音域的那幾個分頁。跟 `p` 的暗色再暗一階 —— 它是註腳不是內容。 */
  #issueBox #issueSkip{color:var(--dimmer);font-size:11px}

  /* ---------- 設定使用樂器 ---------- */
  /*
    620px 就夠：一列是「編號＋名稱」加兩顆窄鈕，最長的名字（Festival Bass Drum 之類）
    離折行還很遠。不加寬是刻意的 —— 這張清單要**掃**，短一點眼睛走得快。

    `.list` 的高度交給 max-height 夾，跟 #issueBox 同一套：說明與取消／確定不在 .list 裡，
    所以它們永遠看得到，不必捲到底才找得到確定鈕。
  */
  #instBox .list{
    flex:0 1 auto; min-height:140px; max-height:56vh; overflow-y:auto;
    border:1px solid var(--line); border-radius:3px; background:var(--bg);
    overscroll-behavior:contain;
  }
  /* 三欄：名稱吃掉剩下的寬度，兩顆鈕各自貼著自己的字。`1fr` 配 `min-width:0` 才截得斷。 */
  .instrow{
    display:grid; grid-template-columns:1fr auto auto; gap:8px; align-items:center;
    padding:5px 9px; border-bottom:1px solid var(--line-soft);
  }
  .instrow:last-child{border-bottom:0}
  .instrow .nm{
    font-family:var(--mono); font-size:12px; color:var(--txt);
    min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
  }
  /* 預設樂器那一列。**整列上色而不是只把字變色** —— 六十幾列裡要一眼找到它，
     而一整條色帶掃得到，一個變色的字掃不到。配色沿用 `--sel`（「這顆是選中的」）。 */
  .instrow.on{background:var(--sel)}
  .instrow.on .nm{color:#fff}
  /* 隱藏的變灰。整列（含按鈕文字）都暗下去，不只名稱 —— 那一列整個是「不參與」的狀態。 */
  .instrow.off .nm{color:var(--dimmer)}
  /*
    列內的兩顆鈕。**不能直接用 `.act`** —— 那個 class 是 `width:100%`（抽屜裡每一顆都是
    整排寬的），照抄會把名稱擠成零寬。
  */
  .instrow .act.tiny{
    width:auto; padding:4px 9px; font-size:11px; white-space:nowrap;
    background:var(--panel2); color:var(--dim);
  }
  .instrow .act.tiny:not(:disabled):hover{border-color:var(--t2);color:var(--txt)}
  /* 高亮那一列的按鈕要坐在深色底上，`--panel2` 在上面會糊掉。 */
  .instrow.on .act.tiny{background:transparent;border-color:rgba(255,255,255,.35);color:#fff}
  /* 灰掉的兩種來源長得一樣是對的：「這一列已經是預設了」與「預設不能隱藏」都是
     「這顆現在按不動」。理由由 title 講（見 instbox.js）。 */
  .instrow .act.tiny:disabled{opacity:.4;cursor:not-allowed}

  #shareDelBox .card{width:min(460px,92vw)}
  #shareDelBox p b{color:var(--warn)}
  #shareDelBox .what{
    font-family:var(--mono); font-size:12px; color:var(--txt);
    background:var(--bg); border:1px solid var(--line); border-radius:3px; padding:9px 11px;
  }

  /* ---------- 本機存檔 ---------- */
  /*
    版面骨架整套沿用分享框（見那邊「矮螢幕的處理」那段）：卡片不捲，讓**清單**縮，這樣置底的
    批次工具列永遠看得到 —— 那裡有一顆不可復原的「刪除選取」。這個框有七欄，所以卡片寬一點。
  */
  #saveBox .card{
    width:min(760px,95vw);
    /* 這一條 flex 鏈（card → .sect → .list）就是為了讓縮的地方落在清單上 */
    display:flex; flex-direction:column; gap:12px;
  }
  /*
    本機分頁的常駐警告。**不是 .hint** —— 這一句講的是資料會不見。

     **class 叫 `lede` 而不是 `sub`。** `.sub` 是標題列站名副標的 class，**576px 以下
    `display:none`** —— 共用它會讓這則警告在手機上**整個消失**。
  */
  #saveBox .lede{
    margin:0; font-size:12px; line-height:1.7; color:var(--dim);
  }
  #saveBox .lede b{color:var(--warn);font-weight:500}
  /*
    `flex:0 0 auto` 從 .lede 搬到這裡，因為現在是這一層當 flex 子項。卡片是 column flex，矮螢
    幕上清單讓路時沒有它的話這一段會被壓扁 —— 而被壓扁的是「你的檔案會不見」那則警告。
  */
  #saveBox .notes{flex:0 0 auto}
  #saveBox .sect{display:flex;flex-direction:column;gap:9px}
  /* 清單所在的那一區要一起讓路，縮才傳得到 .list */
  #saveBox .sect:last-child{flex:1 1 auto;min-height:0}
  #saveBox .sect + .sect{border-top:1px solid var(--line-soft);padding-top:15px}

  /*
    分頁列（這台電腦／Web）。**它同時就是原本那個「已儲存的檔案」的 legend**。做成底線而不是
    實心分頁：這個框裡已經有很多框線。作用中的那一條用 --t1。
  */
  #saveBox .tabrow{
    flex:0 0 auto; display:flex; gap:2px;
    border-bottom:1px solid var(--line); margin-bottom:3px;
  }
  #saveBox .stab{
    background:none; border:0; border-bottom:2px solid transparent;
    margin-bottom:-1px; padding:7px 14px; cursor:pointer;
    font-size:12px; color:var(--dimmer); font-family:inherit;
  }
  #saveBox .stab:hover{color:var(--dim)}
  #saveBox .stab.on{color:var(--txt);border-bottom-color:var(--t1)}
  #saveBox .stab:focus-visible{outline:2px solid var(--t1);outline-offset:-2px}

  /*
    ─── 切分頁時卡片不准跳高度 ─────────────────────────────────────────────

    兩塊會跟著分頁換內容，而它們本來都是**內容決定高度**。兩塊都用 grid 把候選內容疊在同一
    格解決，但**理由不同**：

    `.notes` —— 被藏起來的那一句走 `visibility:hidden` 而不是 `display:none`，所以它**照樣
    參與格子的高度計算**（釘死像素高度的話那個數字要同時容納四個語言 × 兩句說明的折行數）。

    `.swap` —— 這一格**不需要互相撐高度**（#saveBody 永遠不隱藏），疊圖只是為了讓登入提示壓
    在清單前面。
  */
  #saveBox .notes{display:grid}
  #saveBox .notes > p{grid-area:1 / 1}
  #saveBox .swap{display:grid;flex:1 1 auto;min-height:0}
  #saveBox .swap > *{grid-area:1 / 1;min-height:0}
  /*
     **這兩條是上面那套的關鍵。** `hidden` 的預設樣式是 `display:none`，而 display:none 的
    元素**不佔格子**。`visibility:hidden` 同時會把裡面的東西移出無障礙樹與 Tab 順序 —— 登入
    之後那顆登入鈕**不可以卜還 Tab 得到**。
  */
  #saveBox .notes > p[hidden]{display:block;visibility:hidden}
  #saveBox .swap > *[hidden]{visibility:hidden}

  /*
    未登入的 web 分頁：**蓋在清單前面的遮罩**，不是取代清單。半透明底寫死 rgba 而不是
    color-mix：同 #midiBusy，`.86` 也是同一個數字。

    `position:relative` 是 z-index 生效的前提：grid 子項不是 positioned element。
  */
  #saveBox #saveWebLogin{
    position:relative; z-index:2;
    display:flex; flex-direction:column; justify-content:center;
    align-items:center; gap:14px; padding:0 24px;
    background:rgba(6,14,15,.86); border-radius:3px;
  }
  #saveBox #saveWebLogin p{margin:0;font-size:12px;line-height:1.7;color:var(--dim);text-align:center}
  /* 登入鈕是一個 <a>，要長得跟 .act 一樣（.act 的選擇器只吃 button）—— 同 #shareBox */
  #saveBox a.act{
    width:auto; background:var(--panel2); border:1px solid var(--line);
    border-radius:3px; padding:9px 20px; text-align:center; font-weight:500;
    font-size:13px; color:var(--dim); text-decoration:none; cursor:pointer;
  }
  #saveBox a.act:hover{border-color:var(--t2);color:var(--txt)}

  /*
    清單 + 置底工具列。**永遠顯示，登入與否都一樣** —— 未登入時它就是一張空清單（登入提示疊
    在它前面）。所以這裡**沒有也不該有** `[hidden]{display:none}`。
  */
  #saveBox #saveBody{
    display:flex; flex-direction:column; gap:9px;
  }

  /* 檔名 + 儲存排成一列，讀起來就是「這個名字，存下去」 */
  #saveBox .namerow{display:flex;gap:8px}
  #saveBox #saveName{
    flex:1; min-width:0; padding:9px 11px; background:var(--bg);
    border:1px solid var(--line); border-radius:3px;
    font-size:13px; color:var(--txt);
  }
  #saveBox #saveName::placeholder{color:var(--dimmer)}
  #saveBox #saveName:focus-visible{outline:2px solid var(--t1);outline-offset:1px}
  #saveBox .namerow .act{width:auto;flex:0 0 auto;padding:9px 20px}

  /*
    三行回饋（未儲存狀態／成功／錯誤）**預留高度**。 **預留兩行（40px）而不是一行**：按下
    儲存之後 #saveDirty 與 #saveOk 兩行一起在，而切分頁會把 ok／err 清掉。
  */
  #saveBox .result{min-height:40px;display:flex;flex-direction:column;gap:3px;font-size:12px}
  #saveBox .result p[hidden]{display:none}
  /* 「已儲存」是中性的灰，「有未儲存的變更」才亮起來 —— 前者是常態，
     常態不該搶注意力（同狀態列「已暫存」的處理）。 */
  #saveBox #saveDirty{color:var(--dimmer)}
  #saveBox #saveDirty.on{color:var(--t2)}
  #saveBox #saveOk{color:var(--t2)}
  #saveBox #saveErr{color:var(--warn)}

  /*
    清單高度**固定** —— 只有 2 筆時置底工具列不能往上跳。370px ≈ 表頭 32 + 7 列 × 48。
    height 走 flex-basis 而不是 min-height：後者會贏過 max-height，矮螢幕上就壓不下來。
  */
  #saveBox .list{
    flex:0 1 370px; min-height:150px; overflow:auto;
    border:1px solid var(--line); border-radius:3px; background:var(--bg);
  }
  #saveBox table{width:100%;border-collapse:collapse;font-size:12px}
  #saveBox th{
    position:sticky; top:0; z-index:1; background:var(--bg2);
    font-size:11px; letter-spacing:.1em; text-transform:uppercase; color:var(--dimmer);
    font-weight:400; text-align:left; padding:7px 9px;
    border-bottom:1px solid var(--line); white-space:nowrap;
  }
  #saveBox td{padding:9px;border-bottom:1px solid var(--line-soft);color:var(--dim)}
  #saveBox tbody tr:last-child td{border-bottom:0}
  /*
    整列都是勾選的靶，做法跟 #midiBox 那張清單一致（見 filebox.js）。`user-select:none` 是因
    為列變成可點之後，雙擊選字會順手把它勾起來又取消。

     **`cursor:pointer` 不能下在 td.ops 上**：那一格裡的兩顆按鈕有自己的 cursor。
  */
  #saveBox tbody tr{cursor:pointer;user-select:none}
  #saveBox td.ops{cursor:default}
  /*
    四階要分得開：底色 → 停在上面 → 勾起來 → 勾起來又停在上面。
    列本身是點擊目標，「我現在會點到哪一列」必須一眼看得出來。
  */
  #saveBox tbody tr:hover td{background:var(--panel);color:var(--txt)}
  #saveBox tbody tr.on td{background:var(--panel2);color:var(--txt)}
  #saveBox tbody tr.on:hover td{background:var(--line)}
  /*
    檔名欄跟這一列一樣的那一份 —— 「按下儲存會蓋掉的就是它」。比對的是名字，不是內容。

     **不能用底色**：`var(--panel2)` 被 `tr.on` 拿去用了，而 `.hit` 與 `.on` 是**完全正交**
    的兩件事。所以改成左緣一條金色的槓。
  */
  #saveBox tbody tr.hit td:first-child{box-shadow:inset 3px 0 0 var(--t1)}
  #saveBox tbody tr.hit td.name{color:var(--t1)}
  /* 檔名可以很長（上限 100 字），給它剩下的全部寬度並在放不下時截斷 ——
     其他六欄都是 width:1% 的窄欄。 */
  #saveBox td.name{
    color:var(--txt); max-width:0;
    overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
  }
  /* 數字靠右對齊、等寬 —— 兩欄數字上下對齊才掃得動 */
  #saveBox td.num{font-family:var(--mono);text-align:right;white-space:nowrap;width:1%}
  #saveBox td.when{font-family:var(--mono);white-space:nowrap;width:1%;color:var(--dimmer)}
  #saveBox td.ops{white-space:nowrap;width:1%}
  #saveBox td.empty{
    color:var(--dimmer); text-align:center; padding:28px 9px; border-bottom:0;
  }
  #saveBox td.ops button{
    background:none; border:1px solid var(--line); border-radius:3px;
    color:var(--dim); font-size:12px; padding:4px 9px; cursor:pointer;
    margin-right:4px;
  }
  #saveBox td.ops button:last-child{margin-right:0}
  #saveBox td.ops button:hover{border-color:var(--t2);color:var(--txt)}
  #saveBox td.ops button.del:hover{border-color:var(--warn);color:var(--warn)}

  /* 置底工具列。全選的勾放在最左邊，跟表格第一欄對齊 */
  #saveBox .foot{display:flex;align-items:center;gap:8px}
  /*
    「全選」那兩個字包在 <label> 裡，所以整塊（checkbox + 文字 + 兩側的 padding）都點得到。
    user-select:none：這是一個按鈕，雙擊它應該勾選兩次而不是選字。
  */
  #saveBox .foot .pickall{
    display:flex; align-items:center; gap:6px; padding:0 4px;
    cursor:pointer; user-select:none; font-size:12px; color:var(--dimmer);
  }
  #saveBox .foot .pickall:hover{color:var(--dim)}
  #saveBox .foot .count{font-size:12px;color:var(--dimmer)}
  /*
    右半邊的推手。本機是「已用 x / 20 MB」，Web 是「共 n 份」，而且**永遠只有一個看得見**
    （savebox.syncTab 把另一個 hidden 掉）。

     那件事是這條規則的前提：兩個都可見的話 `margin-left:auto` 會出現兩次，剩餘空間被平
    分，右半邊整組就不再靠右了。
  */
  #saveBox .foot #saveUsed,
  #saveBox .foot #saveTotal{margin-left:auto;font-family:var(--mono)}
  #saveBox .foot .pg{
    background:none; border:1px solid var(--line); border-radius:3px;
    color:var(--dim); font-size:12px; padding:5px 11px; cursor:pointer;
  }
  #saveBox .foot .pg:not(:disabled):hover{border-color:var(--t2);color:var(--txt)}
  #saveBox .foot .pg.del:not(:disabled):hover{border-color:var(--warn);color:var(--warn)}
  #saveBox .foot .pg:disabled{opacity:.3;cursor:default}
  /*
    分頁器只有 Web 分頁有（本機一次全撈，20MB 裝不下需要分頁的筆數）。
    上下頁那兩顆窄一點 —— 它們的內容是一個箭頭，用批次鈕的 padding 會顯得很空。
  */
  #saveBox .foot #savePager{display:inline-flex;align-items:center;gap:6px}
  #saveBox .foot #savePager[hidden]{display:none}
  #saveBox .foot #savePager .pg{padding:5px 9px}
  /* 「2 / 7」用等寬，翻頁時數字不會左右抖 */
  #saveBox .foot #savePage{font-family:var(--mono);min-width:44px;text-align:center}

  /* 三個確認框跟 #shareDelBox 完全同一套 —— 它們問的是同一種問題 */
  #saveOverBox .card,#saveDelBox .card{width:min(460px,92vw)}
  /*
    搬遷的那個框寬一點：它要列出撞名的檔名（最多 20 個，每個上限 100 字），
    460px 會變成一團折了六行的字。
  */
  #saveMoveBox .card{width:min(560px,92vw)}
  /* 三顆鈕（取消／只搬其餘／全部覆蓋）比兩顆擠，縮小間距讓它們排得下 */
  #saveMoveBox .row{gap:8px}
  #saveOverBox p b,#saveDelBox p b{color:var(--warn)}
  #saveOverBox .what,#saveDelBox .what{
    font-family:var(--mono); font-size:12px; color:var(--txt);
    background:var(--bg); border:1px solid var(--line); border-radius:3px; padding:9px 11px;
    /* 檔名可能很長，而這裡不能截斷 —— 使用者正要決定要不要蓋掉「哪一個」 */
    overflow-wrap:anywhere;
  }

  /* ─── 舞台平面圖（`stageplot.js`）───────────────────────────────────────────
      **整組是 class 不是 id。** 三個呼叫端：#stageBox（混音匯出）、影片頁的 #stageBox、
     以及 #panBox（編輯中的 3D 混音）—— 而最後兩個裡的 #panBox 跟第一個**在同一頁**，
     兩個舞台不可能共用一個 id。 */

  #stageBox .card,
  #panBox .card{width:min(560px,92vw)}
  /*
     舞台是**正方形**，而正方形在矮螢幕上最危險 —— 寬度給滿的話高度就跟著給滿，把下面那排
    按鈕（含「開始匯出」）推出畫面外，而溢出的部分**捲不到**（見 .modal .card）。

     330px 是照 #stageBox 量的（試聽列 + 三段 + 按鈕列）。#panBox 沒有試聽列，但多了一根
    左右聲道滾桿與一行說明 —— 兩邊差不多，所以共用同一個數字。
  */
  .stage{
    position:relative; margin:0 auto;
    width:min(100%, calc(100dvh - 330px)); aspect-ratio:1;
    background:var(--bg); border:1px solid var(--line); border-radius:4px;
    /* 沒有這一行，手機上拖曳會被瀏覽器判戈成捲動，chip 完全拖不動 */
    touch-action:none;
    overflow:hidden;
  }
  .stage.busy{opacity:.5; pointer-events:none}
  /* 空間混音關著：位置擺在那裡但現在不算數，所以淡掉而**不是**藏起來或鎖住 ——
     它們仍然拖得動，而拖下去就會把空間混音打開（見 mixstage.armSpatial）。 */
  .stage.flat svg{opacity:.35}
  .stage.flat .chip{opacity:.55}
  .stage svg{position:absolute; inset:0; width:100%; height:100%; pointer-events:none}
  /* stroke-width 的單位是 viewBox 的單位，也就是公尺。0.04 m 在 500px 寬的舞台上約 1.7px */
  .stage svg circle{fill:none; stroke:var(--line); stroke-width:.04}
  /*
     最內圈是**界線不是尺標**：它畫的就是 refDistance，而圈內的增益固定是 1.0
    （「圈內不分遠近」）。粗一倍多、換一個色 —— 跟另外三圈長得一樣的話沒有人看得出來
    那條規則存在，而編輯中的 3D 混音的預設排列整列就塞在這一圈裡。
  */
  .stage svg circle.ref{stroke:var(--t2); stroke-width:.09}

  .stage .chip,
  .stage .ear{position:absolute; transform:translate(-50%,-50%); padding:0}
  /* 兩行：樂譜名稱 + 樂器名稱。圓角收成 10px 而不是 999px —— 兩行的膠囊會變成
     很奇怪的橢圓。 */
  /* padding 比無框時少 1px：加了 1px 框線之後外框尺寸才跟灰色（靜音）那版對得起來，
     兩者在俯視圖上並排時高度一致。 */
  .stage .chip{
    display:flex; flex-direction:column; align-items:center;
    background:var(--c); color:#fff; border:1px solid #fff; border-radius:10px;
    padding:2px 7px; font-size:11px; line-height:1.35; white-space:nowrap;
    max-width:46%; cursor:grab; box-shadow:0 1px 4px rgba(0,0,0,.5);
  }
  /* 兩行都要各自省略，不能只在外層省 —— 那樣長的樂器名會把整顆撐開 */
  .stage .chip .nm,
  .stage .chip .inst{max-width:100%; overflow:hidden; text-overflow:ellipsis}
  /* 樂器名是次要資訊：小一階、透明一點，但仍要讀得出來（0.72 是量過的下限） */
  .stage .chip .inst{font-size:10px; opacity:.72}
  /* 沒選音色時第二行是空的。留著它讓所有 chip 等高，高矮不一在俯視圖上會被誤讀成
     「這些是不一樣的東西」。 */
  .stage .chip .inst:empty::before{content:"\00a0"}
  .stage .ear{
    width:26px; height:26px; border-radius:50%;
    background:var(--txt); border:2px solid var(--bg); cursor:grab;
  }
  /* 朝向指示：頭上一個朝上的小三角。聽者的朝向是固定的（永遠面向畫面上方），
     所以這個三角不會轉 —— 它存在是為了讓那條規則不必用文字說明。 */
  .stage .ear::after{
    content:""; position:absolute; left:50%; top:-10px; transform:translateX(-50%);
    border-left:5px solid transparent; border-right:5px solid transparent;
    border-bottom:7px solid var(--txt);
  }
  .stage .chip.grab,
  .stage .ear.grab{cursor:grabbing}
  .stage .chip:focus-visible,
  .stage .ear:focus-visible{outline:2px solid var(--t2); outline-offset:2px}
  /*
     焦點模式（`stageplot.setFocus`，只有 #panBox 用）。

     **淡掉 ≠ 鎖住。** 非主角的牌子只是淡一階，**照樣拖得動** —— 空間感的意義在相對位置，
    只能動一個就沒得排。淡的意思是「左右聲道滾桿與重置鈕管不到它」。所以這裡是 class 而不
    是 `:disabled`（那個屬性會一起關掉拖曳、鍵盤與 Tab）。

     真的鎖住的只有**聽者**，而它刻意**不淡化** —— 它是「正前方」那句話的參照點（重置鈕
    就是照它算的），淡掉等於把唯一的座標原點藏起來。

     第二條的 `.flat` 版本是必要的：`.stage.flat .chip` 已經把整片壓到 .55，不再壓一階的話
    「停用」時主角會比配角**還暗**。
  */
  .stage .chip.dim{opacity:.62}
  .stage.flat .chip.dim{opacity:.34}
  /*
     **疊放順序：聽者 > 主角 > 其他人。**

     預設排列刻意把整列疊在第一圈裡（那是「零音量差」換來的代價，見 mixmath.columnLayout），
    所以牌子**一定會互相遮**。唯一不能被遮的是**你正在調的那一份** —— 它是滾桿與重置鈕作用
    的對象。聽者排最上面：它是「正前方」那句話的原點，被一疊牌子蓋住就沒有參照了。
  */
  .stage .chip{z-index:2}
  .stage .chip.dim{z-index:1}
  .stage .ear{z-index:3}
  .stage .ear:disabled{cursor:default}

  /*
     **樣式要自己寫全，不能借 .tiny。** 那個 class 的規則整組掛在 `#rollbar button.tiny`
    底下，在別的地方掛上去等於什麼都沒有 —— 而第 31 行的 `button{color:inherit}` 讓文字繼承
    近白的 --txt，配上瀏覽器預設的淺灰底就是白字配白底。
  */
  #stagePlay,
  #stageReset,
  #stageCancel,
  #panReset{
    background:none; border:1px solid var(--line); border-radius:3px;
    color:var(--dim); font-size:11px; padding:5px 10px; cursor:pointer;
    white-space:nowrap; flex:0 0 auto;
    transition:border-color .15s,color .15s;
  }
  #stagePlay:not(:disabled):hover,
  #stageReset:not(:disabled):hover,
  #stageCancel:not(:disabled):hover,
  #panReset:not(:disabled):hover{border-color:var(--t2); color:var(--txt)}
  #stagePlay:disabled,
  #stageReset:disabled,
  #stageCancel:disabled,
  #panReset:disabled{opacity:.4; cursor:not-allowed}
  /* 試聽鈕比其他兩顆重要一階：它是這個框裡最常按的東西 */
  #stagePlay{color:var(--txt); min-width:6.5em; text-align:center}

  .stagetransport{display:flex; align-items:center; gap:10px}
  .stagetransport input[type=range]{flex:1; min-width:0}
  /* 左右聲道與遠近那兩根滾桿（#panBox）。**它們站的是 .stagetransport 的位子** —— 那個框
     不試聽，所以沿用同一套排版語言：兩側各一段固定寬度，中間一根給滿。
     兩側都訂 min-width 是為了**兩列的滾桿左右緣要對齊**：不訂的話「左右聲道」與「遠近」
     字寬不同，兩根桿子會錯開，而錯開的兩根桿子讀起來像是不同的量。 */
  .panrow{display:flex; align-items:center; gap:10px; margin-top:10px}
  .panrow .lbl{
    font-size:11px; color:var(--dim); flex:0 0 auto;
    min-width:4.5em;
  }
  .panrow .slot{position:relative; flex:1; min-width:0; display:flex}
  .panrow .slot input[type=range]{flex:1; min-width:0; margin:0}
  /*
     中央那一刻。**上下各露出 4px 是必要的，不是裝飾** —— 原生 range 的軌道是 UA 畫的，
    畫在軌道範圍內會被它蓋掉。露出來的那兩截保證看得見。
  */
  .panrow .slot.mid::before{
    content:""; position:absolute; left:50%; top:-4px; height:calc(100% + 8px);
    width:1px; background:var(--t2); transform:translateX(-50%); pointer-events:none;
  }
  /* 數值。等寬 ＋ 固定寬度 ＋ 靠右：不然數字換位數時整列會左右抽動。 */
  .panrow .val{
    font-family:var(--mono); font-size:11px; color:var(--dim);
    flex:0 0 auto; min-width:4.5em; text-align:right;
  }
  #stageTime{font-family:var(--mono); font-size:12px; color:var(--dim); min-width:3.5em; text-align:right}
  #stageWarn{margin:0}
  /* 直排而不是讓兩個核取方塊跟按鈕擠同一列：那一列在窄畫面會 wrap 成很難讀的三段，
     而且「空間混音」是「喇叭模式」的前提，上下排才看得出從屬。 */
  .stagefoot{display:flex; flex-direction:column; gap:8px}
  .stagefoot .spk{display:flex; align-items:center; gap:6px; font-size:12px; flex-wrap:wrap}
  .stagefoot .spk .hint{margin:0; color:var(--dim); font-size:11px}
  /* 喇叭模式在空間混音關著時是暗的 —— 它只有在空間混音開著時才有意義。 */
  .stagefoot .spk:has(:disabled){opacity:.45}
  .stagefoot .acts{display:flex; gap:8px; margin-left:auto}
  #stageProg{display:flex; align-items:center; gap:10px; width:100%}
  #stageProg progress{flex:1; height:6px}
  #stageStage{font-size:12px; color:var(--dim); min-width:5em}

  @media (prefers-reduced-motion:reduce){
    *{transition:none!important}
  }
