/* @file star_realms/styles.css
 * @project KAINYNE Website
 * @description STAR REALMS page styles — extracted from index.html.
 *   Cache-bust: bump ?v=N in index.html when changing this file.
 *   [REQ-460] Pure reorganization: the late-cascade modal / drag /
 *   grid / responsive / animation tail (original lines 1788–3073)
 *   was extracted into the sibling file `styles-modal-grid.css`,
 *   loaded AFTER this file from `index.html` so the cascade order is
 *   preserved byte-for-byte. This file keeps the early-cascade rules
 *   (tokens, page chrome, lobby, cards, chips, dialogs head, pick
 *   modes, prompts, action buttons, hand / trade / base columns).
 * @module UI/StarRealms/Styles
 * @requirements REQ-005, REQ-203, REQ-212, REQ-249, REQ-252, REQ-313, REQ-317, REQ-327, REQ-328, REQ-329, REQ-334, REQ-336, REQ-343, REQ-349, REQ-350, REQ-376, REQ-407, REQ-408, REQ-409, REQ-419, REQ-426, REQ-450, REQ-460
 */

  * { box-sizing: border-box; margin: 0; padding: 0; }
  /* [REQ-350] Strict no-scroll fit — every responsive value reads
     from one token block. Card token is 2D-aware: picks the smaller
     of width-budget-per-card vs height-budget-per-row-as-width so
     cards always fit BOTH axes. `--sr-chrome-h` reserves the height
     for non-card rows (top + opp-stats + you-stats + actions) and is
     retuned per viewport. The 4 card-rows (oppplay / trade / youplay
     / hand) split the remaining height evenly via grid fr-units. */
  :root {
    --sr-card-gap: clamp(2px, 0.5vw, 8px);
    --sr-pad-y: clamp(2px, 0.6vh, 12px);
    --sr-pad-x: clamp(4px, 1.2vw, 14px);
    --sr-section-py: clamp(1px, 0.3vh, 6px);
    --sr-section-gap: clamp(2px, 0.4vh, 6px);
    --sr-h1-size: clamp(0.9rem, 2.6vw, 3rem);
    --sr-h2-size: clamp(0.55rem, 1.6vw, 0.7rem);
    --sr-card-name-size: clamp(0.5rem, 1.3vw, 0.85rem);
    --sr-card-effect-size: clamp(0.45rem, 1.1vw, 0.68rem);
    /* [REQ-350] Reserved height for non-card rows; per-viewport
       overrides retune this. */
    --sr-chrome-h: 220px;
    /* [REQ-350] Card token derives from BOTH viewport dimensions and
       the row count so cards fit both axes. 8 trade-row tiles is the
       widest row; 4 card-rows is the tallest stack. Aspect ratio
       1:1.353 preserved (170/230 of REQ-343 reference cards). */
    --sr-card-w: min(
      calc((100vw - var(--sr-pad-x) * 2 - var(--sr-card-gap) * 7) / 8),
      calc((100dvh - var(--sr-chrome-h)) / 4 / 1.353)
    );
    --sr-card-h: calc(var(--sr-card-w) * 1.353);
  }
  html { min-height: 100dvh; background: #0f0f0f; }
  body {
    background: #0f0f0f;
    color: #f0f0f0;
    font-family: monospace;
    min-height: 100vh;
    min-height: 100dvh;
    padding: var(--sr-pad-y) var(--sr-pad-x);
    line-height: 1.45;
    -webkit-user-select: none;
    user-select: none;
  }
  input,
  textarea {
    -webkit-user-select: text;
    user-select: text;
  }
  /* [REQ-350] [PTR-048] Lock the body to 100dvh + no-scroll while the
     game area is visible, on every viewport class. The :has() selector
     keys on the runtime [hidden] attribute on #sr-game-area, so the
     lobby state (PTR-048's #sr-game-area[hidden] { display: none }
     contract) leaves body default-scrollable for the lobby form. The
     game area's own grid template + card tokens guarantee fit; this
     rule prevents any stray overflow from leaking onto body. */
  body:has(#sr-game-area:not([hidden])) {
    overflow: hidden;
    height: 100dvh;
    min-height: 0;
  }
  a.home-link {
    display: inline-block;
    margin-bottom: 1.5rem;
    color: #e8f020;
    text-decoration: none;
    border: 1px solid #e8f020;
    padding: 0.35rem 0.6rem;
  }
  a.home-link:hover { background: #e8f020; color: #0f0f0f; }
  /* [REQ-366] Phase 10 redesign — hide the legacy header strip
     (Home link + STAR REALMS h1 + turn banner). They stay in the
     DOM for SEO + a11y; the new top-right hamburger menu carries
     all the same affordances. */
  .sr-page-chrome { display: none; }
  /* [REQ-366] Top-right hamburger button. Fixed-position so it stays
     above the in-game grid even on narrow viewports; sits above the
     dialog backdrop and is hidden when the dialog is open via the
     [open]+button rule below. */
  #sr-menu-btn {
    position: fixed;
    top: 0.5rem;
    right: 0.5rem;
    z-index: 50;
    background: rgba(0, 0, 0, 0.6);
    color: #e8f020;
    border: 1px solid #e8f020;
    border-radius: 4px;
    padding: 0.25rem 0.55rem;
    font-size: 1.1rem;
    line-height: 1;
    cursor: pointer;
  }
  @media (hover: hover) {
    #sr-menu-btn:hover {
      background: #e8f020;
      color: #0f0f0f;
    }
  }
  #sr-menu-btn:focus-visible {
    background: #e8f020;
    color: #0f0f0f;
    outline: none;
  }
  /* [REQ-433] Center the hamburger menu in the viewport. UA default
     positions <dialog> at the top of the page; same inset:0 + margin:auto
     + height:fit-content pattern used for .sr-end-turn-confirm (PTR-076). */
  #sr-menu[open] {
    position: fixed;
    inset: 0;
    margin: auto;
    height: fit-content;
    border: 1px solid #e8f020;
    background: #14161b;
    color: #e8e9ef;
    padding: 0.75rem;
    border-radius: 6px;
    min-width: 18rem;
    max-width: 90vw;
    max-height: 80dvh;
    overflow-y: auto;
  }
  #sr-menu::backdrop { background: rgba(0, 0, 0, 0.5); }
  .sr-menu-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 0.5rem;
    border-bottom: 1px dashed #2a2d38;
    padding-bottom: 0.4rem;
  }
  /* [REQ-444] In the lobby, hide the turn-status indicator + drop the
     header divider so the menu only shows the close button + actions
     (Toggle fullscreen, Back to Games, Debug). The turn status is
     meaningless before a game starts. */
  body[data-sr-surface="lobby"] .sr-menu-head {
    border-bottom: none;
    padding-bottom: 0;
    justify-content: flex-end;
  }
  body[data-sr-surface="lobby"] #sr-menu-turn-status {
    display: none !important;
  }
  /* [REQ-444] Back-to-Games link is lobby-only — hide it during games
     so the in-game menu only shows game-session actions. */
  body[data-sr-surface="game"] #sr-menu-back-to-games {
    display: none !important;
  }
  .sr-menu-turn {
    font-weight: 700;
    color: #e8f020;
    letter-spacing: 0.05em;
  }
  #sr-menu-close {
    background: transparent;
    color: #e8e9ef;
    border: 1px solid #2a2d38;
    border-radius: 4px;
    width: 1.6rem;
    height: 1.6rem;
    line-height: 1;
    font-size: 1.1rem;
    cursor: pointer;
  }
  #sr-menu-close:hover, #sr-menu-close:focus {
    border-color: #e8f020;
    color: #e8f020;
    outline: none;
  }
  .sr-menu-actions {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    margin-bottom: 0.6rem;
  }
  /* [REQ-456] [PTR-106] Extended to also cover #sr-surrender and
     #sr-leave-game. Pre-fix those two buttons relied on the
     `.sr-back-btn` class for visual styling, but `.sr-back-btn`
     also subscribes to a global click handler that runs
     pre-confirm teardown — racing the buttons' own confirm-dialog
     flows. The class was removed; styling moved here so the
     visual contract survives intact. */
  #sr-menu-fullscreen,
  #sr-surrender,
  #sr-leave-game,
  .sr-menu-home {
    display: inline-block;
    background: transparent;
    color: #e8f020;
    border: 1px solid #e8f020;
    border-radius: 4px;
    padding: 0.3rem 0.6rem;
    font-size: 0.85rem;
    cursor: pointer;
    text-decoration: none;
    font-family: monospace;
  }
  /* [REQ-444] Use @media (hover: hover) so touch devices don't get
     stuck-yellow hover states after tap. Use :focus-visible so focus
     style only shows for keyboard navigation, not after touch tap. */
  @media (hover: hover) {
    #sr-menu-fullscreen:hover, .sr-menu-home:hover,
    #sr-surrender:hover, #sr-leave-game:hover {
      background: #e8f020;
      color: #0f0f0f;
      outline: none;
    }
  }
  #sr-menu-fullscreen:focus-visible, .sr-menu-home:focus-visible,
  #sr-surrender:focus-visible, #sr-leave-game:focus-visible {
    background: #e8f020;
    color: #0f0f0f;
    outline: none;
  }
  .sr-menu-log {
    margin-top: 0.5rem;
    border-top: 1px dashed #2a2d38;
    padding-top: 0.4rem;
  }
  .sr-menu-log > summary {
    cursor: pointer;
    font-weight: 600;
    padding: 0.2rem 0;
    color: #cfd6e4;
  }
  /* Debug Tools section in the hamburger menu — groups the
     toggleable inspector / debug buttons (Debug, Inspect HUD,
     Inspect Grid, Error Log) under a single heading so they don't
     clutter the main action row. Sits between the actions and the
     Game Log <details> at the bottom of the menu. */
  .sr-menu-debug-section {
    margin-top: 0.6rem;
    padding-top: 0.5rem;
    border-top: 1px dashed #2a2d38;
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
  }
  .sr-menu-section-heading {
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: #888;
    padding-bottom: 0.2rem;
  }
  /* Aria-pressed-aware styling for the four debug toggles. The
     button-press.css universal :active scale already paints the
     tap feedback; this rule just paints a persistent "ON" state
     while the toggle is engaged. */
  .sr-menu-debug-section button[aria-pressed="true"] {
    background: #40e840;
    color: #0f0f0f;
    border-color: #40e840;
  }
  /* [REQ-444] Ensure hidden attribute works correctly. The HTML hidden
     attribute should apply display: none !important by default, but
     explicitly enforce it here to override any CSS rules that might
     set display properties. */
  [hidden] {
    display: none !important;
  }
  h1 {
    /* [REQ-343] Title shrinks to a chip on phones; full size on desktop. */
    font-size: var(--sr-h1-size);
    color: #e8f020;
    letter-spacing: 0.08em;
    margin-bottom: 0.15rem;
  }
  .sr-banner {
    border: 1px solid #e8f020;
    color: #e8f020;
    padding: 0.75rem 1rem;
    max-width: 720px;
    margin: 1rem 0 1.5rem;
    font-size: 0.85rem;
  }
  section.sr-section {
    /* [REQ-343] 960px cap removed — the #sr-game-area grid now owns
       layout width, so individual sections must not clamp themselves
       narrower than the grid cell they sit in. */
    border-top: 1px dashed #2a2d38;
    padding: var(--sr-section-py) 0;
    min-width: 0;
  }
  /* [REQ-412] [PTR-068] Opp sections paint the dashed separator on the
     BOTTOM edge so it hugs the trade row (mirror of player border-top).
     The opp authority panel itself is excluded — it owns the gold frame
     defined further down so the dashed-grey edge would clash. */
  .sr-zone[data-sr-area="opphand"] section.sr-section,
  .sr-zone[data-sr-area="oppplay"] section.sr-section,
  .sr-zone[data-sr-area="oppbases"] section.sr-section {
    border-top: 0;
    border-bottom: 1px dashed #2a2d38;
  }
  section.sr-section h2 {
    /* [REQ-343] Section labels shrink with the viewport. */
    font-size: var(--sr-h2-size);
    letter-spacing: 0.18em;
    color: #888;
    margin-bottom: 0.25rem;
  }
  /* [REQ-350] Card rows are flex containers without min-height; the
     #sr-game-area grid owns row height via fr-units. nowrap keeps
     each row on a single line so the grid row height is enough — if
     the row's contents would wrap, the page would scroll. The
     2D-aware --sr-card-w token guarantees content fits. */
  /* [REQ-368] Phase 10 redesign Chunk 10 — hand overflow scroller.
     When the hand exceeds the visible width (>6 cards), boot.js
     reveals the chevron buttons (otherwise they stay hidden via
     the [hidden] attribute). The wrapper is position:relative so
     the chevrons absolutely position over the row edges; the inner
     #sr-hand row scrolls horizontally with scroll-snap so the
     chevron-clicked nudge lands cleanly on a card boundary. */
  .sr-row-scroller {
    position: relative;
    overflow-x: auto;
    overflow-y: hidden;
    scroll-snap-type: x mandatory;
    scrollbar-width: thin;
    -webkit-overflow-scrolling: touch;
    width: 100%;
  }
  .sr-row-scroller > .sr-row {
    /* The inner row keeps its existing flex layout; scroll-snap
       targets the row's children so each card is a snap point. */
    scroll-snap-type: x mandatory;
  }
  .sr-row-scroller > .sr-row > * {
    scroll-snap-align: start;
    flex-shrink: 0;
  }
  .sr-row-chevron {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    z-index: 4;
    width: 1.6rem;
    height: 2.2rem;
    background: rgba(0, 0, 0, 0.7);
    color: #e8f020;
    border: 1px solid #e8f020;
    border-radius: 4px;
    font-size: 1.2rem;
    line-height: 1;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0;
  }
  .sr-row-chevron:hover, .sr-row-chevron:focus {
    background: #e8f020;
    color: #0f0f0f;
    outline: none;
  }
  .sr-row-chevron-prev { left: 0; }
  .sr-row-chevron-next { right: 0; }
  .sr-row-chevron[hidden] { display: none; }
  .sr-row {
    display: flex;
    flex-wrap: nowrap;
    gap: var(--sr-card-gap);
    min-width: 0;
    min-height: 0;
    align-items: flex-start;
  }
  .sr-authority {
    padding: 0.35rem 0.8rem;
    border: 1px solid #2a2d38;
    border-radius: 2px;
    margin-right: 0.5rem;
    color: #f0f0f0;
    font-size: 0.9rem;
  }
  .sr-authority-p1 { border-color: #40b4e8; color: #40b4e8; }
  .sr-authority-p2 { border-color: #e84040; color: #e84040; }
  /* [REQ-339] Player-name label chip rendered next to each authority
     chip so both sides know who is Player 1 vs Player 2. Mirrors the
     authority chip's per-seat palette so the row reads as a single
     coloured unit. */
  .sr-player-label {
    padding: 0.35rem 0.8rem;
    border: 1px solid #2a2d38;
    border-radius: 2px;
    margin-right: 0.5rem;
    font-size: 0.85rem;
    font-weight: 600;
  }
  .sr-player-label-p1 { border-color: #40b4e8; color: #40b4e8; }
  .sr-player-label-p2 { border-color: #e84040; color: #e84040; }
  .sr-pool {
    display: inline-block;
    padding: 0.35rem 0.8rem;
    border: 1px solid #2a2d38;
    border-radius: 2px;
    font-size: 0.9rem;
  }
  .sr-pool-trade { color: #e8f020; border-color: #e8f020; }
  .sr-pool-combat { color: #ff8040; border-color: #ff8040; }
  /* [REQ-363] Phase 10 redesign — three-chip resource panel (Gold /
     Damage / Health) painted next to the legacy authority + pools
     line. Each chip is a small block carrying a label + value
     stacked vertically; the per-colour modifiers paint borders,
     text, and a subtle glow so the three resources read as
     distinct surfaces at a glance. */
  /* [REQ-370] Phase 10 redesign follow-up — drop the legacy
     player-side authority + pools text readout now that the resource
     chips (Gold/Damage/Health, REQ-363) display the same info in a
     faster-to-scan colored panel. The mounts stay in the DOM so the
     existing renderYourAuthority + renderPools renderers keep
     painting into them (avoiding a render-time NPE on null nodes);
     only the visual layer is suppressed. The opponent-side mounts
     (#sr-opp-authority + #sr-opp-pools) stay visible since no
     opponent chips ship yet. */
  /* [REQ-371] Phase 10 redesign bugfix — symmetric hide for the
     opponent's legacy text readout now that REQ-371 ships opponent
     resource chips alongside the player's. */
  #sr-your-authority,
  #sr-your-pools,
  #sr-opp-authority,
  #sr-opp-pools { display: none; }
  .sr-resource-chips {
    display: flex;
    gap: 0.4rem;
    margin-top: 0.4rem;
    flex-wrap: wrap;
  }
  .sr-chip {
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 0.3rem 0.6rem;
    border: 2px solid;
    border-radius: 4px;
    min-width: 3rem;
    line-height: 1.1;
    background: rgba(0, 0, 0, 0.35);
    font-weight: 600;
  }
  .sr-chip-label {
    font-size: 0.6rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    opacity: 0.85;
  }
  .sr-chip-value {
    font-size: 1.1rem;
    font-weight: 700;
  }
  .sr-chip-gold {
    border-color: #e8d040;
    color: #e8d040;
    box-shadow: 0 0 6px rgba(232, 208, 64, 0.25);
  }
  .sr-chip-damage {
    border-color: #e84040;
    color: #e84040;
    box-shadow: 0 0 6px rgba(232, 64, 64, 0.25);
  }
  .sr-chip-health {
    border-color: #40b4e8;
    color: #40b4e8;
    box-shadow: 0 0 6px rgba(64, 180, 232, 0.25);
  }
  /* [REQ-336] Authority + Trade/Combat chips share a single row inside
     each `OPPONENT · AUTHORITY` / `YOUR · AUTHORITY` section so each
     player's full resource state reads at a glance. flex-wrap keeps
     the chips legible on narrow viewports. */
  .sr-authority-line {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    align-items: center;
  }
  /* [REQ-328] Visible pile counts (deck / discard / hand). One row
     per player; the OPP row uses the existing P2-red palette + the
     YOU row uses the P1-blue so the row identity matches the
     authority chips. Chips sit in a flex-wrap row so a narrow
     viewport stacks them cleanly. */
  .sr-pile-row {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    align-items: center;
    margin: 0.25rem 0;
    font-size: 0.85rem;
  }
  .sr-pile-row-opp { color: #e84040; }
  .sr-pile-row-you { color: #40b4e8; }
  .sr-pile-label {
    font-size: 0.7rem;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    min-width: 5.5rem;
    color: currentColor;
    opacity: 0.85;
  }
  .sr-pile-chip {
    display: inline-block;
    padding: 0.2rem 0.55rem;
    border: 1px solid currentColor;
    border-radius: 2px;
    font-size: 0.78rem;
    color: currentColor;
    background: rgba(0, 0, 0, 0.25);
  }
  /* [REQ-329] Discard chip is a clickable trigger that opens the
     pile-viewer modal. Hover/focus boost matches REQ-327's
     `.sr-card-info-btn` so the affordance reads as interactive. */
  .sr-pile-chip-clickable {
    cursor: pointer;
    transition: background 0.12s, box-shadow 0.12s;
  }
  .sr-pile-chip-clickable:hover,
  .sr-pile-chip-clickable:focus {
    background: rgba(255, 255, 255, 0.08);
    box-shadow: 0 0 8px currentColor;
    outline: 1px dotted currentColor;
    outline-offset: 1px;
  }
  /* [REQ-329] Discard-pile viewer modal. Native <dialog> element;
     wider than `.sr-card-detail` so a longer pile lays out two
     columns of items on desktop. ::backdrop dims the page behind. */
  .sr-pile-viewer {
    background: #0a0716;
    color: #f0f0f0;
    border: 2px solid #2a2d38;
    border-radius: 6px;
    padding: 1.25rem 1.5rem;
    max-width: 540px;
    width: 92%;
    max-height: 80vh;
    overflow-y: auto;
    font-family: monospace;
    box-shadow: 0 0 32px rgba(0, 0, 0, 0.6);
  }
  .sr-pile-viewer::backdrop {
    background: rgba(0, 0, 0, 0.65);
  }
  .sr-pile-viewer-close {
    position: absolute;
    top: 0.5rem;
    right: 0.6rem;
    background: transparent;
    border: none;
    color: #888;
    font-size: 1.4rem;
    line-height: 1;
    cursor: pointer;
    padding: 0.2rem 0.5rem;
  }
  .sr-pile-viewer-close:hover { color: #f0f0f0; }
  .sr-pile-viewer-title {
    font-size: 1.05rem;
    margin-bottom: 0.85rem;
    letter-spacing: 0.06em;
    color: #e8f020;
  }
  .sr-pile-viewer-empty {
    color: #888;
    font-style: italic;
    font-size: 0.85rem;
    padding: 0.5rem 0;
  }
  .sr-pile-viewer-list {
    display: flex;
    flex-direction: column;
    gap: 0.45rem;
  }
  .sr-pile-viewer-item {
    border-left: 3px solid currentColor;
    padding: 0.45rem 0.7rem;
    background: rgba(255, 255, 255, 0.03);
    border-radius: 2px;
    cursor: pointer;
    transition: background 0.12s;
  }
  .sr-pile-viewer-item:hover,
  .sr-pile-viewer-item:focus {
    background: rgba(255, 255, 255, 0.07);
    outline: 1px dotted currentColor;
    outline-offset: 1px;
  }
  .sr-pile-viewer-head {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    align-items: baseline;
    margin-bottom: 0.2rem;
  }
  .sr-pile-viewer-name {
    font-weight: bold;
    font-size: 0.9rem;
  }
  .sr-pile-viewer-meta {
    color: #b0b0b0;
    font-size: 0.7rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
  }
  .sr-pile-viewer-effects {
    color: #d0d0d0;
    font-size: 0.78rem;
    line-height: 1.35;
  }
  .sr-pile-viewer-effect {
    display: flex;
    align-items: baseline;
    gap: 0.3rem;
    margin: 0.1rem 0;
  }
  @media (max-width: 600px) {
    .sr-pile-viewer {
      max-width: none;
      width: 95%;
      padding: 1rem 1.1rem;
    }
  }
  .sr-hand-card, .sr-trade-slot {
    background: #17171d;
    border: 1px solid #2a2d38;
    color: #f0f0f0;
    font-family: monospace;
    padding: 0.4rem 0.7rem;
    cursor: pointer;
    font-size: 0.8rem;
    border-radius: 2px;
  }
  .sr-hand-card:not([disabled]):hover,
  .sr-trade-slot:not([disabled]):hover {
    border-color: #e8f020;
    color: #e8f020;
    opacity: 1;
  }
  .sr-hand-card[disabled], .sr-trade-slot[disabled] {
    cursor: not-allowed;
    opacity: 0.5;
  }
  .sr-trade-explorer { color: #b0b0b0; }
  .sr-inplay-card, .sr-base {
    background: #17171d;
    border: 1px solid #2a2d38;
    padding: 0.35rem 0.7rem;
    border-radius: 2px;
    font-size: 0.8rem;
    cursor: pointer;
  }
  .sr-inplay-card:hover { border-color: #ff8040; }
  .sr-outpost { border-color: #e84040; }
  /* [REQ-362] Phase 10 redesign — full-card faction tint. The
     mapping (SE yellow, TF blue, Blob green, MC red, neutral slate)
     aligns with sr-visuals.js and the retail Star Realms artwork,
     and supersedes the prior border-only accent. The gradient tilts
     from the faction colour at the top-left to a darker derivative
     bottom-right so the corner glyphs (REQ-358) stay legible against
     the lighter region. The 3px border-left remains as a secondary
     edge cue for users who keep cards close together; both the
     border colour and the background are sourced from the same
     mapping so the two never disagree.
     [PR-#520] Blob and Machine Cult border-left + gradient values
     are swapped relative to the original Phase 10 ship so this
     stylesheet agrees with sr-visuals.js (which has always painted
     Blob green and MC red on the inline art panels) and with the
     retail box. Pre-fix the two layers fought: CSS painted Blob
     red while the inline art painted Blob green. */
  .sr-faction-star_empire {
    border-left: 3px solid #e8d040;
    background: linear-gradient(135deg, #4a3f0c 0%, #1a160a 100%);
  }
  .sr-faction-trade_federation {
    border-left: 3px solid #40b4e8;
    background: linear-gradient(135deg, #0c2e4a 0%, #0a1018 100%);
  }
  .sr-faction-blob {
    border-left: 3px solid #40e8a0;
    background: linear-gradient(135deg, #0c4a2e 0%, #0a1812 100%);
  }
  .sr-faction-machine_cult {
    border-left: 3px solid #e84040;
    background: linear-gradient(135deg, #4a0c0c 0%, #1a0a0a 100%);
  }
  .sr-faction-neutral {
    border-left: 3px solid #566275;
    background: linear-gradient(135deg, #1c1f28 0%, #0c0d12 100%);
  }
  /* [REQ-362] Phase 10 redesign — 4-corner card face. REQ-358 emitted
     `.sr-card-faction` / `.sr-card-cost` / `.sr-card-name` /
     `.sr-card-type` slots on every tile; this rule absolutely
     positions them inside the card frame. Parent tiles already
     declare `position: relative` (existing rule), so the corners
     anchor to the tile's own bounding box. */
  /* [PTR-052] On narrow cards (e.g. portrait phones with the small
     `--sr-card-w` token) the faction acronym + cost stamps were
     bleeding into each other — the user reported "no regular spacing
     between those two stamps. usually they are right next to each
     other and on some cards there is one space". Without a per-corner
     `max-width`, long names (e.g. `[NEU]`) plus a 2-digit cost (e.g.
     `(10)`) absolutely-positioned at left:0.4rem / right:0.4rem
     can overlap horizontally when the card is narrower than their
     combined intrinsic width. Cap each at 45% of the card width and
     pin `white-space: nowrap` so they can't wrap into a 2-line stack
     either; the residual gap of >=10% of card width keeps them
     visually anchored to opposite corners on every viewport. */
  .sr-card-faction {
    position: absolute;
    top: 0.25rem;
    left: 0.4rem;
    max-width: 45%;
    white-space: nowrap;
    overflow: hidden;
    font-size: var(--sr-card-name-size);
    font-weight: 700;
    letter-spacing: 0.03em;
    pointer-events: none;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.7);
  }
  .sr-card-cost {
    position: absolute;
    top: 0.25rem;
    right: 0.4rem;
    max-width: 45%;
    white-space: nowrap;
    overflow: hidden;
    text-align: right;
    font-size: var(--sr-card-name-size);
    font-weight: 700;
    pointer-events: none;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.7);
  }
  .sr-card-name {
    position: absolute;
    top: 50%;
    left: 0.3rem;
    right: 0.3rem;
    transform: translateY(-50%);
    font-size: var(--sr-card-name-size);
    font-weight: 700;
    text-align: center;
    line-height: 1.15;
    pointer-events: none;
    /* [REQ-444] Multi-directional outline (4-way + soft halo) so the
       light faction-tinted text reads against the dark gradient art
       at every viewport size. Single drop-shadow was insufficient on
       small mobile cards where the gradient is busy. */
    text-shadow:
      -1px -1px 0 rgba(0, 0, 0, 0.95),
       1px -1px 0 rgba(0, 0, 0, 0.95),
      -1px  1px 0 rgba(0, 0, 0, 0.95),
       1px  1px 0 rgba(0, 0, 0, 0.95),
       0    0   4px rgba(0, 0, 0, 0.9);
    overflow: hidden;
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    /* [PTR-092] Prevent mid-character word breaks — words wrap only
       at whitespace so names like 'Mothership' are never split.
       overflow-wrap: break-word allows a single long word to break
       if it truly exceeds the card width as a last resort. */
    word-break: keep-all;
    overflow-wrap: break-word;
  }
  .sr-card-type {
    position: absolute;
    bottom: 0.25rem;
    left: 50%;
    transform: translateX(-50%);
    font-size: var(--sr-card-name-size);
    font-weight: 700;
    pointer-events: none;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.7);
    opacity: 0.85;
  }
  /* [REQ-362] Retire the legacy text rows REQ-358 left in place for
     back-compat. The 4-corner face now carries the same info — head
     line / meta line / effect bullets are hidden so the corners own
     the visual space. The DOM emit stays in `buildCardBody` so the
     `el.title` (full card text on hover) keeps working. */
  /* [REQ-374] Phase 10 follow-up — retire in-tile effect bullets so
     the 4-corner face owns the tile's visual space at every viewport.
     Inspect overlays (card-detail modal + pile-viewer) keep their own
     wrappers (.sr-card-detail-effect, .sr-pile-viewer-effect) and
     remain visible. */
  .sr-effects,
  .sr-effect-line,
  .sr-card-head,
  .sr-card-meta,
  .sr-card-effect {
    /* [REQ-371] Bumped to !important so later orphan rules with the
       same selector + specificity can't silently re-enable display
       (REQ-362 originally shipped without !important; the orphans
       at the bottom of the file overrode it via cascade order). */
    display: none !important;
  }
  .sr-hand-empty, .sr-inplay-empty, .sr-bases-empty {
    color: #666;
    font-style: italic;
    font-size: 0.8rem;
  }
  .sr-ally-fired { outline: 1px dotted #e8f020; }
  /* [REQ-245] Phase 2c — activated-base button. Inline child of .sr-base. */
  /* [REQ-385] Fired-ability spent indicator text */
  .sr-base-activate-tag {
    display: block;
    font-size: 0.5rem;
    color: #555;
    text-align: center;
    letter-spacing: 0.08em;
    font-family: monospace;
    margin-top: 3px;
    border-top: 1px solid #333;
    padding-top: 3px;
  }

  .sr-base-activate {
    background: transparent;
    border: 1px solid #e8f020;
    color: #e8f020;
    font-family: monospace;
    font-size: 0.7rem;
    padding: 0.1rem 0.4rem;
    margin-left: 0.4rem;
    border-radius: 2px;
    cursor: pointer;
  }
  .sr-base-activate:hover:not([disabled]) { background: #e8f020; color: #0f0f0f; }
  .sr-base-activate-fired,
  .sr-base-activate[disabled] {
    border-color: #555;
    color: #555;
    cursor: not-allowed;
  }
  /* [REQ-317] [REQ-395] Pending-choice prompt. Pre-fix this was an
     inline panel (`position: static`) sitting in document flow — the
     comment cited "not a true overlay to avoid CSP / focus-trap
     complexity". REQ-395 promotes it to a true fixed overlay so
     opening / closing the picker no longer reflows #sr-game-area:
     the play surface stays put + the picker floats above the bottom
     HUD chip bar. Anchored bottom-center (`left: 50%` +
     `translateX(-50%)`); `bottom: 64px + safe-area-inset-bottom`
     clears the YOU HUD (TRADE/COMBAT/AUTHORITY chips + PLAY ALL)
     AND the iOS home-indicator. The hand row (data-sr-area="hand",
     grid row 7) sits ABOVE the HUD so it stays uncovered. z-index:
     50 lifts it above #sr-game-area zones (default z=0); native
     <dialog> modals (win modal, pile-viewer, card-detail) sit in
     the top-layer above any z-index per the HTML spec, so this
     panel never covers them. 0.92 alpha background + box-shadow
     separate the panel visually from the play surface without
     blocking the hand row from view. */
  /* [PTR-051] The previous bottom-anchored prompt covered hand cards;
     moved to top-of-viewport fix. [REQ-450] Now centered vertically so
     the prompt appears mid-screen without obscuring the hand row below. */
  .sr-choice-prompt {
    position: fixed;
    left: 50%;
    top: 50%;
    bottom: auto;
    transform: translate(-50%, -50%);
    width: min(94vw, 520px);
    max-height: 40vh;
    overflow-y: auto;
    z-index: 50;
    margin: 0;
    padding: 0.5rem 0.85rem;
    border: 2px solid #e8f020;
    border-radius: 4px;
    background: rgba(10, 7, 22, 0.92);
    box-shadow: 0 4px 24px rgba(0, 0, 0, 0.55);
  }
  .sr-choice-prompt[hidden] { display: none; }
  .sr-choice-heading {
    font-size: 0.8rem;
    color: #e8f020;
    letter-spacing: 0.08em;
    /* [REQ-396] heading-to-options gap tightened 0.6rem → 0.35rem. */
    margin-bottom: 0.35rem;
    text-transform: uppercase;
  }
  .sr-choice-option {
    display: inline-block;
    /* [REQ-396] tighter button paddings + margins so the picker fits
       in 32vh without scrolling. Saves ~8px per button. */
    margin: 0.15rem 0.3rem 0.15rem 0;
    padding: 0.35rem 0.7rem;
    background: transparent;
    border: 1px solid #e8f020;
    color: #e8f020;
    font-family: monospace;
    font-size: 0.78rem;
    border-radius: 2px;
    cursor: pointer;
  }
  .sr-choice-option:hover { background: #e8f020; color: #0f0f0f; }
  /* [REQ-339] Multi-select chip + Confirm button styling for the
     Recycling Station discard picker. Toggle state surfaced via the
     `.sr-choice-multi-selected` class set by the boot click delegate
     so users see which cards are marked before confirming. */
  .sr-choice-multi {
    display: inline-block;
    margin: 0.25rem 0.4rem 0.25rem 0;
    padding: 0.5rem 0.9rem;
    background: transparent;
    border: 1px dashed #e8f020;
    color: #e8f020;
    font-family: monospace;
    font-size: 0.85rem;
    border-radius: 2px;
    cursor: pointer;
  }
  .sr-choice-multi:hover { background: rgba(232, 240, 32, 0.18); }
  .sr-choice-multi-selected {
    background: #e8f020;
    color: #0f0f0f;
    border-style: solid;
  }
  /* forced_discard UX overhaul — hide the chip-menu list inside the
     prompt. The chips stay in the DOM as the state machine for
     selection (preserves REQ-443 / PTR-100 save/restore) but the user
     interacts via the hand cards directly + reads selections from the
     .sr-choice-selected-names row below the counter. */
  body.sr-forced-discard-active .sr-choice-prompt .sr-choice-multi {
    display: none;
  }
  /* Live comma-separated names of cards the player has picked to
     discard. Sits between the counter and the (hidden) chip list. */
  .sr-choice-selected-names {
    margin: 0.2rem 0 0.5rem;
    padding: 0.25rem 0.5rem;
    font-size: 0.78rem;
    color: #ffd040;
    font-family: monospace;
    min-height: 1.4em;
    line-height: 1.4;
    background: rgba(232, 208, 64, 0.06);
    border-left: 2px solid rgba(232, 208, 64, 0.5);
    border-radius: 0 3px 3px 0;
  }
  /* Hand-card visual feedback when picked for forced_discard. The
     unmarked state already carries the dashed orange outline from
     `body.sr-forced-discard-active #sr-hand button[data-card-id]`
     (inline style block in star_realms/index.html). When selected,
     flip to a solid bright fill so the picked cards read at a glance. */
  body.sr-forced-discard-active #sr-hand button[data-card-id][data-sr-pick-selected="1"] {
    outline: 3px solid #ff8040;
    outline-offset: -3px;
    box-shadow: 0 0 12px rgba(255, 128, 64, 0.55);
  }
  .sr-choice-confirm {
    display: inline-block;
    margin: 0.5rem 0 0.25rem;
    padding: 0.5rem 1.1rem;
    background: #2a402a;
    border: 1px solid #5fc05f;
    color: #5fc05f;
    font-family: monospace;
    font-size: 0.9rem;
    border-radius: 2px;
    cursor: pointer;
  }
  .sr-choice-confirm:hover { background: #5fc05f; color: #0f0f0f; }
  /* [REQ-391] [PTR-061] Physical pick-mode visuals — the user taps a
     hand / trade-row tile to mark a scrap target, then taps Confirm.
     Two visual layers: a dashed outline on EVERY legal target while
     pick mode is active (so the user sees what's tappable), and a
     solid yellow outline on the SELECTED target (so the user sees
     which one they chose). Clicking a different target moves the
     yellow outline; the boot also keeps the .sr-pick-status chip in
     sync. PTR-061 fixed the hand selector — the original REQ-391 rule
     targeted #sr-your-hand which doesn't exist (the hand element id
     is #sr-hand — see index.html), so the dashed-orange + yellow
     outlines never painted on hand cards and the user had no visual
     feedback that hand-card taps were registering as picks. The
     #sr-trade-row branch was correct and is unchanged. */
  body.sr-pick-mode-scrap-hand-discard #sr-hand button[data-card-id],
  body.sr-pick-mode-trade-scrap #sr-trade-row button[data-slot-idx]:not([data-slot-idx="explorer"]),
  body.sr-pick-mode-discard-then-draw #sr-hand button[data-card-id],
  /* [REQ-419] [PTR-075] The youdiscard branch is gated on the
     parent `.sr-zone[data-sr-area="youdiscard"][data-sr-pick-target="1"]`
     ancestor so the dashed-orange pick-target outline stops
     painting whenever `applyPickModeBodyClasses` skips youdiscard
     from the live-zones list (i.e. when the discard is empty —
     no card to scrap means no legal target). */
  body.sr-pick-mode-scrap-hand-discard
    .sr-zone[data-sr-area="youdiscard"][data-sr-pick-target="1"]
    [data-pile-target="discard"][data-pile-owner="you"] {
    outline: 2px dashed #ff8040;
    outline-offset: -2px;
  }
  body.sr-pick-mode-scrap-hand-discard #sr-hand button[data-sr-pick-selected="1"],
  body.sr-pick-mode-trade-scrap #sr-trade-row button[data-sr-pick-selected="1"],
  body.sr-pick-mode-discard-then-draw #sr-hand button[data-sr-pick-selected="1"],
  body.sr-pick-mode-scrap-hand-discard
    .sr-zone[data-sr-area="youdiscard"][data-sr-pick-target="1"]
    [data-pile-target="discard"][data-pile-owner="you"][data-sr-pick-selected="1"] {
    outline: 3px solid #e8f020;
    outline-offset: -3px;
  }
  /* [REQ-407] [PTR-063] Trade-row scrap-pick visual polish — eligible
     non-Explorer slots get a small brightness/saturation boost so they
     read as ACTIVE against the global gray-out applied to the rest of
     the board. The Explorer slot is not in pc.targets (engine excludes
     it from scrap targets), so it inherits the dim from the global
     non-target rule below. */
  body.sr-pick-mode-trade-scrap #sr-trade-row button[data-slot-idx]:not([data-slot-idx="explorer"]) {
    filter: brightness(1.1) saturate(1.15);
  }
  /* [REQ-391] [PTR-094] Restore brightness for hand cards and the
     discard tile during scrap_hand_or_discard pick mode. The
     sr-not-my-turn body class dims these elements via filter while
     any pendingChoice is set; mirroring the PTR-063 trade-scrap fix,
     these rules override that dim so live pick targets read as active.
     The discard tile rule is gated on data-sr-pick-target="1" so it
     stays dim when the discard zone is empty (no legal scrap target). */
  body.sr-pick-mode-scrap-hand-discard #sr-hand button[data-card-id] {
    filter: brightness(1.1) saturate(1.15);
  }
  body.sr-pick-mode-scrap-hand-discard .sr-zone[data-sr-area="youdiscard"][data-sr-pick-target="1"] .sr-discard-pile-tile-you {
    filter: brightness(1.1) saturate(1.15);
  }
  /* [REQ-409] [PTR-065] Cross-prompt targeting consistency. While any
     pendingChoice is open, every .sr-zone NOT marked as the active
     target zone (via data-sr-pick-target="1" set by
     applyPickModeBodyClasses in boot.js) is dimmed and made
     non-interactive. The zone(s) flagged as the legal target retain
     their normal colour + pointer-events so the player knows where
     to tap. The choice prompt itself sits outside .sr-zone and is
     unaffected. */
  /* Non-target zones during a pick are click-guarded (so a stray tap
     can't trigger an unrelated card) but kept FULLY VISIBLE — the user
     wants game state readable while picking discards. Pre-fix this rule
     also painted `filter: grayscale(0.6) brightness(0.65)` which dimmed
     the board into an unreadable opaque-feeling overlay. */
  body.sr-pick-mode-active #sr-game-area .sr-zone:not([data-sr-pick-target="1"]) {
    pointer-events: none;
  }
  /* [REQ-419] [PTR-075] Opponent-turn gray-out for non-button tiles.
     `setClickable(interactive)` (boot.js render()) only flips the
     `disabled` attribute on real `<button>` elements, so the scrap
     pile (rendered as `<div role="button">` by `_buildPileTile`)
     and the explorer slot (whose `.sr-trade-explorer` accent wins
     over the default disabled-opacity rule) stayed full-bright
     while the rest of the board was visibly gated. The
     `sr-not-my-turn` body class is toggled in render() against the
     existing `interactive` flag so the dim flips synchronously
     with `setClickable`. Visual-only — pile-viewer click delegates
     remain wired so the scrap pile + explorer stay inspectable on
     the opponent's turn (mirror of REQ-334 / REQ-410). */
  /* [REQ-424] [PTR-078] Extend the PTR-075 opp-turn dim to the
     remaining player-owned non-button tiles — bases under
     `#sr-your-bases` and the YOU discard tile — which are rendered as
     `<div role="button">` by `renderBases` / `_buildPileTile` and so
     never pick up `[disabled]` from `setClickable()`. Visual-only:
     base `data-base-idx` and discard pile-viewer click delegates stay
     wired so the player can still inspect on the opponent's turn
     (mirror of the scrap pile + explorer contract). Opp-side bases
     (`#sr-their-bases`) and the opp discard tile
     (`.sr-discard-pile-tile-opp`) are intentionally NOT included —
     they belong to the active player on opp turn. */
  /* [REQ-427] [PTR-080] Extend the same dim to `#sr-hand
     button[data-card-id]` because under PTR-080 the hand row stays
     `disabled = false` on the opponent's turn (so the REQ-388 drag
     controller's pointerdown can fire) — which means the UA :disabled
     dim no longer applies to hand tiles, leaving them full-bright
     while the rest of the player's side is grayed. The on-drop / play
     self-gates keep release-on-target a no-op, so the visual cue is
     the only thing that needed restoring. */
  body.sr-not-my-turn #sr-game-area .sr-scrap-pile-tile,
  body.sr-not-my-turn #sr-trade-row button[data-slot-idx="explorer"],
  body.sr-not-my-turn #sr-your-bases .sr-base,
  body.sr-not-my-turn .sr-discard-pile-tile-you,
  body.sr-not-my-turn #sr-hand button[data-card-id] {
    filter: grayscale(0.6) brightness(0.65);
    opacity: 0.85;
  }
  /* Forced-discard fires when the OPPONENT plays a card that makes the
     player discard — so the `sr-not-my-turn` body class is set (it's
     the opponent's turn) but the player IS the one acting on the hand.
     Override the not-my-turn dim on hand cards (and the discard tile,
     for when discard-pile picking lands) so the picked surfaces are
     readable and full-bright while picking. */
  body.sr-not-my-turn.sr-forced-discard-active #sr-hand button[data-card-id],
  body.sr-not-my-turn.sr-forced-discard-active .sr-discard-pile-tile-you {
    filter: none;
    opacity: 1;
  }
  .sr-pick-status {
    display: block;
    margin: 0.4rem 0;
    padding: 0.3rem 0.6rem;
    background: #1a1a1a;
    border: 1px solid #5fc05f;
    color: #c0c0c0;
    font-family: monospace;
    font-size: 0.85rem;
    border-radius: 2px;
  }
  /* [REQ-408] [PTR-064] Live "Selected: N / max" read-out for the
     discard_then_draw field-tap picker. Same shape as .sr-pick-status
     so the prompt panel reads consistently across pick kinds. */
  .sr-pick-counter {
    display: block;
    margin: 0.4rem 0;
    padding: 0.3rem 0.6rem;
    background: #1a1a1a;
    border: 1px solid #5fc05f;
    color: #c0c0c0;
    font-family: monospace;
    font-size: 0.85rem;
    border-radius: 2px;
  }
  .sr-pick-confirm {
    display: inline-block;
    margin: 0.5rem 0.4rem 0.25rem 0;
    padding: 0.5rem 1.1rem;
    background: #2a402a;
    border: 1px solid #5fc05f;
    color: #5fc05f;
    font-family: monospace;
    font-size: 0.9rem;
    border-radius: 2px;
    cursor: pointer;
  }
  .sr-pick-confirm:disabled {
    opacity: 0.5;
    cursor: not-allowed;
  }
  .sr-pick-confirm:hover:not(:disabled) {
    background: #5fc05f;
    color: #0f0f0f;
  }
  .sr-pick-none {
    display: inline-block;
    margin: 0.5rem 0 0.25rem;
    padding: 0.5rem 1.1rem;
    background: #1a1a1a;
    border: 1px solid #c08080;
    color: #c08080;
    font-family: monospace;
    font-size: 0.9rem;
    border-radius: 2px;
    cursor: pointer;
  }
  .sr-pick-none:hover { background: #c08080; color: #0f0f0f; }
  /* [REQ-313] Card-readability styles. `.sr-card-head` is the bold
     name + cost line. `.sr-effects` is the inline ability list shown
     under every card so the player can read the rules without
     hovering. `.sr-effect-line` is one bullet. The hover `title`
     attribute on each card surfaces the same text as a native
     browser tooltip. Keeps trade-row / hand / base / inplay buttons
     auto-sized so longer text wraps instead of clipping. */
  .sr-trade-slot, .sr-hand-card { text-align: left; vertical-align: top; }
  /* [REQ-371] Orphan .sr-card-head re-style removed — REQ-362 retired
     the legacy text rows; this block was overriding the display:none
     hide via cascade order and leaking the legacy text onto the
     redesigned 4-corner card faces. */
  .sr-effects {
    margin-top: 0.15rem;
    color: #b0b0b0;
    /* [REQ-343] Card effect text scales with viewport. */
    font-size: var(--sr-card-effect-size);
    line-height: 1.15;
    overflow: hidden;
  }
  /* [REQ-212] Phase 1b — effect lines now lay out as
     [prefix?] [icon] [text]. The icons use `currentColor` so they
     pick up the tile's faction-tinted text colour set inline by
     applyTileStyle. `align-items: baseline` keeps the SVG vertically
     centred against the first line of wrapped text. */
  .sr-effect-line {
    white-space: normal;
    display: flex;
    align-items: baseline;
    gap: 0.3rem;
    margin: 0.1rem 0;
  }
  .sr-effect-icon, .sr-effect-prefix {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    width: 14px;
    height: 14px;
    line-height: 1;
    opacity: 0.85;
  }
  .sr-effect-prefix { opacity: 0.55; }
  .sr-effect-text { flex: 1 1 auto; }
  /* [REQ-212] Phase 1b — procedural art panel: a faction-tinted
     starfield + nebula generated by sr-visuals.js's `artPanelStyle`
     and a centred ship/base/outpost silhouette. The panel sits at
     the top of every card tile (hand, trade row, in-play, base) and
     is ~46 px tall so it doesn't dwarf the readable name + effects.
     Negative top margin pulls it flush with the tile's inner edge
     past the existing button padding. */
  .sr-art-panel {
    position: relative;
    /* [REQ-343] Art panel scales so the readable name+effects keep
       proportional space on shrunk cards. */
    height: clamp(18px, 4.5vh, 46px);
    margin: -0.4rem -0.7rem 0.4rem -0.7rem;
    border-bottom: 1px solid rgba(255, 255, 255, 0.08);
    overflow: hidden;
    border-radius: 2px 2px 0 0;
  }
  .sr-inplay-card .sr-art-panel,
  .sr-base .sr-art-panel {
    margin: -0.35rem -0.7rem 0.35rem -0.7rem;
  }
  .sr-art-silhouette {
    position: absolute;
    inset: 6px 18%;
    display: flex;
    align-items: center;
    justify-content: center;
    color: currentColor;
  }
  .sr-art-silhouette svg { width: 100%; height: 100%; }
  /* [REQ-327] Phase 2 — info trigger that opens the detail-card
     modal. Small "?" pip in the top-right of every card tile.
     Inherits the tile's faction-tinted color so it reads against
     the nebula art panel; bumps to full-opacity on hover/focus.
     Positioned absolutely so it overlays the art panel without
     reflowing the tile content. The parent tile sets
     `position: relative` below; without it the pip would escape
     the tile bounds. */
  .sr-trade-slot, .sr-hand-card, .sr-inplay-card, .sr-base { position: relative; }
  /* [REQ-364] Phase 10 redesign — the `?` info pip is retired. The
     whole card tile is now the tap target (single = view modal,
     double = action). The legacy positioning + hover rules below
     stay for diff cleanliness; this `display: none !important`
     override (cascading on the same selector) forces them inert
     so any stale call site that emits the span before the next
     polish chunk can't ship a visible regression. */
  .sr-card-info-btn { display: none !important; }
  .sr-card-info-btn {
    position: absolute;
    top: 4px;
    right: 4px;
    width: 20px;
    height: 20px;
    line-height: 18px;
    text-align: center;
    border-radius: 50%;
    border: 1px solid currentColor;
    background: rgba(0, 0, 0, 0.45);
    color: currentColor;
    font-size: 0.75rem;
    font-weight: bold;
    cursor: pointer;
    opacity: 0.6;
    user-select: none;
    z-index: 1;
  }
  .sr-card-info-btn:hover,
  .sr-card-info-btn:focus {
    opacity: 1;
    outline: 1px dotted currentColor;
    outline-offset: 1px;
  }
  /* [REQ-327] Phase 2 — detail-card modal. Native <dialog> styling.
     The faction-tinted border + glow are applied inline by
     populateDetail (matches the tile palette). ::backdrop dims the
     page behind the modal so the user's eye lands on the card.
     Mobile: full-width on viewports ≤ 600px. */
  .sr-card-detail {
    background: #0a0716;
    color: #f0f0f0;
    border: 2px solid #2a2d38;
    border-radius: 6px;
    padding: 1.25rem 1.5rem;
    max-width: 480px;
    width: 90%;
    font-family: monospace;
    box-shadow: 0 0 32px rgba(0, 0, 0, 0.6);
  }
  .sr-card-detail::backdrop {
    background: rgba(0, 0, 0, 0.65);
  }
  .sr-card-detail-close {
    position: absolute;
    top: 0.5rem;
    right: 0.6rem;
    background: transparent;
    border: none;
    color: #888;
    font-size: 1.4rem;
    line-height: 1;
    cursor: pointer;
    padding: 0.2rem 0.5rem;
  }
  .sr-card-detail-close:hover { color: #f0f0f0; }
  .sr-card-detail-title {
    font-size: 1.15rem;
    margin-bottom: 0.3rem;
    letter-spacing: 0.04em;
  }
  .sr-card-detail-meta {
    color: #b0b0b0;
    font-size: 0.8rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    margin-bottom: 0.85rem;
  }
  .sr-card-detail-art {
    height: 160px;
    border-radius: 3px;
    margin-bottom: 0.85rem;
    border: 1px solid rgba(255, 255, 255, 0.08);
    overflow: hidden;
    position: relative;
  }
  .sr-card-detail-silhouette {
    position: absolute;
    inset: 18px 22%;
    display: flex;
    align-items: center;
    justify-content: center;
    color: currentColor;
    opacity: 0.85;
  }
  .sr-card-detail-silhouette svg { width: 100%; height: 100%; }
  .sr-card-detail-effects {
    color: #d0d0d0;
    font-size: 0.85rem;
    line-height: 1.45;
  }
  .sr-card-detail-effect {
    display: flex;
    align-items: baseline;
    gap: 0.4rem;
    margin: 0.35rem 0;
    padding: 0.25rem 0;
    border-bottom: 1px dashed rgba(255, 255, 255, 0.05);
  }
  .sr-card-detail-effect:last-child { border-bottom: none; }
  @media (max-width: 600px) {
    .sr-card-detail {
      max-width: none;
      width: 95%;
      padding: 1rem 1.1rem;
    }
    .sr-card-detail-art { height: 130px; }
  }
  .sr-trade-slot, .sr-hand-card, .sr-inplay-card, .sr-base {
    /* [REQ-343] Cards size from a single token so trade row + hand +
       in-play + bases all scale together. The 1:1.353 ratio matches
       the original 170x230 art proportions. */
    width: var(--sr-card-w);
    height: var(--sr-card-h);
    padding: clamp(2px, 0.6vw, 8px);
    display: inline-flex;
    flex-direction: column;
    margin: 0.2rem 0.3rem 0.2rem 0;
    overflow: hidden;
  }
  /* [REQ-389] Hand + in-play cards use flex `gap` for inter-card
     spacing already, so the 0.3rem right-margin inherited from the
     shared sizing rule above just shoves the 5th card past the
     5-card max-width cap. Zero the horizontal margin here so
     5 × var(--sr-card-w) + 4 × var(--sr-card-gap) lines up exactly
     with the cap and the 5th card stays visible.
     Also zero the vertical (block) margins — the parent .sr-row is
     a flex container whose cross-axis size grows by each child's
     vertical margins, so the inherited 0.2rem block-margin added
     ~6.4px above + below every card, inflating the hand row to
     71.9px while the adjacent deck tile (margin: 0) stayed exactly
     card-h. Result was a 6.4px height mismatch between deck and hand
     in the same grid row. */
  .sr-hand-card,
  .sr-inplay-card {
    margin: 0;
  }
  button#sr-end-turn {
    background: transparent;
    border: 1px solid #e8f020;
    color: #e8f020;
    padding: 0.5rem 1rem;
    font-family: monospace;
    font-size: 0.8rem;
    letter-spacing: 0.1em;
    cursor: pointer;
  }
  button#sr-end-turn:hover:not([disabled]) { background: #e8f020; color: #0f0f0f; }
  button#sr-end-turn[disabled] { cursor: not-allowed; opacity: 0.4; }
  /* [REQ-334] Phase 9 layout — action row groups Play All / Attack /
     End Turn next to the active player's pools so the human reads
     resources + controls in one glance. The buttons mirror the
     existing `#sr-end-turn` palette: green for Play All (it pushes
     the player's tempo), red for Attack (combat damage), yellow
     for End Turn (wraps the turn). */
  /* [REQ-367] Phase 10 redesign — staged action button. One button
     cycles Play All -> Attack -> End Turn (one press per stage);
     the live label is set by render() via UI.computeStageAction.
     Legacy three-button row stays in the DOM but hides visually
     under .sr-legacy-action-buttons so a follow-up can drop them
     once the staged button has soaked. */
  button#sr-stage-btn {
    background: #0f0f0f;
    color: #e8f020;
    border: 2px solid #e8f020;
    border-radius: 6px;
    padding: 0.85rem 2rem;
    font-size: 1.15rem;
    font-weight: 700;
    letter-spacing: 0.05em;
    cursor: pointer;
    text-transform: uppercase;
    /* Background + colour transition only — the scale/shadow press
       animation is handled by the universal RFE-299 button-press
       rules at /src/button-press.css (`button:active` + `.pressed`),
       which the pre-fix `:active { transform: scale(0.97) }` was
       fighting with a slower 0.05s transition + a different scale. */
    transition: background 0.12s, color 0.12s;
  }
  button#sr-stage-btn:hover:not([disabled]),
  button#sr-stage-btn:focus:not([disabled]) {
    background: #e8f020;
    color: #0f0f0f;
    outline: none;
  }
  button#sr-stage-btn[disabled] { cursor: not-allowed; opacity: 0.4; }
  .sr-legacy-action-buttons { display: none; }
  .sr-action-row {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    align-items: center;
  }
  button#sr-play-all {
    background: transparent;
    border: 1px solid #40e8a0;
    color: #40e8a0;
    padding: 0.5rem 1rem;
    font-family: monospace;
    font-size: 0.8rem;
    letter-spacing: 0.1em;
    cursor: pointer;
  }
  button#sr-play-all:hover:not([disabled]) { background: #40e8a0; color: #0f0f0f; }
  button#sr-play-all[disabled] { cursor: not-allowed; opacity: 0.4; }
  button#sr-attack {
    background: transparent;
    border: 1px solid #ff8040;
    color: #ff8040;
    padding: 0.5rem 1rem;
    font-family: monospace;
    font-size: 0.8rem;
    letter-spacing: 0.1em;
    cursor: pointer;
  }
  button#sr-attack:hover:not([disabled]) { background: #ff8040; color: #0f0f0f; }
  button#sr-attack[disabled] { cursor: not-allowed; opacity: 0.4; }
  /* [REQ-334] Turn counter — large yellow chip so the player always
     knows which turn they're on at a glance. */
  .sr-turn-counter {
    display: inline-block;
    padding: 0.4rem 0.9rem;
    border: 1px solid #e8f020;
    color: #e8f020;
    font-size: 1rem;
    letter-spacing: 0.18em;
    border-radius: 2px;
  }
  /* [REQ-334] Game Log + Error Log panels — fixed-height scrollable
     monospace areas. The error variant tints text red so visible
     failures stand out from regular action history. */
  .sr-log-panel {
    max-height: 8rem;
    overflow-y: auto;
    border: 1px dashed #2a2d38;
    padding: 0.4rem 0.6rem;
    background: rgba(0, 0, 0, 0.25);
    font-family: monospace;
    font-size: 0.8rem;
    line-height: 1.4;
  }
  .sr-log-panel-error { border-color: #a33; color: #f88; }
  .sr-log-line { padding: 0.05rem 0; }
  .sr-log-empty { color: #666; font-style: italic; }
  /* [REQ-349] Trade-deck and player-deck pile tiles — non-interactive
     card-sized count tiles (deck contents are face-down by rule). The
     legacy small `.sr-trade-deck-chip` / `.sr-scrap-pile-chip` chip
     styles have been removed; both the trade deck and the scrap pile
     now render as card-sized `.sr-pile-tile` elements inside the
     trade row. */
  .sr-trade-deck-tile {
    border-color: #888;
    color: #888;
    cursor: default;
  }
  .sr-deck-pile-tile-opp { border-color: #e84040; color: #e84040; cursor: default; }
  .sr-deck-pile-tile-you { border-color: #40b4e8; color: #40b4e8; cursor: default; }
  .sr-deck-pile-tile.sr-deck-pile-tile-has-cards { background: rgba(255,255,255,0.1); }
  /* [REQ-449] Player name label rendered under each deck pile tile by
     renderDeckName. Hidden by default via the HTML `hidden` attribute;
     un-hidden by the renderer when state.playerNames[seat] is set. */
  .sr-deck-player-name {
    font-size: 0.75rem;
    text-align: right;
    color: #9aa0b4;
    padding: 0.15rem 0.25rem;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  /* [REQ-376] New Game lives inside the hamburger menu; the
     `.sr-menu-actions` flex row owns its spacing via `gap`, so the
     legacy footer margins are dropped. The visual treatment matches
     the neighbouring Toggle fullscreen / Home buttons (transparent
     bg, monospace text) — same chromeless-on-dark look. */
  button#sr-new-game {
    background: transparent;
    border: 1px solid #888;
    color: #888;
    padding: 0.3rem 0.6rem;
    font-family: monospace;
    font-size: 0.85rem;
    cursor: pointer;
    border-radius: 4px;
  }
  button#sr-new-game:hover { border-color: #f0f0f0; color: #f0f0f0; }

  /* [REQ-249] Phase 3a-ii — lobby panels. Default-hidden via the
     `hidden` HTML attribute; the inline boot toggles visibility per
     role click. Spacing matches .sr-section so the lobby reads as a
     sibling of the game board, not a foreign overlay. */
  .sr-lobby-section {
    border-top: 1px dashed #2a2d38;
    padding: 0.85rem 0;
    max-width: 720px;
  }
  .sr-lobby-section[hidden] { display: none; }
  .sr-lobby-section label {
    display: inline-block;
    font-family: monospace;
    color: #888;
    font-size: 0.7rem;
    letter-spacing: 0.18em;
    margin-bottom: 0.3rem;
    text-transform: uppercase;
  }
  .sr-lobby-section input[type="text"] {
    background: #17171d;
    border: 1px solid #2a2d38;
    color: #f0f0f0;
    font-family: monospace;
    font-size: 0.95rem;
    padding: 0.45rem 0.7rem;
    border-radius: 2px;
    outline: none;
    width: 100%;
    max-width: 320px;
  }
  .sr-lobby-section input[type="text"]:focus {
    border-color: #e8f020;
  }
  /* [REQ-093] flex wrapper so the Set button sits inline with the name input */
  .sr-name-input-row {
    display: flex;
    gap: 8px;
    align-items: stretch;
    max-width: 320px;
  }
  .sr-name-input-row input[type="text"] {
    flex: 1;
    min-width: 0;
    max-width: none;
    width: auto;
  }
  .sr-btn-set-name {
    background: #17171d;
    border: 1px solid #e8f020;
    color: #e8f020;
    font-family: monospace;
    font-size: 0.8rem;
    font-weight: bold;
    letter-spacing: 0.08em;
    padding: 0 14px;
    border-radius: 2px;
    cursor: pointer;
    flex-shrink: 0;
    transition: background 0.15s, color 0.15s;
  }
  .sr-btn-set-name:hover  { background: rgba(232,240,32,0.12); }
  .sr-btn-set-name:active { background: rgba(232,240,32,0.25); }
  .sr-role-btn {
    display: inline-block;
    margin: 0.4rem 0.6rem 0.4rem 0;
    background: transparent;
    border: 1px solid #2a2d38;
    color: #f0f0f0;
    font-family: monospace;
    font-size: 0.85rem;
    padding: 0.6rem 1rem;
    border-radius: 2px;
    cursor: pointer;
    transition: border-color 0.15s, color 0.15s, background 0.15s;
  }
  .sr-role-btn:not([disabled]):hover { border-color: #e8f020; color: #e8f020; }
  .sr-role-btn[disabled] { cursor: not-allowed; opacity: 0.45; }
  .sr-role-primary {
    border-color: #e8f020;
    color: #e8f020;
  }
  .sr-role-primary:not([disabled]):hover {
    background: #e8f020;
    color: #0f0f0f;
  }
  .sr-back-btn {
    display: inline-block;
    margin-top: 0.8rem;
    background: transparent;
    border: 1px solid #888;
    color: #888;
    font-family: monospace;
    font-size: 0.75rem;
    padding: 0.4rem 0.8rem;
    border-radius: 2px;
    cursor: pointer;
  }
  .sr-back-btn:hover { border-color: #f0f0f0; color: #f0f0f0; }
  .sr-soon-note {
    color: #888;
    font-size: 0.85rem;
    line-height: 1.5;
    margin: 0.4rem 0 0 0;
  }
  /* [REQ-252] Phase 3a-iii-C — room-code display. Oversized
     monospace chip so the host can read it across a room. */
  .sr-room-code {
    display: inline-block;
    font-family: monospace;
    font-size: 2.2rem;
    letter-spacing: 0.4em;
    color: #e8f020;
    padding: 0.4rem 0.8rem;
    border: 1px solid #e8f020;
    border-radius: 3px;
    background: rgba(232, 240, 32, 0.08);
    margin: 0.4rem 0;
    /* [REQ-444] Tap / press-and-hold to copy; pointer cursor + a tiny
       active highlight so the affordance is discoverable. */
    user-select: all;
    transition: background 0.15s ease-out;
    /* [PTR-???] Constrain width to original code size so "Copied!" text
       fits within the box without expanding it. Text centers horizontally. */
    width: 11rem;
    text-align: center;
    overflow: hidden;
    white-space: nowrap;
  }
  .sr-room-code:active,
  .sr-room-code[data-copied="1"] {
    background: rgba(232, 240, 32, 0.25);
  }
  #sr-join-code {
    font-family: monospace;
    font-size: 1.3rem;
    text-transform: uppercase;
    letter-spacing: 0.3em;
    max-width: 160px;
    padding: 0.5rem 0.75rem;
    border: 1px solid #2a2d38;
    border-radius: 2px;
    background: #17171d;
    color: #e8f020;
    outline: none;
    text-align: center;
    margin-right: 0.5rem;
  }
  #sr-join-code:focus { border-color: #e8f020; }

  /* [Card layout] Meta line — second row in every card tile;
     shows Ship/Base + cost + defense so field cards carry the
     same information set as trade-row cards. */
  /* [REQ-371] Orphan .sr-card-meta re-style removed — REQ-362 retired
     the legacy text rows; this block was overriding the display:none
     hide via cascade order. */
  /* [REQ-350] Trade row holds 8 card-sized columns: TRADE DECK tile
     + 5 trade slots (or empty placeholders) + Explorer + SCRAP tile.
     The 2D-aware `--sr-card-w` token from :root guarantees 8 columns
     fit the viewport width on every device class — no overflow-x
     needed. `min-width: 0` on the columns lets them honour the
     token's computed width without expanding past their flex/grid
     allocation. */
  /* [REQ-382] [REQ-383] [REQ-384] Trade row holds three groups
     (TRADE DECK, `.sr-trade-row-slots` wrapping the 5 slots + Explorer,
     SCRAP) centred horizontally via `justify-content: center`. REQ-384
     swaps `align-items: start` for `center` so the 8 tiles vertically
     centre within the trade row's grid-cell height — they sit in the
     optical middle of the row instead of hugging the top edge. */
  #sr-trade-row {
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: center;
    gap: var(--sr-card-gap);
    min-width: 0;
  }
  /* [REQ-382] Inner wrapper for the 5 trade slots + Explorer — kept as
     a 6-column grid so the buyable cards stay aligned as a unit
     regardless of how many slots are filled. */
  .sr-trade-row-slots {
    display: grid;
    grid-template-columns: repeat(6, minmax(0, var(--sr-card-w)));
    gap: var(--sr-card-gap);
    align-items: start;
    min-width: 0;
  }
  /* [REQ-384] Bases live in dedicated 1-card-wide vertical column
     zones on the side of the play surface (player LEFT, opp RIGHT
     via grid-template-areas above). The inner row flips from
     horizontal flex to vertical flex so up to 3 base tiles stack
     downward at standard --sr-card-w / --sr-card-h size. The
     min-height on the zone wrapper reserves enough vertical space
     in the grid cell for the 3-card stack at standard size. */
  #sr-their-bases,
  #sr-your-bases {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: var(--sr-card-gap);
    width: var(--sr-card-w);
    /* Allow vertical scroll when a player has more bases than fit in
       the column's 2-row grid span. Pre-fix the container had no
       overflow rule and the parent zone clipped via overflow: hidden,
       so the 4th+ base was just invisible. min-height: 0 lets the
       flex container shrink below its content height; the bases
       inside have flex-shrink: 0 so they don't compress, forcing the
       overflow-y to activate. overscroll-behavior-y: contain so the
       swipe doesn't leak into a page-level pull-to-refresh. */
    overflow-y: auto;
    overscroll-behavior-y: contain;
    min-height: 0;
  }
  #sr-their-bases > .sr-base,
  #sr-your-bases > .sr-base {
    flex-shrink: 0;
  }
  /* [REQ-386] Opp bases stack from the BOTTOM of the column upward
     so played bases huddle immediately above the trade row regardless
     of fill level. Player bases stay top-down (flex-start) so they
     hug the trade row from below — each player's played bases are
     adjacent to the trade row. */
  #sr-their-bases {
    justify-content: flex-end;
  }
  #sr-your-bases {
    justify-content: flex-start;
  }
  /* [REQ-404] When opp has no bases, the (no bases) placeholder span
     must hug the BOTTOM of the 3-card-tall #sr-their-bases column so
     it sits immediately above the trade row — mirror of the player's
     (no bases) span which naturally sits at the TOP of #sr-your-bases
     immediately below the trade row. The column's
     `justify-content: flex-end` (line 1386) only positions flex items
     once the line is sized; on the empty span (single inline child)
     `margin-top: auto` is what actually pushes it down through the
     remaining vertical space. Mirror rule on the player side is a
     no-op (margin-top: 0 is already the default for flex-start) so
     it is omitted to avoid carrying a redundant declaration. */
  #sr-their-bases > .sr-bases-empty {
    margin-top: auto;
  }
  /* [REQ-391] min-height MUST live on the INNER flex container, not
     the .sr-zone wrapper. Pre-fix the rule sat on the grandparent
     (.sr-zone[data-sr-area="oppbases"]); but column-flex `align-items:
     stretch` only stretches the cross-axis (width), so the inner
     flex container shrinks to its content's height regardless of the
     wrapper's reservation. With 1 base played the inner container
     was 1-card-tall and `justify-content: flex-end` had zero distance
     to push the card downward → opp bases rendered at the TOP of the
     column instead of immediately above the trade row. Moving the
     min-height to #sr-their-bases / #sr-your-bases gives flex-end a
     3-card-tall column to position the cards at the bottom = adjacent
     to the trade row from above. The grid cell auto-sizes around
     this inner min-height so REQ-380's grid-template-areas row
     reservation is unchanged. */
  #sr-their-bases,
  #sr-your-bases {
    /* [REQ-391] +1.2rem accounts for the 6 × 0.2rem vertical margins
       (top + bottom) on each .sr-base tile — without it the 3-card
       column overflows by that amount and overflow-y: auto clips the
       bottom of the third card. */
    min-height: calc(3 * var(--sr-card-h) + 2 * var(--sr-card-gap) + 1.2rem);
    /* [REQ-392] Pin max-height to a definite 3-card calc so 4+ bases
       reliably scroll via overflow-y: auto in every layout. With the
       previous `max-height: 100%`, in `auto`-row landscape layouts
       (styles.css:2329, 2374, 2377) the inner's max-content grew
       with base count, the auto track stretched, the 1fr play /
       trade / hand rows lost their 100dvh share, and their cards
       were clipped by `#sr-game-area`'s overflow: hidden. */
    max-height: calc(3 * var(--sr-card-h) + 2 * var(--sr-card-gap) + 1.2rem);
    overflow-y: auto;
    /* [REQ-397] Lock to vertical scroll only. Without this, sub-pixel
       width drift from section padding could produce a horizontal
       scrollbar on viewports where the auto LEFT/RIGHT grid column
       widens slightly under content pressure — user reported player
       bases scrolling horizontally with 4+ played. */
    overflow-x: hidden;
  }
  /* [REQ-426] flex-shrink: 0 on bases inside the column zones forces a
     4th base to push the column into vertical scroll (REQ-392's
     overflow-y: auto) instead of compressing the existing 3 — without
     this lock the cards inherit flex-shrink: 1, the flex column
     squashes every tile to fit the 3-card max-height, and the user
     sees four squashed bases instead of three full-size bases plus a
     scrollbar. Mirrors REQ-389 which pinned in-play ships against
     horizontal flex compression. */
  #sr-your-bases > .sr-base,
  #sr-their-bases > .sr-base {
    flex-shrink: 0;
  }
  /* [REQ-384] [REQ-412] Pile-tile mounts hug the right edge of their
     grid cell (auto column track can otherwise leave slack). REQ-411
     added oppdeck + oppdiscard — both moved to the RIGHT column. */
  .sr-zone[data-sr-area="youdiscard"] .sr-pile-tile-mount,
  .sr-zone[data-sr-area="youdeck"] .sr-pile-tile-mount,
  .sr-zone[data-sr-area="oppdiscard"] .sr-pile-tile-mount,
  .sr-zone[data-sr-area="oppdeck"] .sr-pile-tile-mount {
    justify-self: end;
  }
  /* [REQ-393] Player hand cell strips its section padding + gap so
     the cards stop being pushed below the (mobile-hidden) h2, and
     the youdeck pile-tile bottom-pins so it sits at the same
     vertical baseline as the hand cards. Pre-fix the hand cards
     rendered offset downward by section.sr-section's vertical
     padding while the deck tile sat at the top of its bare cell —
     they shared a grid row but not a baseline. flex-end on the
     section pushes cards to the bottom of the cell on viewports
     where the h2 is still visible (>480px landscape, etc.) so
     desktop users see the same alignment. */
  .sr-zone[data-sr-area="hand"] section.sr-section {
    padding: 0;
    gap: 0;
    /* Make the section a flex column so `justify-content: flex-end`
       actually bottom-pins the .sr-row-scroller inside. Pre-fix the
       section was a block element so `justify-content` was a no-op:
       the cards sat at the TOP of row 8 while the youdeck flex-end
       put the deck tile at the BOTTOM — they ended up at opposite
       vertical ends of the same grid row, breaking the "deck inline
       with the hand" baseline. */
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
  }
  .sr-zone[data-sr-area="youdeck"] .sr-pile-tile-mount {
    align-self: end;
  }
  /* [REQ-385] Hand cards align right inside the centre column so the
     hand starts adjacent to the deck pile in the right column instead
     of spreading from the left edge. The .sr-row-scroller wrapper
     justifies its children right too so the chevron + scroll affordance
     stays aligned with the cards. */
  #sr-hand {
    justify-content: flex-end;
  }
  .sr-row-scroller[data-sr-row="hand"] {
    justify-content: flex-end;
    /* [REQ-368] Cap the hand scroller's visible width at exactly
       5 cards + 4 inter-card gaps so the 6th+ hand card overflows
       horizontally and is only reachable via the swipe-scroll +
       chevron affordance. `margin-left: auto` floats the scroller to
       the right of its grid cell so the rightmost hand card stays
       flush with the deck-tile column. Paired with the
       `margin-inline: 0` override on `.sr-hand-card` (~line 1247) so
       the cap math is exact. */
    max-width: calc(5 * var(--sr-card-w) + 4 * var(--sr-card-gap));
    margin-left: auto;
  }
  /* [REQ-385] Trade zone vertically + horizontally centres its content
     so the 8-card trade row sits in the optical middle of the trade
     grid cell — visible vertical centring between the opp and player
     play areas. */
  .sr-zone[data-sr-area="trade"] {
    display: flex;
    align-items: center;
    justify-content: center;
  }
  /* Zero the inherited `padding: var(--sr-section-py) 0` on the trade
     row's section so the trade-row cards sit flush against the
     opp HUD above + player HUD below. Paired with the negative
     margin-block on the two HUD zones (further down) which eats the
     grid `gap: var(--sr-section-gap)` between trade and each HUD.
     Together those two changes close the visible dead space the user
     reported between each gold-framed HUD and the trade row. */
  .sr-zone[data-sr-area="trade"] section.sr-section {
    padding-block: 0;
  }
  /* [REQ-385] Hide the DISCARD overlay label on face-up discard tiles
     so the top-card art reads cleanly without the word "DISCARD"
     printed across it. Empty (face-down) discard tiles keep their
     `.sr-pile-tile-head` label so the player still knows which tile
     is the discard pile. */
  .sr-pile-tile-faceup.sr-discard-pile-tile .sr-pile-tile-overlay-label {
    display: none;
  }
  /* [REQ-385] [REQ-386] Bases with an unfired activated_* ability
     glow yellow (REQ-385). REQ-386 retired the small ⚡ Activate
     button + thunder-bolt — the glow is the only visual cue, and
     double-tap on the card body fires the ability via REQ-378's
     singleOrDoubleTap delegate. Per user feedback "the glow is fine
     how it is" — no outline / inner ring. */
  .sr-base.sr-base-can-activate {
    outline: 2px solid rgba(232, 240, 32, 0.9);
    outline-offset: 2px;
    animation: sr-activate-pulse 2.2s ease-in-out infinite;
    box-shadow: 0 0 14px 3px #e8f020;
  }
  /* [REQ-387] Ship + base tiles whose scrap_for_* ability is still
     firable get the same yellow glow REQ-385 set on activated bases.
     Bases with BOTH activated_* and scrap_for_* (Blob Wheel, Trading
     Post, Barter World) carry both classes; once the activated fires
     (.sr-base-can-activate is dropped) the .sr-can-activate keeps the
     glow on so the player remembers they can still scrap the base
     via the REQ-387 multi-ability picker. */
  .sr-inplay-card.sr-can-activate,
  .sr-base.sr-can-activate {
    outline: 2px dashed rgba(232, 240, 32, 0.8);
    outline-offset: 2px;
    box-shadow: 0 0 10px 2px #e8f020;
  }
  /* [REQ-396] [PTR-060] Trade-row affordability glow — saturated
     gold halo on slots whose card.cost <= local viewer's trade pool.
     play-ui.js renderTradeRow ALSO stamps the same boxShadow inline
     after buildCardBody (because applyTileStyle writes a faction-
     palette inline boxShadow that beats this class rule in the
     cascade); this rule is the back-stop for any future call site
     that doesn't fight an inline style. The class is flipped on
     every renderBoard() call, so the glow updates whenever the
     trade pool moves (play / buy / scrap). */
  /* [REQ-714] [REQ-717] Affordability via CONTRAST. Earlier iterations
     (a gold box-shadow halo, then an amber outline + cost pill) either
     blended into the starfield, covered the card's faction colour, or
     — because the trade-row gap is only 2-8px — collided with the
     neighbouring slot. Instead, trade-row cards the CURRENT BUYER
     can't afford dim + desaturate so the affordable ones keep full
     faction colour and read as buyable. Nothing is drawn outside the
     card, so there is no collision and no faction coverage. The CSS
     rule applies on BOTH turns; .sr-can-afford is stamped by
     play-ui.js's renderTradeRow with asymmetric semantics (REQ-717,
     superseding REQ-716): on YOUR turn against your live trade pool,
     on the OPPONENT's turn against Math.max(opp.trade, sum-of-trade-
     effects in opp.inPlay). The opp-side inPlay sum is monotonic
     across their turn (cards stay in inPlay until endTurn), so the
     glow PERSISTS through their buys as a "what could they afford"
     tactical cue. Excludes pick-targets (free ally buys) + empty
     slots. */
  /* [REQ-714] [REQ-717] [REQ-718] Affordability via CONTRAST. The
     previous design fought a hidden cascade collision: setClickable
     flipped trade-row buttons to disabled=true on opp turn, triggering
     .sr-trade-slot[disabled] { opacity: 0.5 } + the browser UA :disabled
     dim, which clobbered the brighten + outline cue regardless of the
     filter strength used. REQ-718 keeps trade buttons enabled both
     turns (the click delegate self-gates), so the simple two-way
     contrast model now reads cleanly. On YOUR turn, .sr-can-afford
     is computed against your live trade pool; on the OPP turn against
     Math.max(opp.trade, sum-of-trade-effects in opp.inPlay) so the
     glow persists through the AI's buys (REQ-717). Excludes pick-targets
     (free ally buys) + empty slots. */
  #sr-trade-row .sr-trade-slot:not(.sr-can-afford):not(.sr-pick-target):not(.sr-trade-slot-empty) {
    filter: grayscale(0.6) brightness(0.55);
    opacity: 0.92;
  }
  .sr-trade-slot.sr-can-afford {
    /* [REQ-714] [REQ-717] [REQ-718] Affordable cards actively light up
       — brighten + saturate + lift + magenta outline / halo. Magenta is
       deliberately faction-palette-free (TF=yellow, Blob=green, SE=red,
       MC=cyan, neutral=white) so the cue can't be confused with a
       faction tint. The !important on box-shadow beats applyTileStyle's
       inline faction boxShadow that wins the cascade otherwise. */
    filter: brightness(1.18) saturate(1.15);
    transform: translateY(-3px);
    outline: 2px solid #ff2e88;
    outline-offset: -1px;
    box-shadow: 0 0 12px 2px #ff2e88, inset 0 0 0 1px rgba(255, 46, 136, 0.55) !important;
    transition: transform 0.12s ease, filter 0.12s ease, outline 0.12s ease, box-shadow 0.12s ease;
  }
  /* [REQ-718] Mirror of the PTR-080 hand-button "non-interactive cue
     without disabled=true" pattern. On opp turn the trade-row buttons
     stay enabled (so the click delegate's self-gates handle no-op taps
     and the .sr-can-afford brighten reads cleanly) but unaffordable +
     non-explorer slots get a visual dim so the row still reads as
     "you can't interact right now" — same affordance the [disabled]
     opacity 0.5 used to give. Affordable slots are excluded so their
     brighten / outline / halo cue stays unobstructed. Explorer is
     excluded because line 1083's body.sr-not-my-turn explorer-specific
     dim already covers it. */
  body.sr-not-my-turn #sr-trade-row .sr-trade-slot:not(.sr-can-afford):not(.sr-pick-target):not(.sr-trade-slot-empty):not(.sr-trade-explorer) {
    cursor: not-allowed;
  }
