/* DeepEnough — component layer (DD-006 B2)
   Every rule here references tokens.css only. The sole literal values
   allowed are 1px hairline border widths, SVG stroke-width, and fixed
   control geometry that has no equivalent in the spacing/radius scale
   (e.g. a 36px icon-button square, a 460px modal max-width) — there is no
   token for arbitrary intrinsic control dimensions, so those stay literal.
   Colors, spacing rhythm, radius, and type all come from tokens.css.

   Load order: tokens.css -> components.css (this file) -> app.css. */

/* ---- Display face (wordmark only) ---------------------------------------
   Self-hosted, subset to the 9 glyphs the wordmark draws (DEeghnopu), woff2,
   ~1.4KB (web/static/fonts/). SIL OFL 1.1 — see fonts/OFL.txt. Same-origin
   under /static/fonts/, so the existing CSP (`default-src 'self'`, no
   explicit font-src) already permits it; no header change needed. Referenced
   only by --font-display (tokens.css), which only .brand-name uses — every
   other UI element stays on --font-sans (DESIGN §1/§3 "one family" rule). */
@font-face {
  font-family: "DE Display";
  src: url("/static/fonts/bricolage-wordmark.woff2?v=1") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}

/* ---- Hidden attribute --------------------------------------------------
   The `hidden` attribute must always defeat a component's own display rule.
   Class selectors like `.foot-btn { display: inline-flex }` tie the UA
   `[hidden] { display: none }` rule on specificity and win on source order,
   which left `#key-accept` (Accept -> memory) visible and clickable at the
   idle empty state. app.js only ever toggles the attribute (never
   `style.display`), so forcing it here is safe for every hidden element
   (toast, settings overlay, panel, custom-model inputs). (DD-006 UIUX R1) */
[hidden] { display: none !important; }

/* ---- Button base (.btn) -------------------------------------------------
   Shared interaction recipe for every button-like control (.btn-primary,
   .btn-ghost, .icon-btn, .accept-btn, .foot-btn, .swap-btn, .modal-btn,
   .toast-action, and the .btn-secondary file-input trigger label): the Liquid
   Glass spring press, the :active squish, and the focus-visible ring. Each
   control below adds only its own shape/fill on top; markup carries
   class="btn <variant>".
     .btn-primary   — pill CTA, filled accent. Reserved for Accept -> memory
       and Save settings (DD §2: pill radius is a primary-CTA-only signal).
     .btn-ghost     — quiet 8px-radius rectangle for clear/copy/swap.
     .icon-btn      — 36px square icon-only control (header actions, close).
     .btn-secondary — card-fill + hairline rectangle (modal/panel/file trigger). */
.btn {
  /* Transform springs (small overshoot, then settle) — the Liquid Glass
     press/hover feel; color/border stay on the quick standard ease. */
  transition: transform var(--dur-press) var(--ease-spring),
    background-color var(--dur-fast) var(--ease-standard),
    border-color var(--dur-fast) var(--ease-standard),
    color var(--dur-fast) var(--ease-standard),
    box-shadow var(--dur-fast) var(--ease-standard);
}
.btn:active { transform: scale(var(--press-scale)); }

/* ---- Focus ring (shared) ------------------------------------------------
   One 2px outline recipe for every keyboard-focusable control: the button
   base (.btn), the language selects, the modal "How to get a key" link, the
   tag badge, and the readonly Base URL field. Defined once here; each control
   adds only what differs (the readonly field also pins its resting border,
   below). WCAG 2.4.7. */
.btn:focus-visible,
.lang-select:focus-visible,
.modal-keydocs:focus-visible,
.tag:focus-visible,
.modal-field input[readonly]:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

/* ---- Liquid-glass CTA specular -----------------------------------------
   Filled accent CTAs get a 1px specular highlight along the TOP edge only
   (an inset highlight, never a drop shadow, and never behind the centred
   label — so --on-accent contrast is untouched). Hover swells 2%, press
   squishes to --press-scale; the shared spring transition animates both.
   The :active rule is re-asserted after :hover so a press always wins. */
.btn-primary,
.accept-btn,
.modal-btn.primary {
  box-shadow: inset 0 1px 0 var(--glass-specular);
}
.btn-primary:hover,
.accept-btn:hover,
.modal-btn.primary:hover {
  transform: scale(1.02);
}
.btn-primary:active,
.accept-btn:active,
.modal-btn.primary:active {
  transform: scale(var(--press-scale));
}

.btn-primary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  height: 36px;
  padding: 0 var(--sp-5);
  border: none;
  border-radius: var(--r-pill);
  background: var(--accent);
  color: var(--on-accent);
  font-family: var(--font-sans);
  font-size: var(--fs-chrome-md);
  font-weight: var(--fw-semibold);
  cursor: pointer;
}
.btn-primary:hover { background: var(--accent-hover); }

.btn-ghost {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  height: 36px;
  padding: 0 var(--sp-3);
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--text-muted);
  font-family: var(--font-sans);
  font-size: var(--fs-chrome-sm);
  font-weight: var(--fw-regular);
  cursor: pointer;
}
.btn-ghost:hover { background: var(--surface); color: var(--text); }

.icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border: 1px solid transparent;
  border-radius: var(--r-md);
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
}
/* Hover = a translucent glass chip (a fill, not a second backdrop-filter, so
   it never nests a blur inside the already-frosted nav) with a top specular
   line. */
.icon-btn:hover {
  background: var(--glass-bg);
  color: var(--text);
  box-shadow: inset 0 1px 0 var(--glass-edge);
}
/* Theme toggle swaps which glyph shows via the root data-theme (frozen
   contract, DD-006 §3 — selectors unchanged). */
.theme-toggle .moon { display: none; }
[data-theme="dark"] .theme-toggle .sun { display: none; }
[data-theme="dark"] .theme-toggle .moon { display: block; }

/* Quiet per-pane actions (clear/copy/swap): 8px ghost rectangle. */
.foot-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-1);
  height: 30px;
  padding: 0 var(--sp-2);
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--text-muted);
  font-size: var(--fs-chrome-sm);
  cursor: pointer;
}
.foot-btn:hover {
  background: var(--surface);
  color: var(--text);
}
.foot-btn svg.icon { width: 16px; height: 16px; }

/* Accept -> memory: the one primary CTA inside the translate card, so it
   gets the pill/filled treatment instead of the ghost recipe above. */
.accept-btn {
  gap: var(--sp-1);
  height: 30px;
  padding: 0 var(--sp-3);
  border: none;
  border-radius: var(--r-pill);
  background: var(--accent);
  color: var(--on-accent);
  font-size: var(--fs-chrome-sm);
  font-weight: var(--fw-semibold);
}
.accept-btn:hover { background: var(--accent-hover); }

/* Swap-languages control: a quiet icon button between the two selects.
   36px matches the selects' own computed height so the langbar's three
   controls share one horizontal baseline (DD-006 lens1 P3). Border removed
   at rest (ENH-007): --card === --bg in light mode made both the fill and
   the hairline the only things separating this control from the page —
   --surface gives it a real fill boundary, matching the selects it sits
   between; the accent border still appears on hover/focus below. */
.swap-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border: 1px solid transparent;
  border-radius: var(--r-md);
  background: var(--surface);
  color: var(--text-muted);
  cursor: pointer;
}
.swap-btn:hover {
  border-color: var(--accent);
  color: var(--accent);
  box-shadow: inset 0 1px 0 var(--glass-edge);
}

/* ---- Secondary button (.btn-secondary) ----------------------------------
   Card-fill rectangle with a 1px hairline and --r-md radius — the quiet
   "form action" look shared by the settings-modal buttons, the htmx panel
   form buttons (Add/Save/Delete), and the file-input trigger label. inline-
   flex so a <label for> renders as a proper button box, not an inline run.
   .primary fills with accent (Save / submit); .danger tints the label and its
   hover border error (Delete / Remove keys). --sp-3 side padding is the panel
   default; .modal-btn widens it to --sp-4. */
.btn-secondary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  background: var(--card);
  border: 1px solid var(--hairline);
  border-radius: var(--r-md);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: var(--fs-chrome-md);
  font-weight: var(--fw-semibold);
  padding: var(--sp-2) var(--sp-3);
  cursor: pointer;
}
.btn-secondary:hover { border-color: var(--hairline-strong); }
.btn-secondary.primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}
.btn-secondary.primary:hover { background: var(--accent-hover); }
.btn-secondary.danger { color: var(--error); }
.btn-secondary.danger:hover { border-color: var(--error); color: var(--error); }

/* ---- Text inputs + textarea ---------------------------------------------
   Shared box model across the settings modal and htmx panel fragments. -- */
.panel input[type=text],
.panel textarea,
.modal-field input {
  width: 100%;
  background: var(--bg);
  color: var(--text);
  /* r7: border is decorative-weight (--control-border-soft, see tokens.css
     CONTROL IDENTIFICATION STRATEGY) — the bounded box + focus-to-accent
     border below carry the WCAG 1.4.11 identification duty. 16px text
     avoids iOS focus-zoom. */
  border: 1px solid var(--control-border-soft);
  border-radius: var(--r-md);
  font-family: var(--font-sans);
  font-size: var(--fs-input);
  padding: var(--sp-2) var(--sp-3);
}
.panel input[type=text]:focus,
.panel textarea:focus,
.modal-field input:focus {
  outline: none;
  border-color: var(--accent);
}
.panel input[type=text]::placeholder,
.panel textarea::placeholder,
.modal-field input::placeholder { color: var(--text-subtle); }
.panel textarea { resize: vertical; min-height: 4rem; }
/* Readonly Base URL: auto-filled by a provider pick, not meant to be edited. */
.modal-field input[readonly] {
  background: var(--surface);
  color: var(--text-muted);
  cursor: default;
}
/* Readonly gets the shared focus ring (grouped above) but pins its resting
   border rather than switching to an accent it can't act on. WCAG 2.4.7. */
.modal-field input[readonly]:focus-visible {
  border-color: var(--control-border-soft);
}

/* ---- Select ---------------------------------------------------------- */
.panel select,
.lang-select,
.modal-select {
  width: 100%;
  /* min-width:0 on the wrapper div isn't enough: a <select> keeps a
     content-driven automatic minimum width that Chromium honors as a shrink
     floor, overflowing the target select past the viewport at <=390px.
     Setting it on the control itself is the standard fix. (DD-006 UIUX R1) */
  min-width: 0;
  appearance: none;
  -webkit-appearance: none;
  /* appearance:none strips the native dropdown arrow, so draw our own chevron
     as a data-URI background (CSP img-src 'self' data: allows it). Its stroke
     is a fixed hex per theme — background images can't inherit currentColor —
     so a dark-theme override follows below. Right padding reserves room. */
  /* --control-fill (not a plain var(--surface) reference): any ancestor
     container whose own fill equals --card (which aliases --bg/--surface per
     theme, tokens.css) sets --control-fill: var(--surface-2) on itself
     (below) so a select nested inside never collides with its parent
     (simplify pass, generalizing what was a hardcoded 2-selector list). New
     containers that need this only ever touch their own rule, never this
     shared one. */
  background-color: var(--control-fill, var(--surface));
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%236e6e73' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--sp-3) center;
  background-size: 12px 12px;
  color: var(--text);
  /* r7: resting border removed (ENH-007) — it was already decorative-weight
     and not the WCAG 1.4.11 carrier (tokens.css CONTROL IDENTIFICATION
     STRATEGY: the chevron above, currentColor-strength per theme, plus the
     hover/focus accent border below, both independently >=3:1, already
     carry that duty). Background moved from --card to --surface: --card
     equals --bg in light mode, which left the control with zero fill
     boundary once the border is gone. 16px avoids iOS focus-zoom. */
  border: 1px solid transparent;
  border-radius: var(--r-md);
  padding: var(--sp-2) var(--sp-6) var(--sp-2) var(--sp-3);
  font-family: var(--font-sans);
  font-size: var(--fs-input);
  font-weight: var(--fw-regular);
  cursor: pointer;
}
[data-theme="dark"] .panel select,
[data-theme="dark"] .lang-select,
[data-theme="dark"] .modal-select {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23a1a1a6' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
}
/* .modal-select sits on .modal-card, and #panel select sits on #panel — both
   containers fill with --card, which equals --surface in dark mode
   (tokens.css:212). A plain --surface select background would be visually
   identical to either container (1.00:1, code review REV-code-v1-A M1: an
   earlier version of this comment claimed .panel select "never sits on a
   --card-filled parent" — it does, via #panel's own background, components.css
   ~445). --control-fill is inherited (custom properties inherit by default),
   so setting it once here on each container reaches every select nested
   inside without editing the shared select rule above; --surface stays the
   default there for .lang-select, which sits directly on the page --bg. */
.modal-card,
#panel { --control-fill: var(--surface-2); }
.panel select:hover,
.lang-select:hover,
.modal-select:hover { border-color: var(--accent); }
.panel select:focus,
.lang-select:focus,
.modal-select:focus {
  outline: none;
  border-color: var(--accent);
}

/* ---- Tabs: segmented glass capsule ---------------------------------------
   DD-006 r5. .tabs is the capsule TRACK (recessed glass, --glass-track, its
   own backdrop-filter recipe), applied to the tab row in index.html
   (class="tabs app-tabs"). .tab-chip is a single absolutely-positioned pill
   that a small app.js helper slides under whichever `.tab` is active
   (offsetLeft/offsetWidth, updated on activation + resize) — the raised,
   brighter glass segment. It sits at z-index 0 behind the (position:relative,
   z-index 1) tab labels, so no markup/class changes were needed on the
   buttons themselves: app.js's existing `.tab` / `.active` / `aria-selected`
   contract is untouched. Concentric geometry: the track uses --r-capsule; the
   chip insets by the track's own padding (--sp-1) so its radius is naturally
   --r-capsule minus that inset — Apple's "inner radius = outer radius minus
   inset" rule. */
.tabs {
  position: relative;
  display: flex;
  gap: 0;
  padding: var(--sp-1);
  border-radius: var(--r-capsule);
  background: var(--glass-track);
  box-shadow: inset 0 1px 0 var(--glass-edge-soft);
}
@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .tabs {
    backdrop-filter: saturate(var(--glass-saturate)) blur(calc(var(--glass-blur) * 0.6));
    -webkit-backdrop-filter: saturate(var(--glass-saturate)) blur(calc(var(--glass-blur) * 0.6));
  }
}
.tab-chip {
  position: absolute;
  top: var(--sp-1);
  bottom: var(--sp-1);
  left: 0;
  width: 0;
  border-radius: calc(var(--r-capsule) - var(--sp-1));
  background: var(--glass-chip);
  box-shadow: inset 0 1px 0 var(--glass-edge), var(--glass-shadow);
  transition: transform var(--dur-press) var(--ease-spring),
    width var(--dur-press) var(--ease-spring);
  z-index: 0;
  pointer-events: none;
}
/* Suppress the width-0 -> real-width slide on first paint: the track carries
   `.boot` until app.js has positioned the chip under the initial active tab,
   then drops it on the next frame so every later activation still animates. */
.tabs.boot .tab-chip { transition: none; }
.tab {
  position: relative;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-4);
  border: none;
  background: transparent;
  color: var(--text-muted);
  font-size: var(--fs-chrome-md);
  font-weight: var(--fw-regular);
  cursor: pointer;
  transition: color var(--dur-fast) var(--ease-standard);
}
.tab.active {
  /* The raised chip behind it already reads as "selected" (Apple's
     segmented-control language); the label stays primary ink, not accent —
     see tokens.css GLASS verification for why (accent-on-chip fails AA on
     dark). A small dot is the second, redundant selected-state cue. */
  color: var(--text);
  font-weight: var(--fw-semibold);
}
.tab.active::before {
  content: "";
  display: inline-block;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--accent);
  margin-right: var(--sp-1);
}

/* ---- Card ----------------------------------------------------------------
   Generic reusable surface: hairline border, no shadow (DD §2 — chrome
   never carries a box-shadow). .translate-card, #panel, and .modal-card
   apply the same recipe directly (page/feature-specific containers, kept
   in their own sections below/in app.css rather than restructured onto
   this class, since markup cannot change in B2). ------------------------ */
.card {
  background: var(--card);
  border: 1px solid var(--hairline);
  border-radius: var(--r-lg);
  padding: var(--sp-5);
}

/* Panel drawer (htmx-swapped glossary/guidelines/docs fragments) adopts the
   card recipe directly. The min-height keeps the card from collapsing during a
   slow in-flight fragment load; while the Translate view is active the panel's
   [hidden] attribute keeps it display:none regardless of this height. */
#panel {
  background: var(--card);
  border: 1px solid var(--hairline);
  border-radius: var(--r-lg);
  padding: var(--sp-5);
  min-height: 8rem;
}
/* Panel section titles get real display presence — --fs-heading (19px) and
   tighter tracking rather than sitting at the same 14px/-0.01em as a button
   label. Still --font-sans/600 (DESIGN §1/§3: the display face is reserved
   for the wordmark alone); the character here comes from size + tracking,
   not a second family. DD-006 r5 "type scale richness". */
.panel-title {
  color: var(--text);
  margin: 0 0 var(--sp-3);
  font-size: var(--fs-heading);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-heading);
}
.panel form { margin-top: var(--sp-4); }
.panel .field { margin-bottom: var(--sp-3); }
/* Field labels only (a direct child <label> of a .field) — additive scoping
   rather than a :not() exclusion, so the file-input trigger label (a
   .btn.btn-secondary nested inside .file-input) never inherits this block and
   collapses into a stacked field label. */
.panel .field > label {
  color: var(--text-muted);
  display: block;
  margin-bottom: var(--sp-1);
  font-size: var(--fs-chrome-sm);
}
/* Panel form buttons (Add/Save/Delete) carry class="btn btn-secondary" in the
   fragment markup — box recipe lives on .btn-secondary above. These stay as
   the panel's semantic text-color helpers, which also color non-button
   elements (<p class="danger"> errors, <span class="muted"> hints). */
.panel .danger { color: var(--error); }
.panel .ok { color: var(--success); }
.panel .muted { color: var(--text-subtle); }

/* ---- Field divider ("paste OR upload") -----------------------------------
   A quiet hairline-flanked "or" between two alternative ways to fill the
   same field group (e.g. paste text vs. upload a file) — the standard
   auth-form divider pattern, kept to chrome-xs/label tracking so it reads as
   a rule, not a third field. */
.field-divider {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  margin: var(--sp-4) 0;
  color: var(--text-subtle);
  font-size: var(--fs-chrome-xs);
  text-transform: uppercase;
  letter-spacing: var(--ls-label);
}
.field-divider::before,
.field-divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--hairline);
}

/* ---- File input (standard component, r7) ----------------------------------
   The native <input type=file> stays in the DOM (.visually-hidden — NOT
   display:none, so it is still focusable/keyboard-operable) and its paired
   <label for> stands in as the visible trigger, styled as the existing quiet
   .btn-ghost so this doesn't introduce a fourth button language. A muted
   filename readout sits alongside; app.js updates it via a document-level
   delegated `change` listener (htmx fragments re-render wholesale, so a
   listener bound at parse time would not survive a swap). ---------------- */
.file-input {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  flex-wrap: wrap;
}
/* The trigger is a <label for> carrying class="btn btn-secondary" (the shared
   card-fill + hairline recipe), so it pairs with the Delete button instead of
   adding a new look — no file-input-scoped override needed. */
.file-input-name {
  color: var(--text-subtle);
  font-size: var(--fs-chrome-sm);
}

/* ---- Table ---------------------------------------------------------------
   Generic .table recipe (for future direct use, e.g. B4's fragment rewrite)
   plus the .panel-scoped table currently rendered by glossary/docs. ------ */
.table,
.panel table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--fs-chrome-md);
}
.table th, .table td,
.panel th, .panel td {
  text-align: left;
  padding: var(--sp-3);
  border-bottom: 1px solid var(--hairline);
  /* Middle-align so a row with a Delete button in the last cell reads as one
     coherent row, not the button and short text cells (ID/Lang) sitting at
     different visual heights. */
  vertical-align: middle;
}
/* An optional column (e.g. docs' Lang, glossary's Note) that renders no
   text produces a genuinely empty <td> — show a quiet placeholder instead
   of a blank gap in the row. :empty only matches when there are truly no
   child nodes, which the Go templates guarantee (no stray whitespace inside
   the tag), so this never falsely fires on a cell with real content. */
.table td:empty::before,
.panel td:empty::before {
  content: "\2014";
  color: var(--text-subtle);
}
/* Narrow, content-width ID column (docs table) — the `width:1%` +
   `white-space:nowrap` pairing is the standard shrink-to-fit trick so a
   short numeric ID doesn't share the row's flexible width with Title. */
.table .col-id,
.panel .col-id {
  width: 1%;
  white-space: nowrap;
}
.table th,
.panel th {
  color: var(--text-subtle);
  font-weight: var(--fw-regular);
  font-size: var(--fs-chrome-xs);
  text-transform: uppercase;
  letter-spacing: var(--ls-label);
}

/* ---- Modal (settings/BYOK dialog) ---------------------------------------
   Overlay + dialog. No box-shadow on the card — hairline border only,
   same recipe as .card, with a max-height guard for short viewports. ---- */
.modal-overlay {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--sp-5);
  /* Theme-invariant dim scrim — always dark regardless of theme, so no
     tokens.css color token applies here (analogous to --frost). */
  background: rgba(0, 0, 0, .45);
}
.modal-card {
  width: 100%;
  max-width: 460px;
  max-height: calc(100vh - var(--sp-7));
  overflow-y: auto;
  /* Deliberately OPAQUE (DESIGN §6): a dense form over a dark scrim would
     drop the muted disclaimer below AA if translucent. It earns its glass
     read from the specular top edge + floating shadow + rim, not see-through. */
  background: var(--card);
  border: 1px solid var(--glass-rim);
  border-radius: var(--r-lg);
  box-shadow: var(--glass-shadow), inset 0 1px 0 var(--glass-edge);
  padding: var(--sp-5);
}
/* Themed scrollbar (ENH-007) — .modal-card here and .pane-body (app.css,
   the translate panes) share one recipe (transparent track,
   --hairline-strong thumb, --text-subtle on hover) so every internally-
   scrolling surface reads as one system instead of the browser's native
   chrome. Grouped into one selector list (simplify pass) rather than two
   copies across files — same precedent as the shared focus-ring block
   above (.btn:focus-visible, .lang-select:focus-visible, ...): a single
   visual recipe applied to selectors from both "pages" and "components"
   belongs with the recipe, not duplicated per owning file. Hover triggers
   on the scrolling element itself (not an ancestor) since that is the only
   form Firefox's scrollbar-color can react to. Declaring ::-webkit-scrollbar
   switches macOS off the transient overlay scrollbar to a permanently-
   reserved gutter once content overflows — an intentional trade for a
   consistent themed look. No scrollbar-gutter:stable: it would reserve that
   gutter PERMANENTLY (whenever overflow:auto/overflow-y:auto is set, per
   CSS Overflow L3), not just while actually overflowing — code review
   REV-code-v1-A M2 caught that this makes both .pane-body's content column
   and the modal's own --sp-5 padding always 8px narrower on the right, even
   when idle/never-scrolling. Accepting a one-time reflow when overflow
   first begins instead. Firefox/WebKit render the thumb slightly
   differently — a full-width thin thumb on Firefox vs. an ~4px-inset thumb
   on WebKit (the padding-box background-clip trick below), and no hover
   brightening on Firefox — accepted as a minor cross-engine cosmetic
   difference, not worth extra machinery to unify. */
.pane-body,
.modal-card {
  scrollbar-width: thin;
  scrollbar-color: var(--hairline-strong) transparent;
}
.pane-body::-webkit-scrollbar,
.modal-card::-webkit-scrollbar { width: 8px; }
.pane-body::-webkit-scrollbar-track,
.modal-card::-webkit-scrollbar-track { background: transparent; }
.pane-body::-webkit-scrollbar-thumb,
.modal-card::-webkit-scrollbar-thumb {
  background: var(--hairline-strong);
  border-radius: var(--r-sm);
  border: 2px solid transparent;
  background-clip: padding-box;
}
.pane-body:hover::-webkit-scrollbar-thumb,
.modal-card:hover::-webkit-scrollbar-thumb { background: var(--text-subtle); }
.modal-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  margin-bottom: var(--sp-3);
}
.modal-title {
  margin: 0;
  font-size: var(--fs-chrome-md);
  font-weight: var(--fw-semibold);
  letter-spacing: -0.01em;
  color: var(--text);
}
/* Positioning only — the visual recipe (border/bg/hover) comes from .tag,
   which this link shares (index.html: class="tag modal-guide"). */
.modal-guide { margin-bottom: var(--sp-3); }
.modal-group {
  margin-bottom: var(--sp-4);
  padding-top: var(--sp-3);
  border-top: 1px solid var(--hairline);
}
.modal-group-head {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin-bottom: var(--sp-2);
}
.modal-group-head h3 {
  margin: 0;
  font-size: var(--fs-chrome-xs);
  font-weight: var(--fw-semibold);
  text-transform: uppercase;
  letter-spacing: var(--ls-label);
  color: var(--text-subtle);
}
.modal-help {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  border: 1px solid var(--hairline);
  border-radius: 50%;
  font-size: var(--fs-chrome-xs);
  font-weight: var(--fw-semibold);
  line-height: 1;
  color: var(--text-muted);
  text-decoration: none;
}
.modal-help:hover {
  border-color: var(--accent);
  color: var(--accent);
}
.modal-field { margin-bottom: var(--sp-2); }
.modal-field label {
  display: block;
  margin-bottom: var(--sp-1);
  font-size: var(--fs-chrome-sm);
  color: var(--text-muted);
}
.modal-model-custom { margin-top: var(--sp-1); }
.modal-keydocs {
  display: block;
  margin-top: var(--sp-1);
  font-size: var(--fs-chrome-xs);
  color: var(--text-subtle);
  text-align: right;
  text-decoration: none;
}
.modal-keydocs:hover { color: var(--accent); text-decoration: underline; }
/* Inline external-link glyph (Lucide) replacing the old ↗ entity — sits in
   the text flow rather than dropping to its own line like svg.icon's
   default display:block would (app.css). */
.modal-keydocs svg.icon {
  display: inline-block;
  width: 12px;
  height: 12px;
  vertical-align: -1px;
  margin-left: 2px;
}
.modal-disclaimer {
  margin: var(--sp-3) 0 var(--sp-4);
  font-size: var(--fs-chrome-xs);
  line-height: 1.5;
  color: var(--text-subtle);
}
.modal-actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--sp-2);
}
/* Settings-modal buttons carry class="btn btn-secondary modal-btn [primary|
   danger]" — the box/fill/hover recipe comes from .btn-secondary above; this
   only widens the side padding from the panel default (--sp-3) to --sp-4. The
   Save button's specular CTA treatment comes from the .modal-btn.primary
   selector in the Liquid-glass CTA block near the top of this file. */
.modal-btn { padding: var(--sp-2) var(--sp-4); }

/* ---- Toast ---------------------------------------------------------------
   Fixed, top-center, above the modal overlay (z-index 100) so it stays
   readable even while the settings modal is open. Hairline border, no
   shadow. -------------------------------------------------------------- */
#toast {
  position: fixed;
  top: var(--sp-5);
  left: 50%;
  z-index: 200;
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  max-width: calc(100vw - var(--sp-6));
  padding: var(--sp-2) var(--sp-4);
  /* Floating glass: translucent fill (below), rim for definition, a soft
     floating shadow (sanctioned exception for detached glass), and a top
     specular line. Fallback fill is opaque --card where blur is unsupported. */
  background: var(--card);
  border: 1px solid var(--glass-rim);
  border-radius: var(--r-md);
  box-shadow: var(--glass-shadow), inset 0 1px 0 var(--glass-edge);
  color: var(--text);
  font-size: var(--fs-chrome-sm);
  transform: translateX(-50%);
  animation: toast-in .18s var(--ease-standard);
}
@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  #toast {
    background: var(--glass-bg);
    backdrop-filter: saturate(var(--glass-saturate)) blur(var(--glass-blur));
    -webkit-backdrop-filter: saturate(var(--glass-saturate)) blur(var(--glass-blur));
  }
}
.toast-msg {
  flex: 1 1 auto;
  min-width: 0;
  word-break: break-word;
}
.toast-action {
  flex: 0 0 auto;
  border: 1px solid var(--hairline);
  border-radius: var(--r-sm);
  background: var(--accent-soft);
  color: var(--accent);
  font-family: var(--font-sans);
  font-size: var(--fs-chrome-xs);
  font-weight: var(--fw-semibold);
  padding: var(--sp-1) var(--sp-2);
  white-space: nowrap;
  cursor: pointer;
}
.toast-action:hover {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}
@keyframes toast-in {
  from { opacity: 0; transform: translate(-50%, -8px); }
  to { opacity: 1; transform: translate(-50%, 0); }
}

/* ---- Status meta line ----------------------------------------------------
   Single quiet row of 12-13px muted text (pair/model/glossary-count/docs
   count). Applied to index.html under the translate card and in the footer
   (per DD §2 D6). */
.status-line {
  font-size: var(--fs-chrome-xs);
  color: var(--text-subtle);
  line-height: 1.7;
  word-break: break-word;
}
.status-line .seg { color: var(--text-muted); }
.status-line .sep { color: var(--text-subtle); margin: 0 2px; }

/* ---- Frosted nav bar recipe ------------------------------------------------
   Applied to the top navigation in index.html (class="app-nav nav-frost")
   and shown in the styleguide. ------------------------------------------ */
.nav-frost {
  /* Fallback (no backdrop-filter): a legible opaque-ish frost. */
  background: var(--frost);
  /* Specular top-light line + a faint inner rim at the base: the glass edge.
     Inset highlights only — the nav is edge-pinned, so no drop shadow (its
     hairline lives in .app-nav). */
  box-shadow: inset 0 1px 0 var(--glass-edge),
    inset 0 -1px 0 var(--glass-rim);
}
@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .nav-frost {
    background: var(--glass-bg);
    backdrop-filter: saturate(var(--glass-saturate)) blur(var(--glass-blur));
    -webkit-backdrop-filter: saturate(var(--glass-saturate)) blur(var(--glass-blur));
  }
}

/* ---- Tag ------------------------------------------------------------------
   Quiet inline badge for a secondary link/label that isn't a real action —
   e.g. the settings modal's "Configuration guide" pointer. Deliberately
   understated: hairline border and muted text at rest, an accent border (not
   a filled background) on hover so it never competes with the one real CTA
   per view. 12px/400 weight, --r-sm radius — never pill (that's reserved for
   primary CTAs). */
.tag {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  padding: var(--sp-1) var(--sp-2);
  border: 1px solid var(--hairline);
  border-radius: var(--r-sm);
  background: var(--surface);
  color: var(--text-muted);
  font-size: var(--fs-chrome-xs);
  font-weight: var(--fw-regular);
  text-decoration: none;
  cursor: pointer;
}
.tag:hover {
  border-color: var(--accent);
  color: var(--accent);
}
.tag svg.icon { width: 16px; height: 16px; }

/* ---- Visually hidden (screen-reader only) --------------------------------
   Clip-rect pattern: element stays in the accessibility tree (so a live
   region still announces) while taking no visual space. Used for #sr-result,
   which speaks the completed translation once when a run finishes. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ---- List row (repeated form rows in a panel) -----------------------------
   Used for per-item rows rendered in a loop inside a panel (e.g. one row
   per guideline in guidelines.html). The first row stays flush; a hairline
   divider appears above each subsequent row via the adjacent-sibling
   combinator, so callers only need to add one class per row. */
.list-row { margin-bottom: var(--sp-4); }
.list-row + .list-row {
  border-top: 1px solid var(--hairline);
  padding-top: var(--sp-3);
}

/* ---- Reduced motion ------------------------------------------------------
   Every animation this file introduces (the toast entrance, the springy
   press/hover transforms) has an instant alternative here. The streaming
   caret's own reduce rule lives with it in app.css. WCAG 2.3.3 / SC 2.2.2. */
@media (prefers-reduced-motion: reduce) {
  #toast { animation: none; }
  .btn,
  .tab,
  .tab-chip { transition: none; }
  .btn:hover,
  .btn:active { transform: none; }
}
