/* scout.css -- Scout App shell styles (dark, landscape-first). v2.4: the
   standalone hotkey-layer styles moved out with the unified record screen
   (keyboard feedback now lives in touch.css). */

* {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  padding: 0;
}

body {
  background: var(--c-page-bg);
  color: var(--p-ink-03);
  font-family: "Noto Sans TC", "Microsoft JhengHei", system-ui, sans-serif;
  font-size: 15px;
  line-height: 1.5;
}

h1 {
  font-size: 1.4rem;
  margin: 0 0 0.8rem;
}

h2 {
  font-size: 1.05rem;
  margin: 0 0 0.6rem;
  color: var(--p-blu-04);
}

h3 {
  font-size: 0.95rem;
  margin: 0.6rem 0 0.3rem;
  color: var(--p-blu-04);
}

.screen {
  max-width: 1200px;
  margin: 0 auto;
  padding: 1rem 1.2rem 2rem;
}

.muted {
  color: var(--p-ink-15);
}

.error {
  color: var(--p-red-04);
  margin: 0.3rem 0;
}

.notice {
  background: var(--p-surf-24);
  border-left: 4px solid var(--p-blu-08);
  padding: 0.5rem 0.8rem;
  border-radius: 4px;
}

/* v2.20 FIVB 7.1/12.1.1: deciding-set coin-toss banner — a prominent amber
   alert on the lineup screen steering the operator to re-pick serve + side. */
.notice-decider {
  background: var(--p-org-18);
  border-left: 4px solid var(--p-yel-03);
  color: var(--p-org-01);
  font-weight: 700;
}

.cols {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
}

.cols > .panel,
.cols > fieldset {
  flex: 1 1 320px;
  min-width: 0;
}

/* v3.5 建賽畫面版面修正 —— 原本 .cols 是巢狀的（外層「選擇比賽 | 建立新比賽」，
   內層「主隊 | 客隊」）。外層均分後建賽面板只剩約 560px，內層兩個 flex-basis
   320px 的 fieldset 加上間距就一定換行，主客隊在任何視窗寬都變成上下堆疊。
   修法有兩段：
     1) 外層給建賽面板兩倍權重（比賽列表窄、建賽表單寬），並放寬本畫面的
        max-width，讓內層真的有兩欄的空間；
     2) 內層改用 GRID 明確二欄，不再依賴 flex-wrap 的隱性換行門檻。 */
#screen-match { max-width: 1400px; }

/* The create panel needs the width (two roster columns); the match list needs
   enough not to wrap its row buttons mid-word. 1 : 1.6 gives both room from
   1280px up, which is where the two-column team layout applies; below that the
   teams stack (see .create-teams) and match rows wrap rather than clip. */
.match-cols > .panel:first-child { flex: 1 1 320px; }
.match-cols > .panel:last-child { flex: 1.6 1 620px; }

.create-teams {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1rem;
  align-items: start;
}

/* Below 1200px two team columns would each be ~340px, which crushes the roster
   table's 姓名 field to an unusable sliver. Stack instead: each team then gets
   the full panel width and the table stays comfortable. 主隊（A邊）stays on top
   (DOM order = visual order). Side-by-side is required from 1280px up.
   v3.5.3: the OUTER panels (match list | create form) stack at the same
   breakpoint — leaving them side-by-side squeezed the match list to ~320px,
   whose action buttons then clipped into (and were intercepted by) the
   neighbouring form's fieldset (tablet report, 1024px). */
@media (max-width: 1199px) {
  .create-teams { grid-template-columns: 1fr; }
  .match-cols > .panel:first-child,
  .match-cols > .panel:last-child { flex: 1 1 100%; }
}

.panel {
  background: var(--c-surface-raised);
  border: 1px solid var(--p-surf-28);
  border-radius: 8px;
  padding: 0.9rem 1rem;
  margin-bottom: 1rem;
}

.row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.8rem;
  align-items: center;
  margin: 0.6rem 0;
}

fieldset {
  border: 1px solid var(--p-surf-28);
  border-radius: 8px;
  padding: 0.7rem 0.9rem;
}

legend {
  color: var(--p-blu-04);
  padding: 0 0.4rem;
}

label {
  display: block;
  margin: 0.4rem 0;
}

label.radio {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  margin-right: 1rem;
}

input,
select,
textarea {
  width: 100%;
  margin-top: 0.2rem;
  padding: 0.4rem 0.5rem;
  background: var(--p-surf-44);
  color: var(--p-ink-03);
  border: 1px solid var(--p-surf-15);
  border-radius: 5px;
  font: inherit;
}

.row label {
  flex: 0 1 auto;
}

.row input,
.row select {
  width: auto;
  min-width: 9rem;
}

input[type="radio"] {
  width: auto;
  min-width: 0;
}

textarea {
  font-family: "Cascadia Mono", "Consolas", monospace;
  resize: vertical;
}

.btn {
  display: inline-block;
  padding: 0.45rem 0.9rem;
  background: var(--p-surf-28);
  color: var(--p-ink-03);
  border: 1px solid var(--p-surf-07);
  border-radius: 6px;
  font: inherit;
  cursor: pointer;
}

.btn:hover {
  background: var(--p-surf-17);
}

.btn-primary {
  background: var(--p-blu-20);
  border-color: var(--p-blu-15);
  /* v3.18 (契約 §26-4): a solid accent button states its own ink instead of
     inheriting --c-text, which flips dark in the light theme and would then
     print dark-on-dark. The dark value IS the inherited one, so nothing moves
     in the dark theme. */
  color: var(--c-ink-on-solid);
}

.btn-primary:hover {
  background: var(--p-blu-18);
}

/* Destructive actions (v1.8 match delete). */
.btn-danger {
  color: var(--c-ink-on-solid);
  background: var(--p-red-24);
  border-color: var(--p-red-20);
}

.btn-danger:hover {
  background: var(--p-red-22);
}

/* ---- match list ---- */

.match-list {
  max-height: 20rem;
  overflow-y: auto;
  /* v3.5: `overflow-y: auto` alone computes overflow-x to auto too, which
     turned this into a SIDEWAYS scroller — 「刪除」(and at 1024px 「賽報 PDF」)
     sat outside the visible box and could only be reached by scrolling the
     list horizontally. Rows now wrap instead, so nothing needs an x-scroll. */
  overflow-x: hidden;
  margin-bottom: 0.6rem;
}

.match-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.4rem 0.6rem;
  padding: 0.4rem 0.2rem;
  border-bottom: 1px solid var(--c-divider);
}

/* The label may shrink to nothing before the actions wrap; once it does, the
   action group takes a full line of its own and every button stays reachable. */
/* v3.5.2c: in a narrow list column the label used to soft-wrap mid-ID and the
   buttons wrapped once more — three ragged lines per row (tablet report).
   The label now stays on ONE line and ellipsizes; the action buttons move as
   ONE unit — same line when it fits, otherwise a clean full-width second
   line. flex-wrap on .match-row only ever breaks between the two groups. */
.match-row-label {
  flex: 1 1 14rem;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.match-row-actions {
  display: flex;
  flex-wrap: wrap; /* safety: wrap internally rather than clip if squeezed */
  align-items: center;
  gap: 0.4rem;
  flex: 0 1 auto;
}

/* ---- roster tables ---- */

.roster-table {
  width: 100%;
  border-collapse: collapse;
}

.roster-table th,
.roster-table td {
  text-align: left;
  padding: 0.25rem 0.5rem;
  border-bottom: 1px solid var(--c-divider);
}

.roster-table th {
  color: var(--p-ink-15);
  font-weight: normal;
}

/* ---- player-position badges (v1.4; colors = base.css --pos-* tokens) ---- */

.pos-badge {
  display: inline-block;
  padding: 0 0.5em;
  border-radius: 8px;
  font-size: 0.8rem;
  font-weight: 700;
  color: var(--c-ink-on-yellow);
  line-height: 1.6;
  white-space: nowrap;
}

.pos-badge.pos-s { background: var(--pos-s); }
.pos-badge.pos-mb { background: var(--pos-mb); }
.pos-badge.pos-oh { background: var(--pos-oh); }
.pos-badge.pos-op { background: var(--pos-op); }
.pos-badge.pos-l { background: var(--pos-l); }

/* Lineup/substitution pickers: position-colored option text (v1.4). */
option.pos-s { color: var(--pos-s-text); }
option.pos-mb { color: var(--pos-mb-text); }
option.pos-oh { color: var(--pos-oh-text); }
option.pos-op { color: var(--pos-op-text); }
option.pos-l { color: var(--pos-l-text); }

/* Roster-screen position dropdowns stay compact inside the table. */
.roster-table .pos-select {
  width: auto;
  min-width: 7.5rem;
  margin-top: 0;
}

/* ---- lineup ---- */

/* v3.5: the six pickers ARE the court — net at the top, front row 4/3/2 and
   back row 5/6/1 left→right (app.js LINEUP_GRID_ROWS, same geometry as the
   touch layer's 3×2 grid). Always 3 columns: collapsing to 2 would destroy
   the court reading, so narrow screens shrink the cells instead. */
.lineup-net-label {
  margin: 0.2rem 0 0.35rem;
  text-align: center;
  font-size: 0.78rem;
  letter-spacing: 0.08em;
  color: var(--c-text-dim);
  border-bottom: 2px solid var(--c-border);
  padding-bottom: 0.3rem;
}

.lineup-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0.4rem;
}

/* v3.17 (契約 §24-1): the three line-up helpers sit on their own row under the
   setter hint — a small, quiet row: they prepare the form, they never submit. */
.lineup-helpers { flex-wrap: wrap; gap: 0.4rem; margin: 0.2rem 0 0.4rem; }
.lineup-helpers .btn { font-size: 0.85rem; padding: 0.3rem 0.7rem; }
.lineup-helpers .btn:disabled { opacity: var(--c-opacity-disabled); }

.lineup-cell {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  background: var(--c-surface-alt);
  border: 1px solid var(--c-border);
  border-radius: var(--radius);
  padding: 0.4rem 0.45rem;
  min-width: 0;
}

.lineup-cell select { width: 100%; min-width: 0; }

/* v3.12: once the set is live the whole form is read-only (lineupFormLocked) —
   the values stay readable but the pickers must not LOOK editable. Keyed off
   the selects' own :disabled state so no extra class can drift out of sync
   (plain :disabled only — no :has(), which the field tablets may not have). */
#lineup-forms select:disabled {
  opacity: 0.7;
  cursor: not-allowed;
  background: var(--c-surface);
  border-style: dashed;
}

.lineup-cell-zone {
  font-size: 0.78rem;
  font-weight: 700;
  color: var(--c-text-dim);
}

/* the serving corner (position 1, bottom-right) carries the serve accent */
.lineup-cell[data-zone="1"] { border-color: var(--c-primary); }
.lineup-cell[data-zone="1"] .lineup-cell-zone { color: var(--c-primary-text); }

/* v1.8: auto-derived setter-slot readout (replaces the setter dropdown). */
.setter-hint {
  margin: 0.3rem 0 0;
  font-size: 0.9rem;
}

/* ---- record screen ---- */

.screen-record {
  max-width: none;
  /* v2.22: bound the record screen to the viewport so the scorebar + merged
     control row + buffer strip + 站位盤/技術/評價 main area are all on-screen at
     once with NO documentElement vertical scroll (spec 同屏完整可見 @1280×800).
     The main input area flexes and scrolls INTERNALLY instead of growing the
     page. base.css `[hidden]{display:none!important}` still hides it off-screen.
     `overflow:hidden` is insurance; #input-layer keeps an internal scroll. */
  height: 100vh;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding: 0.5rem 1rem;
  overflow: hidden;
}

/* v2.22: the record screen's flex column — fixed-height header rows, the input
   layer takes the rest and scrolls internally (min-height:0 lets it shrink). */
#screen-record > #scorebar,
#screen-record > #record-toolbar,
#screen-record > #match-banner {
  flex: 0 0 auto;
}
#screen-record > #input-layer {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
}

/* v3.16.6 (契約 §19-2): the record screen's header is ONE row. #record-toolbar
   (取消／上一動作／上一球／替補／暫停／挑戰／更多▾) moved inside, so the old second
   row is gone and the court gets its vertical space back. Below 620px the
   control group may wrap to a second line — but there is still no readout row
   (§19-1), so the header never grows past two. */
#scorebar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem 0.7rem;
  background: var(--c-surface-raised);
  border: 1px solid var(--p-surf-28);
  border-radius: 8px;
  padding: 0.5rem 1rem;
}
/* v3.16.9 (契約 §23): ONE line from 600px of effective width up — every tablet
   in portrait (768 / 820 / 834) and a 1024 landscape at 125–150% page zoom.
   The breakpoint is in `em`, not px, on purpose: a media-query `em` is always
   the browser's INITIAL font size, so a browser-level font enlargement (the
   user's own accessibility setting) narrows the effective width exactly like
   shrinking the window does — which is the behaviour the operator expects —
   while an author-set root font size (`html{font-size:150%}`, what a page zoom
   and our own smoke matrix produce) leaves the breakpoint where it is and is
   absorbed by the clamps below instead. */
@media (min-width: 37.5em) {
  #scorebar {
    flex-wrap: nowrap;
    /* Tighter than the wrapping layout: the score, the meta block, the
       connection block and the two menu buttons share one row. */
    gap: 0.4rem 0.5rem;
  }
  #scorebar > * { min-width: 0; }
  #scorebar #record-toolbar { flex-wrap: nowrap; }
  /* The team's SHORT name never shrinks (契約 §23-1 d) — it is the one label
     the operator reads across the hall. It may ellipsize on a truly long one. */
  #scorebar .sb-team {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    flex: 0 1 auto;
    min-width: 4ch;
  }
  /* The two menu buttons hold the right edge of the FIRST row and never give
     way; everything else yields in the order §23-1 lays down (the clamps on
     .sb-meta / .sb-conn / .sb-score below do the yielding). */
  #scorebar .sb-conn,
  #scorebar .sb-meta,
  #scorebar #record-toolbar { flex: 0 0 auto; }
  #scorebar .sb-meta { overflow: hidden; }
}

/* v3.16.6: the controls join the header, so the header's own labels give up the
   width — the meta block becomes two compact lines (局／局數 above, 發球／輪轉
   below) at the same size as .sb-conn, and the buttons lose their generous
   padding while keeping a 44px touch target.
   v3.16.9 (契約 §23-3): every dimension here is in `rem`/`em`, so a bigger font
   makes the BUTTON grow with its label instead of the label overflowing it. */
#scorebar .tm-btn,
#scorebar #record-toolbar .btn {
  min-height: 2.75rem;
  padding: 0 0.625em;
  font-size: 0.9rem;
  white-space: nowrap;
}

.sb-team {
  /* v3.16.9 (契約 §23-1 d): the short name NEVER shrinks. */
  font-size: 1.3rem;
  font-weight: bold;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
}

.sb-score {
  /* v3.16.9 (契約 §23-1 c): the score is the THIRD thing to give way. The clamp
     is what makes a bigger root font (page zoom, or the operator's own 150%)
     land as a slightly smaller-than-ideal score instead of a wrapped header:
     `vw` does not scale with the font, so it caps the growth, and the 1.05rem
     floor keeps the digits readable at every size. */
  font-size: clamp(1.05rem, 3.1vw, 2rem);
  font-weight: bold;
  font-variant-numeric: tabular-nums;
  /* v3.16.6: the inherited body line-height made this the tallest thing in the
     merged header (96px for 32px of digits) and set the whole row's height;
     nowrap + no shrink stops the `min-width:0` from folding 0：0 in two. */
  line-height: 1.1;
  white-space: nowrap;
  flex: 0 0 auto;
  /* v3.16.9: match the control buttons' box so every first-row item shares one
     baseline band — the score used to sit ~5px lower than 操作▾ simply because
     its box was shorter, which reads as (and tests as) a wrapped row. */
  min-height: 2.75rem;
  display: inline-flex;
  align-items: center;
}

.sb-sep {
  color: var(--p-ink-15);
}

.serve-dot {
  /* v3.18.2 (契約 §28-1): 淺色改鮮明綠＋1px 深色描邊；深色仍是原本的金黃，
     描邊令牌在深色是 none，所以深色一個像素都沒動。 */
  color: var(--c-serve-dot);
  text-shadow: var(--c-serve-dot-shadow);
  font-size: 0.9rem;
}

.sb-meta {
  display: grid;
  grid-template-columns: auto auto;
  gap: 0.1rem clamp(0.25rem, 0.9vw, 0.6rem);
  align-items: baseline;
  line-height: 1.25;
  /* v3.16.9 (契約 §23-1 a): the FIRST thing to give way, down to an 11px floor
     — an absolute legibility limit, so deliberately in px rather than rem (a
     rem floor would itself grow with the font it is supposed to cap). The
     block keeps its two lines the whole way down. */
  font-size: clamp(11px, 1.35vw, 0.8rem);
  white-space: nowrap;
  color: var(--p-ink-09);
}

/* v3.16.6 (契約 §19-4): two compact stacked lines instead of one long strip —
   連線狀態 above, 暫存／未送 below, both a notch smaller. Colour semantics are
   unchanged (conn-ok / conn-off / has-buffer / pending-pairing). */
.sb-conn {
  margin-left: auto;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  justify-content: center;
  gap: 0.1rem;
  line-height: 1.25;
  /* v3.16.9 (契約 §23-1 b): the SECOND to give way, same 11px floor. */
  font-size: clamp(11px, 1.35vw, 0.8rem);
  white-space: nowrap;
}
.sb-counts { display: inline-flex; gap: 0.3rem; align-items: baseline; }
.sb-count-sep { color: var(--p-line-07); }

.conn-ok {
  color: var(--p-grn-01);
}

.conn-off {
  color: var(--p-red-04);
}

#sb-pending {
  color: var(--p-ink-09);
}

/* Rally-buffer count (contract 8.7): lights up while captured touches
   await their rally-close commit. */
#sb-buffer {
  color: var(--p-ink-09);
}

#sb-buffer.has-buffer {
  color: var(--c-pending);
  font-weight: bold;
}

/* v2.19 加速模式: a scoring '#' held open for the opponent's error (待配對). */
#sb-buffer.pending-pairing::after {
  content: '・待配對';
  color: var(--p-org-09);
  font-weight: bold;
}

/* v3.5: follows <html lang> (a ::after cannot call t()); see touch.css. */
html[lang="en"] #sb-buffer.pending-pairing::after {
  content: ' · awaiting pairing';
}

/* v3.16.6 (契約 §19-2): a segment of #scorebar now, not a row of its own. It
   sits after .sb-conn, so the order is 取消…替補（觸控層）｜暫停｜挑戰｜更多▾. */
#record-toolbar {
  /* v3.16.6: a flex ITEM of #scorebar now. Below 1024 it takes a full row of
     the wrapping header and wraps its own buttons inside it (契約 §19-2
     「<620px 允許按鈕群換第二行」); min-width:0 is what lets it shrink at all,
     without it the six buttons overflowed the phone viewport. */
  flex: 1 1 100%;
  min-width: 0;
  display: flex;
  /* v2.22: the SINGLE merged control row. The touch layer's .tm-statusbar
     (readout + 取消/上一動作/上一球/替補) is the leading, width-filling segment,
     followed by the shell's 暫停／挑戰／更多▾. On a wide (tablet/desktop) screen
     it is one line; flex-wrap lets a phone-width strip wrap onto extra lines
     rather than overflow the viewport (spec 375px 直式可換行不破版). */
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: center;
  /* v2.22: the record screen is a flex column with its own gap — no margin. */
  margin: 0;
}

/* v2.22: the touch status controls fill the row and push 暫停／挑戰／更多▾ to the
   trailing edge; min-width:0 lets the readout ellipsize instead of forcing a
   horizontal overflow.
   v3.6.1: basis auto tracked the readout's full TEXT width (nested flex sizes
   by content, not the child's flex-basis), so a long hint pushed 暫停/更多▾
   onto a second line at every viewport width. A fixed basis keeps toolbar
   line placement independent of the hint length. */
/* v3.16.6: with the readout gone (§19-1) the status controls are just buttons —
   they no longer need to fill the row, so they take their natural width and the
   scorebar's own flex layout decides the spacing. */
#record-toolbar .tm-statusbar {
  flex: 1 1 auto;
  min-width: 0;
}

/* v3.16.6 (契約 §19-10): the status controls live INSIDE the 操作 ▾ panel now,
   so .tm-statusbar dissolves into the menu's own vertical stack and each button
   renders like a .more-item. `display: contents` keeps the element (and its
   click delegation, and destroy()) in the DOM while its buttons become direct
   children of the panel. */
#record-actions-menu .tm-statusbar,
#record-actions-menu .tm-subbar { display: contents; }
#record-actions-menu .tm-teambox { display: none; }
#record-actions-menu .tm-btn,
#record-actions-menu .btn {
  display: block;
  width: 100%;
  min-height: 44px;
  padding: 0 12px;
  text-align: left;
  border-radius: 0;
  background: transparent;
  border: 0;
  font-size: 0.95rem;
}
#record-actions-menu .tm-btn:hover,
#record-actions-menu .btn:hover { background: var(--p-surf-28); }
#record-actions-menu .tm-btn:disabled,
#record-actions-menu .btn:disabled { opacity: var(--c-opacity-disabled); }

/* v3.16.6 (契約 §19-5): the build string at the foot of the 更多 ▾ menu — grey,
   not a menu item, not focusable. */
.more-build {
  padding: 6px 12px 2px;
  font-size: 0.75rem;
  text-align: right;
  border-top: 1px solid var(--c-border-soft);
  margin-top: 4px;
  cursor: default;
  user-select: text;
}

/* v2.21: single-row toolbar 更多 ▾ dropdown. The wrapper just holds the button;
   the menu stacks its items and never widens the toolbar. */
.more-wrap {
  display: inline-flex;
}
/* v2.21 bugfix: the menu is position:FIXED (not absolute) so it escapes
   #record-toolbar's overflow-x clip — an absolutely-positioned dropdown was
   cropped by the scroll container and its items became un-clickable. app.js
   places it against the button via getBoundingClientRect (top/left set inline)
   and repositions/closes it on scroll & resize. */
.more-menu {
  position: fixed;
  z-index: 40;
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 0.375rem;
  /* v3.16.9 (契約 §23-3/§23-4): sized in rem so the panel grows with its labels
     instead of clipping them, and capped against the viewport so it still fits
     a 768 portrait tablet at 150%. */
  min-width: 10.5rem;
  max-width: min(92vw, 22rem);
  background: var(--c-menu-bg);
  border: 1px solid var(--p-surf-14);
  border-radius: 8px;
  box-shadow: 0 8px 24px var(--c-shadow-soft);
}
.more-menu[hidden] { display: none; }
.more-menu .more-item {
  width: 100%;
  text-align: left;
  white-space: nowrap;
}

/* v3.16.7 (契約 §20): 操作 ▾ carries twelve items in three groups (回合／換人／
   裁判). The groups are plain wrappers — `display: contents` keeps them out of
   the layout so every item stays a direct row of the menu's flex column, which
   is what .tm-statusbar already relies on. */
.more-menu .more-group { display: contents; }
.more-menu .more-sep {
  height: 1px;
  margin: 0.1875rem 0.125rem;
  background: var(--p-surf-14);
  flex: 0 0 auto;
}
/* A twelve-item menu can outgrow a 667px-tall phone — and at 150% root font it
   outgrows a 1024-tall tablet too: cap it against the VIEWPORT and let it
   scroll rather than run off screen (契約 §20-4 / §23-4). */
.more-menu {
  max-height: min(78vh, 35rem);
  overflow-y: auto;
  overscroll-behavior: contain;
}

/* v2.22: on a phone-width record screen (≤480px) the merged control row wraps
   (it holds the readout + touch controls + 暫停／挑戰／更多▾); tighten the shell
   buttons so a wrapped line stays compact and never forces a horizontal page
   scroll. The touch .tm-btn controls keep their own ≥48px tap targets. */
@media (max-width: 480px) {
  #record-toolbar {
    gap: 0.3rem;
  }
  #record-toolbar > .btn,
  #record-toolbar #btn-more {
    flex: 0 0 auto;
    padding: 0.35rem 0.5rem;
    font-size: 0.82rem;
    white-space: nowrap;
  }
}

.flash {
  background: var(--c-toast-bg);
  border-left: 4px solid var(--p-yel-03);
  padding: 0.4rem 0.8rem;
  border-radius: 4px;
}

/* v1.8: the flash is a fixed overlay so it stays visible on every screen
   (match list, lineup, recording) without shifting the layout. */
/* v3.16.6 (契約 §19-1): the toast moved from the TOP to the BOTTOM of the
   viewport. It is now the record screen's only transient-hint channel (the
   readout row is gone), so it fires far more often — and at the top it sat
   right on the merged header, hiding the score and the controls it was meant
   to comment on. The bottom edge is empty on every viewport we support. */
#flash {
  position: fixed;
  bottom: 0.75rem;
  left: 50%;
  transform: translateX(-50%);
  z-index: 200;
  margin: 0;
  max-width: min(90vw, 42rem);
  box-shadow: 0 4px 16px var(--c-shadow);
}

/* v3.13: the one inline action a flash may carry (換人 toast ▸ 撤銷). Sized
   for a thumb — the toast is the fast route and only lives for 10 seconds. */
.flash-action {
  display: inline-block;
  margin-left: 0.6rem;
  padding: 0.2rem 0.9rem;
  min-height: 2rem;
  background: var(--c-badge-yellow);
  color: var(--p-surf-39);
  border: 0;
  border-radius: 4px;
  font: inherit;
  font-weight: 700;
  cursor: pointer;
}
.flash-action:hover { background: var(--p-yel-02); }

.banner {
  display: flex;
  align-items: center;
  gap: 1rem;
  background: var(--p-surf-29);
  border: 1px solid var(--p-org-17);
  border-radius: 8px;
  padding: 0.8rem 1rem;
  font-size: 1.1rem;
  margin: 0.6rem 0;
}

/* v2.6.1: side-swap reminder — a fixed overlay banner (created on demand
   by app.js) that stays visible on every screen until acted on; sits just
   below the flash toast (#flash is z-index 200). */
#swap-reminder {
  position: fixed;
  top: 3.6rem;
  left: 50%;
  transform: translateX(-50%);
  z-index: 190;
  margin: 0;
  max-width: min(92vw, 46rem);
  flex-wrap: wrap;
  justify-content: center;
  border-color: var(--p-yel-03);
  box-shadow: 0 4px 16px var(--c-shadow);
}

#swap-reminder .swap-now {
  background: var(--c-badge-yellow);
  border-color: var(--c-badge-yellow);
  color: var(--p-org-19);
  font-weight: 700;
}

/* v2.12 TTO: a server-fired technical-timeout banner — a fixed overlay
   (created on demand by app.js) that auto-dismisses after a few seconds or is
   tapped away. Blue edge distinguishes it from the amber side-swap reminder;
   it stacks just below the swap reminder. */
#tech-timeout-banner {
  position: fixed;
  top: 6.4rem;
  left: 50%;
  transform: translateX(-50%);
  z-index: 190;
  margin: 0;
  max-width: min(92vw, 46rem);
  flex-wrap: wrap;
  justify-content: center;
  background: var(--p-surf-34);
  border-color: var(--p-blu-14);
  box-shadow: 0 4px 16px var(--c-shadow);
}

/* v2.17 (FIVB 15.8): the INCOMPLETE banner — a prominent, CLOSABLE fixed
   overlay (created on demand by app.js). Red edge marks it as a rule alert
   the operator must act on; it does NOT auto-dismiss. Stacks below the TTO
   banner. */
#incomplete-banner {
  position: fixed;
  top: 9.2rem;
  left: 50%;
  transform: translateX(-50%);
  z-index: 195;
  margin: 0;
  max-width: min(92vw, 46rem);
  flex-wrap: wrap;
  justify-content: center;
  background: var(--p-surf-37);
  border-color: var(--p-red-12);
  font-weight: 700;
  box-shadow: 0 4px 18px var(--c-shadow-strong);
}
#incomplete-banner .btn {
  background: var(--p-red-12);
  border-color: var(--p-red-12);
  color: var(--p-surf-45);
  font-weight: 700;
}

/* ---- hotkey layer (v2.4): the standalone hotkey-mode DOM is gone ----
   The keyboard module renders through the touch layer (touch.css tm-typing /
   tm-help); the old .hk-* rules were removed with it. */

/* ---- dialogs ---- */

dialog {
  background: var(--c-surface-raised);
  color: var(--p-ink-03);
  border: 1px solid var(--p-surf-15);
  border-radius: 10px;
  padding: 1rem 1.3rem;
  min-width: 20rem;
}

dialog::backdrop {
  background: var(--c-overlay-backdrop);
}

dialog h2 {
  margin-top: 0;
}

/* ---- settings dialog: v2.10 加速模式 auto-close toggle ---- */

.settings-toggle {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 0.2rem 0 0.1rem;
  cursor: pointer;
  font-weight: 600;
}

.settings-toggle input[type="checkbox"] {
  width: auto;
  flex: 0 0 auto;
  margin: 0;
}

.settings-toggle-note {
  margin: 0 0 0.7rem;
}

/* ---- settings dialog: per-skill default-eval dropdowns (contract 8.7) ---- */

/* v3.16.5 隊伍顏色（契約 §16-1）：兩個色票輸入 ＋ 八個球衣色快選。快選是按鈕，
   aria-pressed 標出目前選到的那個；顏色本身就是說明，所以只掛 title/aria-label。 */
.settings-teamcolors {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin: 6px 0 10px;
}
.teamcolor-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
}
.teamcolor-label {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 84px;
  font-weight: 700;
}
.teamcolor-label input[type="color"] {
  width: 48px;
  height: 34px;
  padding: 0;
  border: 1px solid var(--p-surf-11);
  border-radius: 8px;
  background: transparent;
  cursor: pointer;
}
.teamcolor-swatches { display: flex; flex-wrap: wrap; gap: 6px; }
.teamcolor-swatch {
  width: 34px;
  height: 34px;
  border-radius: 8px;
  border: 1px solid var(--p-line-06);
  cursor: pointer;
  padding: 0;
  touch-action: manipulation;
}
.teamcolor-swatch[aria-pressed="true"] {
  box-shadow: 0 0 0 2px var(--p-ink-04);
}

.settings-eval-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(9rem, 1fr));
  gap: 0.3rem 1rem;
}

.settings-eval-grid .settings-eval-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem;
  margin: 0.2rem 0;
}

.settings-eval-grid select {
  width: auto;
  min-width: 6.5rem;
  margin-top: 0;
}

/* ---- settings dialog: setter-call (K combo) list editor (v1.3, spec 5.7) ---- */

.settings-calls-list {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  max-height: 14rem;
  overflow-y: auto;
  margin-bottom: 0.5rem;
}

.settings-call-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.settings-call-row input[data-call-code] {
  width: 4.5rem;
  flex: 0 0 auto;
  margin-top: 0;
  font-family: "Cascadia Mono", "Consolas", monospace;
  text-transform: uppercase;
}

.settings-call-row input[data-call-name] {
  flex: 1 1 auto;
  margin-top: 0;
}

.settings-call-del {
  flex: 0 0 auto;
}

/* ---- narrow screens ---- */

@media (max-width: 720px) {
  /* keep 3 columns (the court shape); just tighten the cells */
  .lineup-cell { padding: 0.3rem 0.25rem; }
  .lineup-cell-zone { font-size: 0.7rem; }

  .sb-score {
    font-size: 1.5rem;
  }

  .hk-buffer {
    min-width: 10rem;
    font-size: 1.4rem;
  }
}

/* Anchor styled as a button (match-list viewer links). */
a.btn-link { text-decoration: none; line-height: 1.6; }

/* ---- v2.2 quick fault-judgment overlay (contract 8.5 v2.2) ----
   Non-dialog fixed overlay shown by core.rallyClose when the rally has no
   finishing action; big (>=64px) buttons for tablet taps, key badges for
   the hotkey 1/2/3 quick pick. */
.fault-overlay {
  position: fixed;
  inset: 0;
  z-index: 150; /* above the record screen, below the #flash (200) */
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  background: var(--c-overlay-backdrop-strong);
}

.fault-panel {
  background: var(--p-surf-38);
  border: 1px solid var(--p-surf-08);
  border-radius: 10px;
  padding: 18px 20px;
  max-width: 640px;
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.fault-title {
  margin: 0;
  font-size: 1.05rem;
  font-weight: 700;
  color: var(--c-note-info);
}

.fault-btns {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

.fault-sub-list {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  flex: 1 1 100%;
}

.fault-btn {
  position: relative;
  flex: 1 1 160px;
  min-height: 64px;
  padding: 10px 14px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  background: var(--p-surf-23);
  color: var(--p-blu-02);
  border: 1px solid var(--p-surf-02);
  border-radius: 8px;
  cursor: pointer;
  font-size: 1rem;
}

.fault-btn:hover { background: var(--p-surf-13); }

/* v2.17 (FIVB 15.1): a timeout button whose team has used both regular
   timeouts this set — greyed out and non-interactive (the 本局暫停已用完 text
   sits in .fault-btn-sub). */
.fault-btn:disabled,
.fault-btn.timeout-exhausted {
  opacity: 0.45;
  cursor: not-allowed;
}
.fault-btn:disabled:hover,
.fault-btn.timeout-exhausted:hover { background: var(--p-surf-23); }

.fault-btn-label { font-size: 1.15rem; font-weight: 700; }

.fault-btn-sub { font-size: 0.78rem; color: var(--p-ink-10); }
.fault-btn.timeout-exhausted .fault-btn-sub { color: var(--p-red-02); }

.fault-btn-skip { background: var(--p-surf-33); border-style: dashed; }

/* Small key badge (hotkey quick pick) in the button corner. */
.fault-key {
  position: absolute;
  top: 6px;
  right: 8px;
  min-width: 1.3em;
  text-align: center;
  font-size: 0.75rem;
  font-weight: 700;
  color: var(--c-ink-on-yellow);
  background: var(--c-badge-yellow);
  border-radius: 4px;
  padding: 0 3px;
}

.fault-back,
.fault-cancel { align-self: flex-start; }

/* ---- v3.12 quick-judgment step 2「誰失誤？」 ------------------------------
   Same overlay shell: a compact skill chip row on top (letter keys) over the
   losing side's on-court 3×2 grid (number keys), then the 跳過 escape hatch.
   The grid mirrors the sanction picker so the operator's muscle memory for
   「找場上那個人」 carries over. */
.fault-who {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.fault-who-hint { margin: 0; font-size: 0.82rem; color: var(--p-ink-10); }
.fault-who-label {
  margin: 2px 0 0;
  font-size: 0.78rem;
  font-weight: 700;
  color: var(--p-ink-13);
}
.fault-who-skills {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.fault-who-skill {
  position: relative;
  min-width: 74px;
  min-height: 42px;
  padding: 4px 10px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
  background: var(--p-surf-23);
  color: var(--p-blu-02);
  border: 1px solid var(--p-surf-02);
  border-radius: 8px;
  cursor: pointer;
  font-size: 0.9rem;
}
.fault-who-skill:hover { background: var(--p-surf-13); }
/* A skill/player the volleyball rules cannot accept for THIS rally (second
   serve, self-double, 4th hit) is offered greyed out rather than hidden — the
   operator sees that the option exists and why it is unavailable (title). */
.fault-who-skill:disabled,
.fault-who-pick:disabled {
  opacity: 0.35;
  cursor: not-allowed;
}
.fault-who-skill:disabled:hover { background: var(--p-surf-23); }
.fault-who-pick:disabled:hover { background: var(--p-surf-23); }
.fault-who-skill.is-active {
  background: var(--p-blu-21);
  border-color: var(--p-blu-07);
  font-weight: 700;
}
.fault-who-skill .fw-key {
  font-size: 0.68rem;
  color: var(--c-note-info);
  font-weight: 700;
}
.fault-who-skill.is-active .fw-key { color: var(--p-yel-01); }
.fault-who-players {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
}
.fault-who-players .fault-who-bench {
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.fault-who-note {
  grid-column: 1 / -1;
  font-size: 0.78rem;
  color: var(--p-ink-13);
}
.fault-who-pick {
  position: relative;
  min-height: 58px;
  padding: 8px 6px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  background: var(--p-surf-23);
  color: var(--p-blu-02);
  border: 1px solid var(--p-surf-02);
  border-radius: 8px;
  cursor: pointer;
  flex: 1 1 96px;
}
.fault-who-pick:hover { background: var(--p-surf-13); }
.fault-who-pick.is-suggested { border-color: var(--p-yel-03); }
.fault-who-pick .fw-no { font-size: 1.2rem; font-weight: 700; }
.fault-who-pick .fw-name {
  font-size: 0.74rem;
  color: var(--p-ink-10);
  max-width: 8em;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.fault-who-pick .fw-suggest {
  font-size: 0.66rem;
  color: var(--c-note-warn);
  font-weight: 700;
}
/* The 跳過 escape hatch: full width, but a FIXED height — inside the column
   flexbox a plain .fault-btn (flex: 1 1 160px) would grow to eat the panel. */
.fault-who-skip {
  align-self: stretch;
  flex: 0 0 auto;
  min-height: 56px;
}

/* ---- v2.4 timeout two-button overlay (reuses the fault-overlay shell) ----
   Team-colored left edges echo the rally-close buttons (home green /
   away red). */
.timeout-btn-home { border-left: 5px solid var(--team-home); }
.timeout-btn-away { border-left: 5px solid var(--team-away); }

/* ---- v2.13 FIVB Rule 21 sanction overlay (reuses the fault-overlay shell) --
   Guided flow: team -> card -> player (on-court 3×2 + bench + 隊職員) ->
   confirm. Card level accents echo the physical cards (yellow / red). */
.sanction-panel { max-width: 680px; }
.sanction-body {
  display: flex;
  flex-direction: column;
  gap: 10px;
  max-height: 60vh;
  overflow-y: auto;
}
.sanction-foot {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}

/* card-level buttons: left edge accents by ladder step */
.sanction-cards .sanction-card-btn { border-left: 6px solid var(--p-line-04); }
.sanction-card-btn.sc-warning { border-left-color: var(--p-yel-05); }
.sanction-card-btn.sc-penalty { border-left-color: var(--p-red-17); }
.sanction-card-btn.sc-expulsion { border-left-color: var(--p-red-07); }
.sanction-card-btn.sc-disqualification { border-left-color: var(--p-red-19); }

/* player picker: on-court 3×2 grid + bench flow, mirroring the touch layer */
.sanction-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
}
.sanction-bench {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.sanction-benchlabel,
.sanction-note { font-size: 0.8rem; color: var(--p-ink-13); }
.sanction-pick {
  flex: 1 1 96px;
  min-height: 56px;
  gap: 2px;
}
.sanction-pick .sp-no { font-size: 1.25rem; font-weight: 700; }
.sanction-pick .sp-name {
  font-size: 0.75rem;
  color: var(--p-ink-10);
  max-width: 8em;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.sanction-official {
  align-self: stretch;
  min-height: 48px;
  text-align: center;
}

/* team-step 撤銷上一張牌 entry — amber, set apart from the team picks */
.sanction-revoke {
  align-self: stretch;
  min-height: 48px;
  background: var(--p-surf-25);
  border-color: var(--p-surf-01);
  color: var(--c-action-ink);
}
.sanction-revoke:disabled { opacity: 0.5; }

/* confirm step */
.sanction-summary { margin: 0; font-size: 1.15rem; font-weight: 700; }
.sanction-warn {
  margin: 0;
  font-weight: 700;
  color: var(--p-red-06);
}
.sanction-confirm { background: var(--p-red-23); border-color: var(--p-red-18); }

/* small card marker (黃 / 紅 / 紅黃) on picker buttons + the revoke summary */
.sc-cardbadge {
  position: absolute;
  top: 4px;
  right: 4px;
  display: inline-flex;
  gap: 2px;
  pointer-events: none;
}
.sc-card {
  width: 9px;
  height: 12px;
  border-radius: 2px;
  border: 1px solid var(--c-overlay-backdrop);
}
.sc-card-y { background: var(--p-yel-05); }
.sc-card-r { background: var(--p-red-17); }

/* ---- v2.15 settings two-section layout (賽前設定 / 使用時設定) ------------ */
.settings-section-title {
  margin: 14px 0 4px;
  padding-bottom: 4px;
  border-bottom: 1px solid var(--p-surf-18);
  font-size: 1rem;
}
.settings-prematch {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-bottom: 4px;
}
.settings-prematch select:disabled,
.settings-prematch input:disabled { opacity: 0.6; }
.settings-prematch-note { margin: 0 0 6px; }

/* ---- v2.15 FIVB VCS coach-challenge overlay (reuses the fault-overlay shell) --
   Guided flow: team -> success/fail -> confirm; a success then offers three
   後續處理 handlings (判決反轉 green / 回合重開 amber / 僅記錄 neutral). */
.challenge-panel { max-width: 640px; }
.challenge-body {
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin: 6px 0;
  max-height: 62vh;
  overflow-y: auto;
}
.challenge-summary { margin: 0; font-size: 1.15rem; font-weight: 700; }
/* v3.10 挑戰原因 picker (optional, on the confirm step). Deliberately quieter
   than the action buttons: it must never look like the thing being confirmed. */
.challenge-reason-field {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 0.9rem;
  color: var(--muted);
}
.challenge-reason-field select {
  flex: 1;
  min-height: 44px;
  padding: 0.3rem 0.6rem;
  font-size: 1rem;
  background: var(--panel-alt, var(--p-surf-36));
  color: var(--text);
  border: 1px solid var(--line);
  border-radius: 8px;
}
.challenge-team-btn:disabled,
.challenge-follow-btn:disabled { opacity: 0.45; cursor: not-allowed; }
.challenge-exhausted { color: var(--p-red-06); font-weight: 700; }
/* result step accents: success green / fail red left edge */
.challenge-result-success { border-left: 6px solid var(--p-grn-05); }
.challenge-result-fail { border-left: 6px solid var(--p-red-17); }
/* 後續處理 three-choice: distinct left-edge accents, left-aligned copy */
.challenge-follow { flex-direction: column; }
.challenge-follow-btn {
  border-left: 6px solid var(--p-line-04);
  text-align: left;
  align-items: flex-start;
}
.challenge-follow-reverse { border-left-color: var(--p-grn-05); }
.challenge-follow-reopen { border-left-color: var(--p-org-10); }
.challenge-follow-record { border-left-color: var(--p-line-04); }
/* team-step 撤銷上一筆挑戰 entry — amber, matching the sanction revoke entry */
.challenge-revoke {
  align-self: stretch;
  min-height: 48px;
  background: var(--p-surf-25);
  border-color: var(--p-surf-01);
  color: var(--c-action-ink);
}
.challenge-revoke:disabled { opacity: 0.5; }

/* v3.0.2: back-to-entry link on the match-list screen (mirrors .sheet-back) */
.scout-back {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-min);
  padding: 0.4rem 0.8rem;
  border: 1px solid var(--c-border);
  border-radius: 8px;
  background: var(--c-surface-alt);
  color: var(--c-text);
  text-decoration: none;
  margin-bottom: 0.4rem;
}
.scout-back:active { filter: brightness(1.2); }

/* ---- v3.3 名單來源（貼上名單／從球隊庫選擇）＋預覽表格 ---- */

.roster-seg {
  display: flex;
  gap: 0;
  margin: 0.5rem 0 0.4rem;
}
.roster-seg .seg-btn {
  flex: 1 1 0;
  min-height: var(--tap-min);
  padding: 0.4rem 0.6rem;
  background: var(--c-surface-alt);
  color: var(--p-ink-08);
  border: 1px solid var(--c-border);
  cursor: pointer;
  font-size: 0.95rem;
}
.roster-seg .seg-btn:first-child { border-radius: 8px 0 0 8px; }
.roster-seg .seg-btn:last-child { border-radius: 0 8px 8px 0; border-left: none; }
.roster-seg .seg-btn.is-active {
  background: var(--p-blu-12);
  border-color: var(--p-blu-12);
  color: var(--p-ink-01);
  font-weight: 600;
}

.roster-tools {
  align-items: center;
  gap: 0.6rem;
  margin: 0.3rem 0 0.2rem;
}
/* v3.5: a native <input type="file"> has a ~250px min-content width and does
   NOT shrink, so at 1024px it pushed 70px past the fieldset and gave the whole
   PAGE a horizontal scrollbar. min-width:0 lets the flex item shrink and the
   input is capped to the space actually available. */
.roster-file-label {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.9rem;
  color: var(--p-ink-08);
  min-width: 0;
  flex: 1 1 auto;
}
.roster-file {
  font-size: 0.85rem;
  min-width: 0;
  max-width: 100%;
  flex: 1 1 auto;
}

/* the tools row itself must be allowed to wrap and to shrink its children */
.roster-tools > * { min-width: 0; }

.roster-table-hint { font-size: 0.85rem; margin: 0.2rem 0 0.3rem; }

/* v3.3 可編輯名單表格（建賽表單與球隊庫編輯器共用 rostertable.js） */
.roster-input-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.9rem;
}
.roster-input-table th,
.roster-input-table td {
  border-bottom: 1px solid var(--c-divider);
  padding: 0.1rem 0.25rem;
  text-align: left;
}
.roster-input-table th { color: var(--p-ink-15); font-weight: 600; }
.roster-input-table input,
.roster-input-table select { margin: 0; width: 100%; }
.roster-input-table .rt-flag { text-align: center; width: 3.4rem; }
.roster-input-table .rt-flag input { width: auto; }
.roster-input-table .rt-number { width: 4.5rem; }
.roster-input-table .rt-del { padding: 0.1rem 0.5rem; min-height: 1.8rem; }
.roster-input-table .rt-row-error input,
.roster-input-table .rt-row-error select { border-color: var(--p-red-20); }

.preview-errors {
  margin: 0.35rem 0 0;
  padding-left: 1.1rem;
  color: var(--p-red-04);
  font-size: 0.88rem;
}

/* ---- v3.3 建賽表單的球隊庫勾選盤 ---- */

/* v3.5: the squad list is shown IN FULL — no inner scroll box. A 16-rem
   scroller hid most of a 16-player squad and made the 12+2 selection a
   guessing game; the page scrolls instead. */
.library-pick {
  border: 1px solid var(--c-border);
  border-radius: 8px;
  padding: 0.3rem 0.5rem;
  margin-bottom: 0.3rem;
}
.lib-pick-row {
  display: flex;
  margin: 0;
  align-items: center;
  gap: 0.4rem;
  min-height: 2rem;
  padding: 0.1rem 0;
  cursor: pointer;
}
.lib-pick-row input[type="checkbox"] { width: auto; flex: 0 0 auto; margin: 0; }
.lib-pick-no { min-width: 2.4rem; font-variant-numeric: tabular-nums; font-weight: 600; }
.lib-pick-name { flex: 1 1 auto; }
.lib-tag {
  font-size: 0.75rem;
  padding: 0.05rem 0.35rem;
  border-radius: 999px;
  background: var(--p-surf-20);
  color: var(--p-ink-05);
}
.lib-tag-l { background: var(--p-surf-12); color: var(--c-action-ink); }

.library-count { margin: 0.2rem 0; font-size: 0.9rem; color: var(--p-ink-08); }
.library-count.over { color: var(--p-red-04); font-weight: 600; }

/* ---- v3.3 球隊名單庫管理面板 ---- */

dialog.dlg-wide { min-width: min(56rem, 94vw); max-height: 88vh; overflow-y: auto; }

.library-list { margin: 0.4rem 0 0.8rem; }
.library-row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.4rem 0.2rem;
  border-bottom: 1px solid var(--c-divider);
}
.library-row-label { flex: 1 1 auto; }

.library-staff { display: grid; gap: 0.3rem; margin-bottom: 0.5rem; }
.staff-row { display: flex; gap: 0.4rem; align-items: center; }
.staff-row select, .staff-row input { width: auto; flex: 1 1 8rem; margin: 0; }

/* 庫編輯器的球員表格＝同一個 .roster-input-table 元件，只多一欄「正式」。 */
.library-players-table { margin-bottom: 0.3rem; }

/* ---- v3.3.1 開賽前名單編輯（名單確認畫面） ---- */

.roster-edit-row { align-items: center; gap: 0.6rem; margin-top: 0.5rem; }
.roster-lock-note { font-size: 0.88rem; }
#btn-edit-roster-HOME:disabled,
#btn-edit-roster-AWAY:disabled { opacity: 0.45; }

/* v3.5: collapsible FIVB P-2 competition-details block on the create form.
   Collapsed by default so the (already long) form stays scannable. */
.create-meta {
  border: 1px solid var(--c-border);
  border-radius: var(--radius);
  padding: 0.4rem 0.6rem;
  margin-bottom: 0.5rem;
}

.create-meta > summary {
  cursor: pointer;
  font-weight: 600;
  color: var(--c-text-dim);
  padding: 0.2rem 0;
}

.create-meta[open] > summary { color: var(--c-text); margin-bottom: 0.3rem; }

/* v3.5: the library pane shows the WHOLE squad vertically (no inner y-scroll,
   see .library-pick); the extra 正式 column can still overflow the narrow
   create-form column, so only the table scrolls sideways. Keeping the scroller
   on this inner wrapper leaves .library-pick's own scrollHeight untouched. */
.library-table-scroll { overflow-x: auto; }
.library-players-table .rt-name { min-width: 7rem; }
.library-players-table .rt-position { min-width: 6.5rem; }

/* v3.5: keep the match-row action buttons on one line — the narrower list
   column made 「進入」 wrap to two characters tall. */
.match-row .btn,
.match-row a { white-space: nowrap; }

/* ---- v3.9 歷史回合編輯器 (§8.14, js/scout/rallyedit.js) -------------------
   Reuses the fault-overlay shell (big touch targets, no <dialog>). Two views
   share #rally-body: the SET's rally list, and one rally's touch editor with
   its re-adjudication row. The panel is wider than the ruling overlays — a
   rally chain plus its ruling has to fit on one line — and scrolls inside
   itself so the overlay never pushes the page. */
.rally-panel { max-width: 860px; }
.rally-body {
  display: flex;
  flex-direction: column;
  gap: 10px;
  max-height: 68vh;
  overflow-y: auto;
}

.re-note {
  margin: 0;
  font-size: 0.8rem;
  color: var(--c-note-warn);
  background: var(--p-yel-04);
  border-left: 3px solid var(--p-yel-03);
  padding: 4px 8px;
  border-radius: 4px;
}
.re-empty { margin: 0; color: var(--p-ink-13); font-size: 0.9rem; }
.re-error { color: var(--p-red-05); }

.re-settabs { display: flex; flex-wrap: wrap; gap: 6px; }
.re-settab-on { background: var(--p-surf-06); border-color: var(--p-blu-13); font-weight: 700; }

.re-list { list-style: none; margin: 0; padding: 0; display: flex;
  flex-direction: column; gap: 4px; }
.re-row {
  display: grid;
  grid-template-columns: 2.6rem 3.6rem 4.5rem 1fr 8.5rem;
  align-items: center;
  gap: 6px;
  padding: 5px 8px;
  border: 1px solid var(--p-surf-08);
  border-radius: 6px;
  background: var(--c-surface-sunken);
  cursor: pointer;
  font-size: 0.85rem;
}
.re-row:hover, .re-row:focus { background: var(--p-surf-30); outline: none; }
.re-no { color: var(--p-ink-13); font-variant-numeric: tabular-nums; }
.re-score { font-weight: 700; font-variant-numeric: tabular-nums; }
.re-winner.re-home { color: var(--team-home-text); }
.re-winner.re-away { color: var(--team-away-text); }
.re-chain { display: flex; flex-wrap: wrap; gap: 3px; }
.re-reason { color: var(--p-blu-03); font-size: 0.8rem; text-align: right; }

.re-chip {
  padding: 1px 5px;
  border-radius: 4px;
  font-size: 0.75rem;
  white-space: nowrap;
}
.re-chip.re-home { background: var(--team-home-tint); color: var(--team-home-tint-ink); }
.re-chip.re-away { background: var(--team-away-tint); color: var(--team-away-tint-ink); }
.re-chip-none { color: var(--p-ink-16); }

.re-touches { list-style: none; margin: 0; padding: 0; display: flex;
  flex-direction: column; gap: 4px; }
.re-touch {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  padding: 4px 8px;
  border: 1px solid var(--p-surf-08);
  border-radius: 6px;
  background: var(--c-surface-sunken);
  font-size: 0.88rem;
}
.re-tno { color: var(--p-ink-13); min-width: 1.4rem; font-variant-numeric: tabular-nums; }
.re-player { font-weight: 700; }
.re-extra { color: var(--p-ink-12); font-size: 0.8rem; }
.re-eval { font-weight: 700; min-width: 1.2rem; text-align: center; }
.re-ev-win { color: var(--p-grn-02); }
.re-ev-err { color: var(--p-red-05); }
.re-acts { margin-left: auto; display: flex; gap: 4px; }
.re-mini { padding: 2px 8px; font-size: 0.78rem; }
.re-danger { border-color: var(--p-line-08); color: var(--p-red-01); }

.re-form {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 8px;
  border: 1px solid var(--p-blu-13);
  border-radius: 6px;
  background: var(--p-surf-35);
}
.re-form-title { margin: 0; font-weight: 700; color: var(--c-note-info); font-size: 0.9rem; }
.re-frow { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.re-flabel { color: var(--p-ink-13); font-size: 0.8rem; min-width: 2.6rem; }
.re-opt { padding: 4px 10px; font-size: 0.85rem; }
.re-opt-on { background: var(--p-surf-06); border-color: var(--p-blu-13); font-weight: 700; }
.re-opt-ev { min-width: 2.2rem; font-weight: 700; }
.re-opt[disabled] { opacity: var(--c-opacity-dim); cursor: not-allowed; }
.re-select { padding: 4px 6px; }
.re-warn { margin: 0; color: var(--p-red-01); font-size: 0.82rem; }
.re-fbtns { margin-top: 2px; }
.re-insert-tail { align-self: flex-start; font-size: 0.82rem; }

.re-fault {
  border-top: 1px solid var(--p-surf-08);
  padding-top: 8px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.re-fault-title { margin: 0; font-size: 0.85rem; color: var(--p-blu-03); }

@media (max-width: 640px) {
  .re-row { grid-template-columns: 2.2rem 3.2rem 1fr; }
  .re-chain, .re-reason { grid-column: 1 / -1; text-align: left; }
}

/* ---- v3.11 完賽鎖（唯讀態）／棄權判負／設定兩層 ------------------------- */

/* The record screen after the final whistle. The touch layer dims its own
   controls (touch.css .tm-readonly); this covers the shell's toolbar buttons
   (暫停／挑戰／例外替補／棄權), which app.js disables. 更多 ▾ itself stays
   fully live — 回合瀏覽／換邊／匯出 are deliberately still allowed. */
#screen-record.is-readonly #btn-timeout:disabled,
#screen-record.is-readonly #btn-challenge:disabled,
#screen-record.is-readonly .more-item:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}
/* The persistent 「比賽已結束——記錄畫面為唯讀」 banner: red-edged so it does
   not read as the neutral set-end notice, and NOT auto-dismissed (it states a
   standing condition, so it stays until 上一球 puts the match back to live). */
#screen-record.is-readonly > #match-banner {
  background: var(--p-surf-37);
  border-color: var(--p-red-12);
}

/* v3.16.1: the 更多 ▾ separator + danger styling went with the 棄權／判負 entry
   (and #dlg-forfeit with it) — nothing in the menu is irreversible any more. */

/* 設定 → 賽前設定, v3.11 two tiers. Once the match starts the format block is
   hidden outright and this single line states the settled format instead. */
.settings-format-locked {
  margin: 0 0 8px;
  padding: 4px 8px;
  border-left: 3px solid var(--p-surf-09);
}

/* ---- v3.59（§8.26）live_mode 'both'：VIS 只記觸球（自由球員進出暫時例外）------
   In a `both` match the e-scoresheet keeps every official record and this
   console records touches only (決策 0009 §6, user 2026-09-25/26). app.js
   (applyLiveModeUi) mirrors the loaded match's mode onto <body data-live-mode>
   and every entry to a write VIS no longer makes is HIDDEN here — keyed off one
   ancestor attribute because half of these elements belong to the touch layer,
   which rebuilds them without asking the shell. The handlers refuse as well
   (app.js SHEET_LEADS_WRITABLE / outboxEnqueue); this is the display half.
   Kept on purpose: 取消, 上一動作 (a correction), 重打 (local), 換邊,
   回合瀏覽, 陣容設定 (read-only there), 設定 — and 替補 plus the bench libero
   entries: libero replacements stay VIS's in `both` for now (user 2026-09-26,
   temporary), and 替補 itself explains that on a tap (touch.js startSubFlow)
   instead of vanishing. 例外替補／撤銷換人 are regular substitutions: hidden. */
body[data-live-mode="both"] .tm-rally,
body[data-live-mode="both"] .tm-faultrow,
body[data-live-mode="both"] .tm-prevrally-btn,
body[data-live-mode="both"] #btn-sub-exc,
body[data-live-mode="both"] #btn-sub-revoke,
body[data-live-mode="both"] #btn-timeout,
body[data-live-mode="both"] #btn-challenge,
body[data-live-mode="both"] #btn-sanction,
body[data-live-mode="both"] #btn-score-correct,
body[data-live-mode="both"] [data-submit-lineup],
body[data-live-mode="both"] #btn-set-start,
body[data-live-mode="both"] .lineup-helpers,
body[data-live-mode="both"] #fs-required-note,
body[data-live-mode="both"] .roster-edit-row button,
body[data-live-mode="both"] #btn-save-positions,
body[data-live-mode="both"] #btn-roster-edit-save,
body[data-live-mode="both"] #settings-prematch-edit {
  display: none !important;
}
/* ---- v3.60（P-0031 §4.0-4）live_mode 'sheet_only'：這一場沒有 VIS ------------
   The e-scoresheet keeps the WHOLE record and the server refuses every write
   from this console (touches included), so VIS only SHOWS the match: every entry
   hidden in `both` above, plus the touch input layer itself (the court, the
   chips, the bench libero entries, the keyboard module), the per-rally group of
   操作 ▾ (取消／上一動作／上一球／重打, mounted there by touch.js), 替補 and the
   two input-layer entries of 更多 ▾. Kept: the scorebar, 換邊, 回合瀏覽 (to look;
   an edit is refused), 陣容設定 (read-only), 設定 (the operator's preferences) and
   回比賽列表. The handlers refuse as well (app.js SHEET_ONLY_WRITABLE is empty;
   core.bufferTouch refuses before a chip exists). */
body[data-live-mode="sheet_only"] .tm-rally,
body[data-live-mode="sheet_only"] .tm-faultrow,
body[data-live-mode="sheet_only"] .tm-prevrally-btn,
body[data-live-mode="sheet_only"] #btn-sub-exc,
body[data-live-mode="sheet_only"] #btn-sub-revoke,
body[data-live-mode="sheet_only"] #btn-timeout,
body[data-live-mode="sheet_only"] #btn-challenge,
body[data-live-mode="sheet_only"] #btn-sanction,
body[data-live-mode="sheet_only"] #btn-score-correct,
body[data-live-mode="sheet_only"] [data-submit-lineup],
body[data-live-mode="sheet_only"] #btn-set-start,
body[data-live-mode="sheet_only"] .lineup-helpers,
body[data-live-mode="sheet_only"] #fs-required-note,
body[data-live-mode="sheet_only"] .roster-edit-row button,
body[data-live-mode="sheet_only"] #btn-save-positions,
body[data-live-mode="sheet_only"] #btn-roster-edit-save,
body[data-live-mode="sheet_only"] #settings-prematch-edit,
body[data-live-mode="sheet_only"] #input-layer,
body[data-live-mode="sheet_only"] #actions-group-rally,
body[data-live-mode="sheet_only"] #actions-group-sub,
body[data-live-mode="sheet_only"] #record-actions-menu .more-sep,
body[data-live-mode="sheet_only"] #btn-kbhelp,
body[data-live-mode="sheet_only"] #btn-input-mode {
  display: none !important;
}
/* 「比分由電子紀錄判定」: its own item of the one-row header, next to the meta
   block and at its size, in the accent the scorebar uses for 待配對. At most two
   short lines (max-width), so it can never be what makes the header wrap. */
.sb-live-mode {
  flex: 0 1 auto;
  max-width: 6em;
  font-size: clamp(11px, 1.35vw, 0.8rem);
  line-height: 1.25;
  color: var(--p-org-09);
  font-weight: bold;
}
