  /* Enchanted Wilds — night forest, arcane violet, firefly amber */
  :root {
    /* Tell the browser which scheme the palette is, or it keeps painting
       native widgets light in dark mode — a <select>'s OS popup came out
       white with the options inheriting near-white --ink (white-on-white,
       FEEDBACK #28). Scoped per palette, not "light dark": the UA must
       never pick the opposite scheme from the CSS variables. */
    color-scheme: light;
    --bg: #F5F3FB;
    --panel: #FFFFFF;
    --ink: #262038;
    --muted: #6F678C;
    --line: #DFD9EE;
    /* augment tiers, referenced by every surface that shows an augment */
    --tier-s: #9aa4ad;
    --tier-g: #d9a441;
    --tier-p: #a48be8;
    --accent: #6D4FC4;
    --accent-ink: #FBFAFF;
    --hot-bg: #FBEECC;
    --hot-ink: #7A5810;
    --down: #B3453E;
    --up: #2E7D5B;
    --team-a-bg: #EAE4FB;
    --team-a-ink: #4A3894;
    --team-a-line: #6D4FC4;
    /* The line axis (app/lines.js): one hue per line type, used only by the
       scatter's outer ring and its legend. Deliberately NOT --up/--down,
       which the same markers already use for performance -- the inner stroke
       says how it went, the outer ring says what it was. */
    --line-level: #38618C;
    --line-rr1: #B07A2E;
    --line-rr2: #7A4FB5;
    --line-rr3: #2E7D6E;
    --team-b-bg: #DCE8F2;
    --team-b-ink: #274B66;
    --team-b-line: #38618C;
    --shadow: 0 1px 2px rgba(38, 32, 56, 0.06), 0 4px 16px rgba(38, 32, 56, 0.06);
  }
  @media (prefers-color-scheme: dark) {
    :root {
      color-scheme: dark;
      --bg: #100E1A;
      --panel: #191627;
      --ink: #EDE9F7;
      --muted: #9C94B8;
      --line: #2E2843;
      --accent: #A78BFA;
      --accent-ink: #14101F;
      --hot-bg: #3A2E14;
      --hot-ink: #F2C879;
      --down: #F08A8A;
      --line-level: #7FA8D4;
      --line-rr1: #E0B267;
      --line-rr2: #B79BEE;
      --line-rr3: #6FC7B2;
      --up: #7FE0B0;
      --team-a-bg: #272041;
      --team-a-ink: #C9B8FF;
      --team-a-line: #8B6EF3;
      --team-b-bg: #1C2B3E;
      --team-b-ink: #9CC4E4;
      --team-b-line: #4A7099;
      --shadow: 0 1px 2px rgba(0, 0, 0, 0.4), 0 4px 16px rgba(0, 0, 0, 0.3);
    }
  }

  * { box-sizing: border-box; }
  body {
    background: var(--bg);
    color: var(--ink);
    font: 16px/1.55 "Avenir Next", "Helvetica Neue", system-ui, sans-serif;
    margin: 0;
    padding: 1.25rem 1rem 3rem;
  }
  .wrap { max-width: 560px; margin: 0 auto; display: flex; flex-direction: column; gap: 1rem; }
  .wrap[hidden] { display: none; }
  #reviewHome, #reviewBlock { display: flex; flex-direction: column; gap: 0.75rem; }
  /* both are display:flex, which outranks the UA's [hidden] rule */
  #reviewBlock[hidden] { display: none; }

  /* sign-in gate — covers everything until Clerk auth (+ beta allowlist) clears */
  .authgate {
    position: fixed;
    inset: 0;
    z-index: 20;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--bg);
    padding: 1.5rem;
  }
  .authgate[hidden] { display: none; }
  .authgate-card {
    max-width: 320px;
    width: 100%;
    text-align: center;
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
  }
  .preset-banner {
    background: var(--panel);
    border: 1px solid var(--line);
    border-left: 3px solid var(--accent);
    border-radius: 0 8px 8px 0;
    padding: 0.7rem 0.9rem;
    margin-bottom: 1rem;
    color: var(--muted);
    font-size: 0.85rem;
    line-height: 1.5;
  }
  .preset-banner[hidden] { display: none; }
  .preset-banner b { color: var(--ink); }
  /* the two account nudges (LINK_NUDGE_SPEC.md): banner copy with a real
     button, sized to sit inside the banner row */
  .link-nudge button {
    margin-left: 0.6rem;
    padding: 0.3rem 0.8rem;
    background: var(--accent);
    color: var(--accent-ink);
    border: none;
    border-radius: 8px;
    font-weight: 600;
    font-size: 0.82rem;
  }
  .link-nudge .nudge-later {
    background: none;
    color: var(--muted);
    border: 1px solid var(--line);
    font-weight: 400;
  }

  .authgate-title { font-size: 1.3rem; font-weight: 700; color: var(--ink); }
  /* The card is 320px wide and the button has to stay above the fold on a
     phone, so this is three lines of copy, not a paragraph. */
  .authgate-desc {
    color: var(--muted);
    font-size: 0.88rem;
    line-height: 1.5;
    text-align: left;
  }
  .authgate-sub { color: var(--muted); font-size: 0.9rem; }
  .authgate-legal {
    display: flex;
    justify-content: center;
    gap: 0.5rem;
    font-size: 0.78rem;
  }
  .authgate-legal a { color: var(--muted); text-decoration: none; }
  .authgate-legal a:hover { text-decoration: underline; }
  .authgate-legal span { color: var(--line); }
  #signInBtn {
    margin-top: 0.5rem;
    padding: 0.7rem 1rem;
    border: none;
    border-radius: 10px;
    background: var(--accent);
    color: var(--accent-ink);
    font-weight: 600;
    font-size: 0.95rem;
  }
  .authgate-note {
    margin-top: 0.5rem;
    padding: 0.6rem 0.75rem;
    border: 1px solid var(--line);
    border-radius: 8px;
    background: var(--panel);
    color: var(--muted);
    font-size: 0.82rem;
    line-height: 1.4;
    /* `break-all` was here for the days this box held a raw Clerk id to copy;
       it now holds sentences, and it was breaking them mid-word ("happenin /
       g"). `anywhere` still breaks an unspaced token that cannot fit, and
       leaves ordinary prose alone. */
    overflow-wrap: anywhere;
  }
  .authgate-note[hidden] { display: none; }

  /* signed-in strip — who's here, and a way out */
  .topbar {
    display: flex;
    align-items: center;
    /* One child now, and its menu hangs off the right edge (.acct-menu is
       right: 0). Left-aligned, that menu opened 84px off the left of a 375px
       phone — space-between was doing nothing here once sign out moved inside. */
    justify-content: flex-end;
    gap: 0.5rem;
    padding-top: 0.2rem;
    font-size: 0.85rem;
    color: var(--muted);
  }
  /* The account name is the settings door. Linking a Riot account is a
     twice-a-set errand that used to hold a quarter of the tab bar; behind the
     name it stays reachable without competing with the tabs that carry data. */
  .acct-menu-wrap { position: relative; }
  #whoBtn {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    min-height: 36px;
    background: none;
    border: 1px solid var(--line);
    border-radius: 8px;
    padding: 0.25rem 0.6rem;
    color: var(--muted);
    font: inherit;
    font-size: 0.8rem;
  }
  .who-bars { display: flex; flex-direction: column; gap: 3px; }
  .who-bars i { display: block; width: 13px; height: 1.5px; background: currentColor; }
  .acct-menu {
    position: absolute;
    top: calc(100% + 0.35rem);
    right: 0;
    z-index: 46;             /* over the sticky tab bar (5) and the stats drawer (22) */
    min-width: 13rem;
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
    background: var(--panel);
    border: 1px solid var(--line);
    border-radius: 10px;
    padding: 0.3rem;
    box-shadow: var(--shadow);
  }
  .acct-menu[hidden] { display: none; }
  .acct-menu button {
    min-height: 40px;
    text-align: left;
    background: none;
    border: 0;
    border-radius: 7px;
    padding: 0 0.6rem;
    color: var(--ink);
    font: inherit;
    font-size: 0.85rem;
  }
  .acct-menu button:hover { background: var(--bg); }
  .acct-menu .menu-sep { height: 1px; background: var(--line); margin: 0.25rem 0.2rem; }

  /* Linking an account is an errand you finish and leave, not a place in the
     app you can end up in. A dialog says that; a tab said the opposite. */
  .modal {
    position: fixed;
    inset: 0;
    /* above the stats drawer (22): a dialog raised underneath a half-screen
       opaque panel had its heading row and part of its form unclickable */
    z-index: 45;
    display: flex;
    align-items: flex-start;
    justify-content: center;
    padding: 1.5rem 1rem 3rem;
    overflow-y: auto;
    background: rgba(16, 14, 26, 0.6);
  }
  .modal[hidden] { display: none; }
  .modal-card {
    width: 100%;
    max-width: 34rem;
    background: var(--bg);
    border: 1px solid var(--line);
    border-radius: 14px;
    padding: 1rem 1.1rem 1.2rem;
    box-shadow: var(--shadow);
  }
  .modal-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.5rem;
    margin-bottom: 0.6rem;
  }
  .modal-head .chart-title { margin-bottom: 0; }
  .modal-close {
    flex: 0 0 auto;
    min-width: 36px;
    min-height: 36px;
    background: none;
    border: 1px solid var(--line);
    border-radius: 8px;
    color: var(--muted);
    font: inherit;
    font-size: 0.9rem;
  }

  .compare-block {
    margin: 0.2rem 0 0.9rem; padding: 0.4rem 0.6rem;
    border: 1px solid var(--line); border-radius: 10px; background: var(--bg);
  }
  .compare-block table { width: 100%; }
  .compare-group .admin-name { color: var(--muted); font-style: italic; }
  .compare-me .admin-name { font-weight: 700; color: var(--ink); }

  .tabbar {
    display: flex;
    gap: 0.25rem;
    position: sticky;
    top: 0;
    z-index: 5;
    background: var(--bg);
    padding: 0.4rem 0;
    border-bottom: 1px solid var(--line);
  }
  .tab {
    flex: 1;
    min-height: 44px;
    background: none;
    border: none;
    border-radius: 8px;
    color: var(--muted);
    font-weight: 600;
    font-size: 0.9rem;
    letter-spacing: 0.02em;
    position: relative;
  }
  .tab.active { background: var(--panel); color: var(--ink); box-shadow: var(--shadow); }
  .tab-badge {
    display: inline-block;
    min-width: 1.15rem;
    margin-left: 0.35rem;
    padding: 0 0.3rem;
    border-radius: 99px;
    background: var(--hot-bg);
    color: var(--hot-ink);
    font-size: 0.72rem;
    line-height: 1.3rem;
    vertical-align: 0.05em;
  }
  .tab-badge[hidden] { display: none; }
  /* The one-time pointer at the games tab: there are games and nothing has
     been written up yet, so the tab holding the instructions is worth
     finding. A dot rather than a count -- a number would be the coverage
     figure GAMES_TAB_DESIGN.md refuses, and there is nothing here to measure,
     only somewhere to go. It carries no tooltip on purpose: tapping it lands
     on #gamesFirstRun, which says the same thing in text a phone can read. */
  .tab-dot {
    display: inline-block;
    width: 0.45rem;
    height: 0.45rem;
    margin-left: 0.35rem;
    border-radius: 50%;
    background: var(--hot-ink);
    vertical-align: 0.35em;
  }
  .tab-dot[hidden] { display: none; }
  /* PBE/live era switch — one per data tab (games, stats), both driving the
     same view state. Styled as a small segmented switch, not a third tab
     bar: it scopes the page, it doesn't navigate. */
  .env-row {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    margin: 0 0 0.75rem;
  }
  .env-toggle {
    display: inline-flex;
    padding: 2px;
    border: 1px solid var(--line);
    border-radius: 99px;
    background: var(--bg);
  }
  .env-btn {
    min-height: 30px;
    padding: 0 0.85rem;
    border: none;
    border-radius: 99px;
    background: none;
    color: var(--muted);
    font-weight: 600;
    font-size: 0.8rem;
    letter-spacing: 0.02em;
  }
  .env-btn.active { background: var(--panel); color: var(--ink); box-shadow: var(--shadow); }
  .env-note { color: var(--muted); font-size: 0.78rem; }
  .env-row[hidden] { display: none; }

  .view { display: none; flex-direction: column; gap: 1rem; min-width: 0; }
  .view.active { display: flex; }

  .capture-empty { color: var(--muted); font-size: 0.9rem; text-align: center; margin: 0.5rem 0; }
  .capture-empty[hidden] { display: none; }

  /* desktop tool — use the width */
  @media (min-width: 900px) {
    body { padding: 1.5rem 2rem 3rem; }
    .wrap { max-width: 1180px; }
    .tabbar { max-width: 1180px; }

    /* Games = master/detail: game log left, the open game right. The log is
       the spine of a recording page -- it is what you arrive to scan and what
       you tap to choose the game you are writing up -- so it reads first, and
       the panel is what the tap fills in. The DOM keeps the entry column
       first so the mobile single-column order (write, then log) is unchanged;
       `order` does the swap at desktop width only. */
    #view-games.active {
      display: grid;
      grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
      gap: 2rem;
      align-items: start;
    }
    #view-games #gamesEntry { min-width: 0; display: flex; flex-direction: column; gap: 1rem; }
    #view-games #reviewLog { min-width: 0; order: -1; }
    #view-games #log { max-height: 80vh; overflow-y: auto; }
    /* page-level chrome, not a column: span both, and stay above them */
    #view-games #refreshRow { grid-column: 1 / -1; order: -2; margin-bottom: 0; }
    #view-games .games-banner { grid-column: 1 / -1; order: -3; }
    /* era switch scopes the whole tab, so it sits above everything it scopes */
    #view-games .env-row { grid-column: 1 / -1; order: -4; margin-bottom: 0; }

    /* Stats = dashboard: summary full-width, then two columns of tiles */
    #view-stats.active {
      display: grid;
      grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
      gap: 1.5rem 2.5rem;
      align-items: start;
    }
    /* page-level chrome spans both columns; the blocks that do not say so
       auto-place two-up, which is what the .stats-col wrappers used to fake
       before they came out (2026-08-31) */
    #view-stats .env-row { grid-column: 1 / -1; }
    #view-stats .stats-row { grid-column: 1 / -1; }
    #view-stats .stats-line { grid-column: 1 / -1; font-size: 1.1rem; }
    /* the scope row is flex-basis:100%, so un-hiding it added a second line to
       the summary and pushed every block below down 27px -- including the row
       that was just tapped. Hold the space whether or not a filter is on; the
       ::before gives the empty span a line box to reserve. */
    #view-stats .stats-line #statsScope[hidden] { display: block; visibility: hidden; }
    #view-stats .stats-line #statsScope[hidden]::before { content: "\00a0"; }
    #view-stats .group-line { grid-column: 1 / -1; }
    /* The rail spans the tab when it is docked at the bottom. */
    #view-stats .stats-games { grid-column: 1 / -1; }
    #view-stats .coach-panel { grid-column: 1 / -1; }
  }
  /* The notes analysis. Deliberately quiet: it sits under the charts because
     it is the slowest thing on the page to read and the only one that costs
     money to produce. */
  .coach-panel {
    border: 1px solid var(--line);
    border-radius: 12px;
    background: var(--panel);
    padding: 0.9rem 1rem 1rem;
    /* It leads the tab, so the gap belongs underneath it. */
    margin-bottom: 0.4rem;
  }
  .coach-head { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; }
  .coach-head h3 { margin: 0; font-size: 1rem; }
  .coach-count { color: var(--muted); font-size: 0.8rem; }
  .coach-blurb { color: var(--muted); font-size: 0.85rem; margin: 0.4rem 0 0.8rem; max-width: 62ch; }
  .coach-actions { display: flex; flex-wrap: wrap; gap: 0.5rem; }
  .coach-actions button {
    min-height: 32px;
    padding: 0 0.9rem;
    border-radius: 99px;
    border: 1px solid var(--line);
    background: var(--panel);
    color: var(--ink);
    font-size: 0.85rem;
    font-weight: 600;
  }
  .coach-actions button.primary { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }
  .coach-actions button[disabled] { opacity: 0.5; cursor: default; }
  .coach-status { color: var(--muted); font-size: 0.85rem; margin-top: 0.7rem; }
  .coach-theme { border-top: 1px solid var(--line); padding-top: 0.8rem; margin-top: 0.8rem; }
  .coach-theme h4 { margin: 0 0 0.3rem; font-size: 0.95rem; }
  /* The finding's target bucket (mistakes / lobby / strengths). One accent
     for strengths -- the positive read is the new thing -- the rest muted. */
  .coach-bucket {
    display: inline-block;
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--muted);
    border: 1px solid var(--line);
    border-radius: 3px;
    padding: 0.05rem 0.35rem;
    margin-right: 0.5rem;
    vertical-align: 0.1rem;
  }
  .coach-bucket-strengths { color: var(--accent); border-color: var(--accent); }
  .coach-quote {
    border-left: 2px solid var(--line);
    padding-left: 0.7rem;
    margin: 0.4rem 0;
    color: var(--muted);
    font-size: 0.85rem;
  }
  .coach-quote .coach-when { display: block; font-size: 0.75rem; opacity: 0.8; }
  .coach-change { font-size: 0.9rem; margin: 0.5rem 0 0; }
  .coach-stat { font-size: 0.85rem; color: var(--muted); margin: 0.25rem 0; font-variant-numeric: tabular-nums; }
  /* What the next few notes would add. Reads as the answer's own next step,
     not as a nag, so it is styled like the body rather than like a prompt. */
  .coach-next { font-size: 0.85rem; color: var(--muted); margin: 0.9rem 0 0; font-style: italic; }
  @media (min-width: 900px) {
    #view-stats #statsGamesSpacer { grid-column: 1 / -1; }
    /* the explorer is the hub, not a sidebar widget — full width */
    #view-stats #explorerBlock { grid-column: 1 / -1; }
    #view-stats #statsTable { grid-column: 1 / -1; }
    #statsTable svg { max-width: none; }
    .chart-block svg { max-width: 460px; }
  }

  .top-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
  }
  .top-row label {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    color: var(--muted);
    font-size: 0.9rem;
  }
  input[type="date"] {
    font: inherit;
    color: var(--ink);
    background: var(--panel);
    border: 1px solid var(--line);
    border-radius: 8px;
    padding: 0.4rem 0.6rem;
    min-height: 44px;
  }

  .grid { display: flex; flex-direction: column; gap: 0.35rem; }
  .line-label {
    font-size: 0.68rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--muted);
    font-weight: 600;
    margin-top: 0.35rem;
  }
  .line-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 6px;
  }

  .board-strip {
    flex-basis: 100%;
    display: flex;
    flex-wrap: wrap;
    gap: 3px;
    padding-top: 3px;
  }
  .unit { position: relative; display: inline-flex; flex-direction: column; align-items: center; gap: 1px; }
  .champ-ic {
    width: 24px;
    height: 24px;
    border-radius: 4px;
    border: 2px solid var(--line);
    background: var(--bg);
    display: block;
  }
  .champ-ic.broken { visibility: hidden; }
  /* the by-unit table reads as a list of names without these; same art the
     board strips already hotlink, just smaller and without the star border */
  .unit-cell { display: flex; align-items: center; gap: 6px; min-width: 0; }
  .unit-ic {
    width: 22px; height: 22px; border-radius: 4px; flex: 0 0 auto;
    border: 1px solid var(--line); background: var(--bg); display: block;
  }
  .unit-ic.broken { visibility: hidden; }
  .unit-nm { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  /* comps not named after a unit keep the names aligned rather than ragged */
  .unit-ic-gap { width: 22px; flex: 0 0 auto; }
  .place-cell { white-space: normal; }
  .place-pill.sm { font-size: 0.62rem; padding: 1px 5px; margin-right: 3px; display: inline-block; }
  /* the start explorer: breadcrumb of chosen components, child chips that
     extend the path, then what got played from that start */
  .explore-row { display: flex; align-items: center; flex-wrap: wrap; gap: 0.35rem; margin: 0.45rem 0; }
  .crumb-chip, .child-chip { display: inline-flex; align-items: center; gap: 4px;
    background: transparent; border: 1px solid var(--line); border-radius: 999px;
    padding: 2px 8px; cursor: pointer; font: inherit; font-size: 0.75rem; color: var(--ink); }
  .crumb-chip { border-color: var(--accent); }
  .child-chip .cnt { color: var(--muted); }
  .explore-sep { color: var(--muted); font-size: 0.8rem; }
  .explore-comp { margin: 0.35rem 0 0.1rem; font-size: 0.85rem; }
  .combo-game { font-size: 0.8rem; color: var(--muted); padding: 0.15rem 0; }
  .combo-game b { color: var(--ink); font-weight: 600; }
  .combo-game .unit-ic { vertical-align: middle; }
  .open-avg { color: var(--muted); font-size: 0.72rem; white-space: nowrap; }
  .champ-ic.s2 { border-color: #A8BCD4; }
  .champ-ic.s3 { border-color: #E3B34D; }
  .champ-ic.s4 { border-color: #6BE3C0; }
  .unit.big .champ-ic { width: 40px; height: 40px; }
  .star-row {
    font-size: 8px;
    line-height: 1;
    height: 8px;
    letter-spacing: -1px;
    color: transparent;
  }
  .star-row.st2 { color: #A8BCD4; }
  .star-row.st3 { color: #E3B34D; }
  .star-row.st4 { color: #6BE3C0; }
  .unit.big .star-row { font-size: 11px; height: 11px; }
  .item-row { display: flex; gap: 1px; }
  .item-ic { width: 10px; height: 10px; border-radius: 2px; display: block; }
  .unit.big .item-ic { width: 13px; height: 13px; }
  .item-unknown { background: var(--line); border: 1px solid var(--muted); }
  .board-big { gap: 5px; margin-top: 0.45rem; }
  .traits-line { color: var(--muted); font-size: 0.78rem; margin-top: 0.35rem; }

  /* lobby view: all eight boards of the open game, MetaTFT-style — one row a
     player, sorted by placement, your own row highlighted. Board strips are
     the same unitHtml() markup the log rows use. */
  .lobby { margin-top: 0.8rem; }
  .lobby-row {
    display: flex; align-items: flex-start; gap: 9px;
    padding: 6px 6px 7px; border-top: 1px solid var(--line);
  }
  .lobby-row.you { background: var(--team-a-bg); border-radius: 6px; }
  .lobby-id { flex: 0 0 138px; min-width: 0; }
  .lobby-comp {
    color: var(--muted); font-size: 0.72rem;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .lobby-comp.rival { color: var(--down); font-weight: 600; }
  .lobby-traits { display: flex; flex-wrap: wrap; gap: 3px; margin-top: 3px; }
  .trait-chip {
    font-size: 0.62rem; line-height: 1; padding: 2px 4px; border-radius: 3px;
    border: 1px solid var(--line); color: var(--muted); white-space: nowrap;
  }
  .trait-chip.ts1 { border-color: #B08D57; color: #B08D57; }
  .trait-chip.ts2 { border-color: #A8BCD4; color: #8FA6C4; }
  .trait-chip.ts3 { border-color: #E3B34D; color: #C29A3A; }
  .trait-chip.ts4 { border-color: #6BE3C0; color: #3FBD9A; }
  .lobby-board { flex: 1; min-width: 0; }
  .lobby-board .board-strip { padding-top: 0; }
  .lobby-note { color: var(--muted); font-size: 0.78rem; margin-top: 0.4rem; }
  /* the glance line is plain text, never clickable -- "see lobby" below is
     the one explicit affordance for opening the table. A bare text+chevron
     toggle read as inert; a real button (relabel-btn's own look) doesn't. */
  .lobby-glance { color: var(--ink); font-size: 0.82rem; line-height: 1.4; margin-top: 0.35rem; }
  .lobby-summary { margin-top: 0.35rem; }
  .lobby-summary > summary {
    cursor: pointer; list-style: none;
    display: inline-flex; align-items: center; gap: 0.35em;
    min-height: 32px; padding: 0.2rem 0.7rem;
    background: var(--panel); color: var(--muted);
    border: 1px solid var(--line); border-radius: 8px;
    font-size: 0.8rem; font-weight: 600; white-space: nowrap;
  }
  .lobby-summary > summary::-webkit-details-marker { display: none; }
  .lobby-summary > summary:hover { color: var(--accent); border-color: var(--accent); }
  .lobby-summary > summary::before { content: '\25B8'; }
  .lobby-summary[open] > summary::before { content: '\25BE'; }
  .lobby-full { margin-top: 0.35rem; }
  input.offlist { border-color: var(--down); }
  /* augment entry: three chips, and a slot+tier-narrowed icon grid behind them.
     Riot's Set 18 match payload has no augments field, so these three values are
     hand-entered every game -- the grid exists to make that 6 taps instead of
     ~21 interactions against a 250-name datalist. */
  /* the old text-input row keeps the plumbing but must actually stay hidden --
     its own `display: grid` rule outranks the [hidden] attribute */
  .aug-row[hidden] { display: none; }
  .aug-chips { display: flex; gap: 0.4rem; margin: 0.4rem 0 0; }
  .aug-chip { flex: 1 1 0; min-width: 0; min-height: 46px; display: flex; align-items: center;
    gap: 5px; padding: 4px 6px; background: var(--card); color: var(--ink);
    border: 1px solid var(--line); border-radius: 8px; font: inherit; font-size: 0.7rem;
    text-align: left; cursor: pointer; }
  .aug-chip:disabled { opacity: 0.45; cursor: default; }
  .aug-chip.open { border-color: var(--accent); }
  /* A slot that granted an augment stacks the two: the pick, and under it what
     it rolled. Within the slot's own column rather than in a row of its own --
     a lone chip in a flex row stretches the full width, and the offset is what
     says "this came from the pick above it". Full width inside the column, so
     both names render; the cost is one chip of height, on Destiny games only.
     (Half-width side-by-side was tried first and clipped every name to two or
     three letters.) */
  .aug-slot-cell { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
  .aug-slot-cell .aug-chip { flex: 0 0 auto; }
  .aug-chips { align-items: flex-start; }
  .aug-chip.bonus[hidden] { display: none; }
  .aug-chip .bonus-of { color: var(--muted); font-size: 0.7rem; flex: 0 0 auto; }
  .aug-chip img { width: 26px; height: 26px; border-radius: 4px; flex: 0 0 auto; display: block; }
  .aug-chip img.broken { visibility: hidden; }
  .aug-chip .slot { color: var(--muted); font-size: 0.62rem; flex: 0 0 auto; }
  .aug-chip .nm { overflow: hidden; line-height: 1.15; max-height: 2.3em; }
  .aug-chip .nm.empty { color: var(--muted); }
  /* The mistake grid. Bucket rows rather than the six columns the design doc
     drew: measured at the review panel's real width, six columns leave ~95px
     each and 19 of 24 labels wrap on a phone. Rows re-wrap instead of
     shrinking, and with several chips lit the buckets you hit read down the
     left gutter. */
  .tag-grid { display: flex; flex-wrap: wrap; gap: 0.3rem; margin: 0.1rem 0 0; }
  .tag-chip { display: inline-flex; align-items: center; min-height: 38px;
    background: transparent; color: var(--ink); border: 1px solid var(--line);
    border-radius: 999px; padding: 4px 12px; font: inherit; font-size: 0.75rem;
    line-height: 1.2; cursor: pointer; text-align: left; }
  .tag-chip:hover:not(:disabled) { border-color: var(--accent); }
  /* Selection is coloured by direction, not by one accent. The panel is meant
     to read at a glance as "how did this game go", which a single fill colour
     cannot say -- and the point of the positives is that they look different
     from the mistakes. */
  .tag-chip.on { color: var(--accent-ink); }
  .tag-chip.on.good { background: var(--up); border-color: var(--up); }
  .tag-chip.on.bad { background: var(--down); border-color: var(--down); }
  .tag-chip:disabled { opacity: 0.45; cursor: default; }
  /* A textarea, not an input: a one-line box hid everything but the last few
     words, and the whole value of this field is re-reading the sentence you
     wrote. */
  .why-row { display: flex; flex-direction: column; gap: 0.25rem; margin: 0.15rem 0 0; }
  .why-row textarea {
    width: 100%; min-height: 68px; resize: vertical; font: inherit; font-size: 0.85rem;
    line-height: 1.45; color: var(--ink); background: var(--bg);
    border: 1px solid var(--line); border-radius: 8px; padding: 8px 10px;
  }
  .why-row textarea:focus { outline: 2px solid var(--accent); outline-offset: 1px; }
  .why-row textarea:disabled { opacity: 0.45; }
  .aug-picker { margin: 0.4rem 0 0; }
  .aug-tiers[hidden] { display: none; }
  .aug-tiers { display: flex; gap: 0.35rem; margin-bottom: 0.35rem; }
  .aug-tier { background: transparent; color: var(--muted); border: 1px solid var(--line);
    border-radius: 999px; padding: 2px 9px; font: inherit; font-size: 0.72rem; cursor: pointer; }
  .aug-tier.on { color: var(--ink); }
  .aug-tier-n { opacity: 0.65; font-size: 0.64rem; }
  .aug-search { width: 100%; box-sizing: border-box; margin-bottom: 0.35rem; }
  .aug-grid { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 6px;
    max-height: 40vh; overflow-y: auto; }
  .aug-cell { min-height: 64px; display: flex; flex-direction: column; align-items: center;
    gap: 2px; padding: 4px 2px; background: var(--card); border: 1px solid var(--line);
    border-radius: 6px; color: var(--ink); font: inherit; cursor: pointer; }
  .aug-cell img { width: 34px; height: 34px; border-radius: 4px; display: block; }
  .aug-cell img.broken { visibility: hidden; }
  .aug-cell .nm { font-size: 0.52rem; line-height: 1.1; max-height: 2.2em; overflow: hidden;
    text-align: center; word-break: break-word; }
  .aug-cell.on { outline: 2px solid var(--accent); }
  .aug-none { color: var(--muted); font-size: 0.75rem; padding: 8px 0; display: block; }
  /* tier is load-bearing: +/++ variants reuse their base art and 48 PBE
     augments still ship Riot's placeholder, so the border is what separates
     two otherwise identical cells */
  .tS { border-color: var(--tier-s); }
  .tG { border-color: var(--tier-g); }
  .tP { border-color: var(--tier-p); }
  .aug-tier.tS.on { border-color: var(--tier-s); }
  .aug-tier.tG.on { border-color: var(--tier-g); }
  .aug-tier.tP.on { border-color: var(--tier-p); }
  .aug-tier.tX.on { border-color: var(--accent); color: var(--accent); }
  button { font: inherit; cursor: pointer; }
  .comp-btn {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.15rem;
    min-height: 72px;
    padding: 0.45rem 0.35rem;
    background: var(--panel);
    color: var(--ink);
    border: 1px solid var(--line);
    border-radius: 10px;
    box-shadow: var(--shadow);
    text-align: center;
  }
  .comp-btn .comp-name { font-weight: 600; font-size: 0.92rem; line-height: 1.25; }
  .comp-btn .tell { color: var(--muted); font-size: 0.72rem; line-height: 1.3; }
  .comp-btn.selected {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--accent-ink);
  }
  .comp-btn.selected .tell { color: var(--accent-ink); opacity: 0.8; }
  .comp-btn.guess { border-color: var(--hot-ink); border-style: dashed; }
  /* compact tiles: name + signature unit icons, prose lives in the title */
  .comp-btn.compact { min-height: 0; padding: 6px 8px; gap: 0.25rem; }
  .comp-btn .comp-units { display: flex; gap: 2px; justify-content: center; }
  .comp-btn .comp-units .unit-ic { width: 24px; height: 24px; border-radius: 4px; }
  .guess-chip {
    font-size: 0.62rem;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--hot-ink);
  }

  .grid-head { display: flex; align-items: baseline; justify-content: space-between; gap: 0.5rem; }
  .meta-stat {
    color: var(--muted);
    font-size: 0.66rem;
    font-variant-numeric: tabular-nums;
  }
  /* contest read: datatft board-share, all-time vs last 24h. hot = trending
     more contested than its own history, cold = trending less. */
  .play-rate {
    display: block;
    color: var(--muted);
    font-size: 0.62rem;
    font-variant-numeric: tabular-nums;
    margin-top: 1px;
  }
  .play-rate.hot { color: var(--hot-ink); }
  .play-rate.cold { color: var(--accent-ink); }
  .comp-btn.selected .play-rate { color: var(--accent-ink); opacity: 0.85; }
  /* committal items: the slams that lead into this line */
  .commit-row { display: flex; gap: 2px; justify-content: center; margin-top: 1px; }
  .commit-ic { width: 15px; height: 15px; border-radius: 3px; display: block; }
  .commit-note { color: var(--muted); font-size: 0.62rem; font-style: italic; }
  .comp-btn.selected .commit-note { color: var(--accent-ink); opacity: 0.8; }
  /* a variant reads as a child of the line above it, not a sibling */
  .comp-btn.variant { border-left: 2px solid var(--accent); padding-left: 8px; }
  .variant-of { color: var(--accent); font-size: 0.58rem; letter-spacing: 0.04em;
                text-transform: uppercase; }
  .variant-note { color: var(--muted); font-size: 0.62rem; }
  .comp-btn.selected .variant-of, .comp-btn.selected .variant-note { color: var(--accent-ink); }

  .pending-strip {
    background: var(--hot-bg);
    color: var(--hot-ink);
    border: 1px solid var(--hot-ink);
    border-radius: 10px;
    padding: 0.6rem 0.8rem;
    font-size: 0.9rem;
    line-height: 1.4;
  }
  .pending-strip[hidden] { display: none; }
  .pending-strip .pending-head { font-weight: 600; }

  /* Live note (FEEDBACK #23): the in-game entry point and its states. Quiet
     by design — one row, muted, so it never competes with a pending game. */
  .live-strip {
    display: flex; align-items: baseline; gap: 0.6rem; flex-wrap: wrap;
    font-size: 0.85rem; color: var(--muted); line-height: 1.4;
  }
  .live-strip[hidden] { display: none; }
  .live-strip button {
    font: inherit; font-weight: 600; cursor: pointer;
    background: none; color: var(--accent);
    border: 1px solid var(--accent); border-radius: 8px;
    padding: 0.25rem 0.7rem;
  }
  .live-strip button.quiet { border-color: var(--muted); color: var(--muted); font-weight: 400; }
  .live-strip button[draggable="true"] { cursor: grab; }
  .live-strip button[draggable="true"]:active { cursor: grabbing; }
  /* a note is being dragged: every row is a possible home, the hovered one says so */
  body.dragging-note .log-list li:not(.review-slot) { outline: 1px dashed var(--muted); outline-offset: -1px; }
  body.dragging-note .log-list li.drop-target {
    outline: 2px dashed var(--accent); outline-offset: -2px; background: var(--team-a-bg);
  }
  .live-strip button[disabled] { opacity: 0.45; cursor: not-allowed; }

  /* Personal augment stats (views/augments.js): slot rows of genre cells,
     then the every-augment list. Same pills the openers tables use. */
  .augstat-slot { display: flex; gap: 0.6rem; align-items: baseline; margin: 0.35rem 0; }
  .augstat-slot-label { font-weight: 600; min-width: 2.2rem; }
  .augstat-genres { display: flex; flex-wrap: wrap; gap: 0.35rem 1rem; }
  .augstat-genre { white-space: nowrap; }
  .augstat-genre .n { color: var(--muted); margin: 0 0.3rem 0 0.25rem; font-size: 0.8rem; }
  .augstat-genre-name { font-size: 0.85rem; }
  .augstat-row { display: flex; align-items: center; gap: 0.5rem; padding: 0.22rem 0; }
  .augstat-ic { width: 22px; height: 22px; border-radius: 5px; }
  .augstat-ic.broken { visibility: hidden; }
  .augstat-name { min-width: 11rem; font-size: 0.85rem; }
  .augstat-slots { color: var(--muted); font-size: 0.75rem; min-width: 4.5rem; }
  .augstat-places { display: flex; flex-wrap: wrap; gap: 2px; align-items: center; }
  .augstat-more {
    font: inherit; font-size: 0.8rem; cursor: pointer; margin-top: 0.3rem;
    background: none; color: var(--muted); border: 1px solid var(--muted);
    border-radius: 8px; padding: 0.2rem 0.6rem;
  }

  /* The personal explorer (views/explorer.js): dimension rows of count-
     bearing chips, then the result head + annotated cards. */
  .xp-row { display: flex; flex-wrap: wrap; gap: 0.3rem; align-items: center; margin: 0.3rem 0; }
  .xp-dim { color: var(--muted); font-size: 0.75rem; min-width: 4.6rem; text-transform: uppercase; letter-spacing: 0.03em; }
  .xp-chip {
    font: inherit; font-size: 0.78rem; cursor: pointer;
    background: none; color: inherit;
    border: 1px solid var(--rule, var(--muted)); border-radius: 8px;
    padding: 0.14rem 0.45rem;
    display: inline-flex; align-items: center; gap: 0.3rem;
  }
  .xp-chip .n { color: var(--muted); font-size: 0.7rem; }
  .xp-chip.on { border-color: var(--accent); color: var(--accent); font-weight: 600; }
  .xp-chip.on .n { color: var(--accent); }
  .xp-chip.none { opacity: 0.35; cursor: not-allowed; }
  .xp-chip.quiet { border-style: dashed; color: var(--muted); }
  .xp-chip.xp-ic img { width: 18px; height: 18px; border-radius: 4px; display: block; }
  .xp-chip .k { font-size: 0.7rem; font-weight: 700; }
  /* the primary control of the whole block — wide enough to read as one */
  .xp-search { flex: 1 1 22rem; min-width: 14rem; max-width: none; font-size: 0.9rem; padding: 0.4rem 0.7rem; }
  .xp-head { margin: 0.55rem 0 0.4rem; display: flex; flex-wrap: wrap; gap: 0.35rem; align-items: center; line-height: 1.5; }
  .xp-n { font-weight: 700; }
  .xp-base { color: var(--muted); font-size: 0.8rem; }
  .xp-chip .x { color: var(--muted); font-size: 0.7rem; margin-left: 0.15rem; }
  .xp-chip .kind { color: var(--muted); font-size: 0.65rem; text-transform: uppercase; letter-spacing: 0.04em; }
  .xp-sugg, .xp-presets { display: flex; flex-wrap: wrap; gap: 0.3rem; align-items: center; margin: 0.35rem 0; }
  .xp-empty { color: var(--muted); font-size: 0.85rem; line-height: 1.5; padding: 0.8rem 0; }
  .xp-updated { color: var(--muted); font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.04em; margin: 0.6rem 0 0.35rem; }
  .xp-tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(7.5rem, 1fr)); gap: 0.45rem; margin-bottom: 0.55rem; }
  .xp-tile { border: 1px solid var(--rule, var(--muted)); border-radius: 10px; padding: 0.45rem 0.6rem; display: flex; flex-direction: column; gap: 0.1rem; }
  .xp-tile-v { font-size: 1.15rem; font-weight: 700; }
  .xp-tile-v small { font-weight: 400; font-size: 0.68rem; color: var(--muted); margin-left: 0.25rem; }
  .xp-tile-l { font-size: 0.72rem; color: var(--muted); text-transform: uppercase; letter-spacing: 0.04em; }
  .xp-tile-o { font-size: 0.72rem; color: var(--muted); }
  .xp-hist { max-width: 24rem; }
  .xp-tabs { display: flex; flex-wrap: wrap; gap: 0.25rem; margin: 0.5rem 0 0.4rem; border-bottom: 1px solid var(--rule, var(--muted)); padding-bottom: 0.35rem; }
  .xp-tab {
    font: inherit; font-size: 0.78rem; cursor: pointer;
    background: none; color: var(--muted); border: none; border-radius: 6px;
    padding: 0.2rem 0.55rem;
  }
  .xp-tab.on { color: var(--accent); font-weight: 700; background: var(--accent-soft, rgba(127,127,127,0.12)); }
  .xp-agg { display: flex; align-items: center; gap: 0.55rem; padding: 0.22rem 0.2rem; cursor: pointer; border-radius: 6px; }
  .xp-agg:hover { background: rgba(127, 127, 127, 0.08); }
  .xp-agg.held { opacity: 0.55; cursor: default; }
  .xp-agg-ic { display: inline-flex; }
  /* art inside a chip (search suggestion or an active filter) */
  .xp-chip-ic { display: inline-flex; gap: 1px; }
  .xp-chip-ic .xp-ic-img { width: 16px; height: 16px; border-radius: 3px; }
  .xp-place .xp-chip.on { background: var(--accent-soft, rgba(127,127,127,0.14)); }
  .xp-ic-img { width: 22px; height: 22px; border-radius: 4px; display: block; }
  .xp-ic-img.champ { border-radius: 5px; }
  .xp-ic-img.broken { visibility: hidden; }
  .xp-agg-l { min-width: 9rem; font-size: 0.85rem; }
  .xp-agg .n { color: var(--muted); font-size: 0.75rem; min-width: 1.4rem; }
  .xp-agg-p { display: flex; flex-wrap: wrap; gap: 2px; align-items: center; }
  /* a note the pull is asking about — the question outranks a plain resume */
  .live-strip button.ask { background: var(--hot-bg); color: var(--hot-ink); border-color: var(--hot-ink); }
  .live-claim-row { flex-wrap: wrap; }
  .live-dot { color: #d33; animation: livePulse 1.6s ease-in-out infinite; display: inline-block; }
  @keyframes livePulse { 50% { opacity: 0.25; } }
  /* The receipt that a note just opened: without it, "next game" and a
     mis-tap look identical. Restarted from JS by re-adding the class. */
  .live-banner.flash { animation: liveBannerIn 0.7s ease-out; }
  @keyframes liveBannerIn {
    0% { transform: scale(1.03); box-shadow: 0 0 0 6px var(--accent); opacity: 0.4; }
    100% { transform: scale(1); box-shadow: 0 0 0 0 transparent; opacity: 1; }
  }
  .live-head-row button[disabled] { opacity: 0.45; cursor: not-allowed; }
  /* What the last tap did — "parked the 11:16pm note … undo". Without it a
     parked note behind a fresh blank sheet reads as a wipe. */
  .live-receipt {
    margin: 0.35rem 0 0;
    padding: 0.3rem 0.55rem;
    border-left: 3px solid var(--hot-ink);
    background: var(--hot-bg);
    color: var(--hot-ink);
    font-size: 0.8rem;
    line-height: 1.4;
    border-radius: 0 6px 6px 0;
  }
  .live-receipt button {
    font: inherit; font-size: 0.75rem; cursor: pointer; margin-left: 0.4rem;
    background: none; color: var(--hot-ink); border: 1px solid var(--hot-ink);
    border-radius: 6px; padding: 0.05rem 0.45rem;
  }
  /* The whole panel wears live mode: hot banner up top, accent outline
     around everything, so any scrolled-into-view slice of the form still
     says which state it is in. Outline treatment matches .classifying. */
  .live-banner {
    background: var(--hot-bg);
    color: var(--hot-ink);
    border: 1px solid var(--hot-ink);
    border-radius: 8px;
    padding: 0.45rem 0.7rem;
    font-weight: 600;
    font-size: 0.85rem;
    letter-spacing: 0.02em;
    line-height: 1.4;
  }
  #reviewBlock.live {
    outline: 2px solid var(--accent);
    outline-offset: 8px;
    border-radius: 10px;
  }
  .live-head-note { font-size: 0.8rem; color: var(--muted); line-height: 1.4; margin: 0.3rem 0 0; }
  .live-head-row { display: flex; gap: 0.5rem; margin-top: 0.45rem; }
  .live-head-row button {
    font: inherit; font-size: 0.8rem; cursor: pointer;
    background: none; color: var(--muted);
    border: 1px solid var(--muted); border-radius: 8px;
    padding: 0.2rem 0.6rem;
  }
  .pending-help { font-size: 0.8rem; opacity: 0.9; margin-top: 0.5rem; line-height: 1.4; }
  /* The mismatch warning: a tapped comp the board does not back. Amber and
     inline rather than a dialog -- it must slow a mash down without making an
     honest correction feel accused. */
  .comp-warn {
    font-size: 0.8rem; line-height: 1.4; margin: 0.5rem 0;
    padding: 0.5rem 0.7rem; border-left: 3px solid #b58900;
    background: rgba(181, 137, 0, 0.12); border-radius: 4px;
  }
  .comp-warn b { font-weight: 600; }

  .chart-title.active { color: var(--accent); }
  .grid.classifying, #offPoolBtn.classifying {
    outline: 2px solid var(--accent);
    outline-offset: 4px;
    border-radius: 10px;
  }
  .grid.classifying { padding: 2px; }

  #compPicker[hidden] { display: none; }
  /* relabel affordances: quiet until used — a wrong label is worth fixing,
     not worth competing with the pending strip for attention. */
  .relabel-cancel, .relabel-btn {
    min-height: 32px;
    padding: 0.2rem 0.7rem;
    background: var(--panel);
    color: var(--muted);
    border: 1px solid var(--line);
    border-radius: 8px;
    font-size: 0.8rem;
    font-weight: 600;
    white-space: nowrap;
  }
  .relabel-cancel[hidden] { display: none; }
  .relabel-btn { margin-left: 0.5rem; vertical-align: middle; }
  .relabel-btn:hover, .relabel-cancel:hover { color: var(--accent); border-color: var(--accent); }

  .offpool-row { display: flex; justify-content: center; }
  .offpool-row[hidden] { display: none; }
  #offPoolBtn {
    min-height: 44px;
    min-width: 8rem;
    padding: 0.5rem 1.5rem;
    background: var(--panel);
    color: var(--muted);
    border: 1px solid var(--line);
    border-radius: 10px;
    font-weight: 600;
  }
  #offPoolBtn.selected {
    background: var(--accent);
    border: 1px solid var(--accent);
    color: var(--accent-ink);
  }

  .placement-row {
    display: flex;
    align-items: center;
    gap: 0.5rem;
  }
  .placement-row[hidden] { display: none; }
  .placement-row .row-label { color: var(--muted); font-size: 0.9rem; }
  .placement-btns {
    flex: 1;
    display: grid;
    grid-template-columns: repeat(8, 1fr);
    gap: 3px;
  }
  .place-btn {
    min-height: 44px;
    padding: 0;
    background: var(--panel);
    color: var(--ink);
    border: 1px solid var(--line);
    border-radius: 8px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
  }
  .place-btn:active { background: var(--accent); color: var(--accent-ink); }

  .review-detail {
    background: var(--panel);
    border: 1px solid var(--line);
    border-radius: 8px;
    padding: 0.4rem 0.6rem;
    color: var(--muted);
    font-size: 0.85rem;
    font-variant-numeric: tabular-nums;
  }
  .review-detail[hidden] { display: none; }
  .review-detail b { color: var(--ink); font-weight: 600; }
  .aug-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; }
  .aug-row label {
    display: flex;
    align-items: center;
    gap: 0.35rem;
    color: var(--muted);
    font-size: 0.85rem;
    min-width: 0;
  }
  .aug-row input {
    flex: 1;
    min-width: 0;
    min-height: 44px;
    font: inherit;
    font-size: 0.85rem;
    color: var(--ink);
    background: var(--panel);
    border: 1px solid var(--line);
    border-radius: 8px;
    padding: 0.3rem 0.45rem;
  }
  .aug-row input:disabled, .note-row input:disabled {
    border-style: dashed;
    background: var(--bg);
    opacity: 0.8;
  }

  .note-row { display: flex; align-items: center; gap: 0.5rem; }
  .duo-row { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; }
  .duo-row .note-row { min-width: 0; }
  .save-row { display: flex; align-items: center; justify-content: flex-end; gap: 0.75rem; }
  .save-status { color: var(--muted); font-size: 0.82rem; }
  .save-status.ok { color: var(--up); }
  .save-status.err { color: var(--down); font-weight: 600; }
  /* A first pull is paced to stay inside the Riot budget and takes over a
     minute, which is long enough that static text reads as a hung app. Drawn
     with a border rather than an image so it inherits the text colour and
     needs nothing the CSP would have to allow. */
  .save-status.busy::before {
    content: '';
    display: inline-block;
    width: 0.75em;
    height: 0.75em;
    margin-right: 0.45em;
    vertical-align: -0.08em;
    border: 2px solid currentColor;
    border-right-color: transparent;
    border-radius: 50%;
    animation: status-spin 0.7s linear infinite;
  }
  @keyframes status-spin { to { transform: rotate(360deg); } }
  @media (prefers-reduced-motion: reduce) {
    /* Still a marker that something is running, just not a moving one. */
    .save-status.busy::before { animation: none; opacity: 0.5; }
  }
  #saveBtn {
    min-height: 40px;
    padding: 0.35rem 1.4rem;
    background: var(--accent);
    color: var(--accent-ink);
    border: none;
    border-radius: 8px;
    font-weight: 600;
  }
  #saveBtn:disabled { opacity: 0.4; }
  .note-row .row-label { color: var(--muted); font-size: 0.9rem; }
  .note-row input {
    flex: 1;
    min-width: 0;
    min-height: 44px;
    font: inherit;
    color: var(--ink);
    background: var(--panel);
    border: 1px solid var(--line);
    border-radius: 8px;
    padding: 0.4rem 0.6rem;
  }
  .note-row input::placeholder, .aug-row input::placeholder { color: var(--muted); opacity: 0.75; }

  hr { border: none; border-top: 1px solid var(--line); margin: 0.25rem 0; width: 100%; }

  .stats-line {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    gap: 0.35rem 1rem;
    color: var(--muted);
  }
  /* each stat is one unbreakable unit -- without this a 375px viewport wraps
     "5-2" into "5-" and "2" on separate lines, which reads as a different value */
  .stats-line > span { white-space: nowrap; }
  .stats-line b { color: var(--ink); font-weight: 600; }
  /* names what the two numbers are counting whenever a comp filter is on */
  .stats-scope { flex-basis: 100%; color: var(--accent); font-size: 0.85rem; }

  table { width: 100%; border-collapse: collapse; }
  th {
    text-align: left;
    font-size: 0.72rem;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--muted);
    font-weight: 600;
    padding: 0.3rem 0.5rem;
    border-bottom: 1px solid var(--line);
  }
  td {
    padding: 0.6rem 0.4rem;
    border-bottom: 1px solid var(--line);
    font-variant-numeric: tabular-nums;
  }
  th.num, td.num { text-align: right; }
  td.num.prov { color: var(--muted); }
  .prov-tag {
    font-size: 0.6rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--muted);
    margin-left: 0.4em;
    vertical-align: 0.08em;
  }
  .prov-note { color: var(--muted); font-size: 0.75rem; margin: 0.4rem 0 0; }
  /* ---- the selected comp's popover ----
     A card pinned at the tapped marker. Absolute inside #compTableWrap, which
     is the positioning context; views/scatter.js sets left/top from the
     marker's rendered rect and pins the caret to the marker's y.
     Sized to its content and capped, not stretched: the whole point of the
     card is that four numbers do not need a half-page sheet.
     Rows on a three-column grid, not a flex chip row: the comp count and the
     overall count are meant to be compared straight down the column, and
     chips that wrap wherever they land cannot be. */
  #compTableWrap { position: relative; }
  .comp-pop {
    position: absolute; z-index: 4;
    width: max-content; max-width: min(21rem, calc(100% - 1.5rem));
    background: var(--panel); border: 1px solid var(--accent); border-radius: 10px;
    box-shadow: 0 8px 26px rgba(0, 0, 0, 0.42);
    padding: 0.55rem 0.65rem;
  }
  /* a small square rotated into a diamond, half of it tucked under the card's
     edge so only the pointing half shows */
  .cp-caret {
    position: absolute; width: 9px; height: 9px; transform: translateY(-50%) rotate(45deg);
    background: var(--panel); border: 1px solid var(--accent);
  }
  .comp-pop.right .cp-caret { left: -5px; border-right: 0; border-top: 0; }
  .comp-pop.flip .cp-caret { right: -5px; border-left: 0; border-bottom: 0; }
  .cp-head { display: flex; align-items: baseline; justify-content: space-between;
    gap: 0.9rem; margin-bottom: 0.4rem; }
  .cp-name { font-weight: 600; font-size: 0.86rem; }
  .cp-meta { font-size: 0.68rem; color: var(--muted); font-variant-numeric: tabular-nums;
    white-space: nowrap; }
  .cp-rows { display: grid; grid-template-columns: 1fr auto auto; gap: 0.18rem 0.7rem;
    align-items: baseline; }
  .cp-row { display: contents; }
  .cp-tag { font-size: 0.78rem; }
  .cp-tag.good { color: var(--up); }
  .cp-k, .cp-o { font-size: 0.72rem; font-variant-numeric: tabular-nums;
    white-space: nowrap; text-align: right; }
  .cp-k { color: var(--ink); }
  .cp-o { color: var(--muted); }
  /* the two column headings do the job the paragraph under the rows used to */
  .cp-hrow .cp-k, .cp-hrow .cp-o { font-size: 0.62rem; color: var(--muted);
    text-transform: uppercase; letter-spacing: 0.04em; }
  .cp-empty { color: var(--muted); font-size: 0.8rem; }
  /* ---- the mistakes readout ---- */
  /* .mist-comp / .mist-row / .mist-head / .mist-name / .mist-tags / .mist-tag /
     .mist-empty retired with the by-comp block (2026-08-31). .mist-meta stays:
     the trend card's "now" label and the pullout head both use it. */
  .mist-meta { font-size: 0.68rem; color: var(--muted); font-variant-numeric: tabular-nums;
    white-space: nowrap; }
  .trend-card { border: 1px solid var(--line); border-radius: 9px; padding: 0.55rem 0.6rem;
    background: var(--panel); display: flex; flex-direction: column; gap: 0.3rem; }
  .trend-card.filtered { border-color: var(--accent); }
  .trend-top { display: flex; align-items: baseline; justify-content: space-between; gap: 0.5rem; }
  .trend-now { font-size: 1.35rem; font-weight: 600; font-variant-numeric: tabular-nums;
    line-height: 1; }
  .trend-now small { font-size: 0.6rem; font-weight: 400; color: var(--muted); margin-left: 0.4em; }
  .trend-card svg { display: block; width: 100%; height: auto; }
  /* the verdict line is a local drill (see activateLocalDrill()) -- tap it to
     see the first-half/second-half games it's comparing. */
  .trend-foot { display: flex; align-items: baseline; justify-content: space-between; gap: 0.5rem;
    font-size: 0.68rem; color: var(--muted); font-variant-numeric: tabular-nums;
    cursor: pointer; border-radius: 6px; padding: 2px 4px; margin: -2px -4px; }
  .trend-foot:hover { background: var(--bg); }
  .trend-card .gridline { stroke: var(--line); stroke-width: 0.8; }
  .trend-card .band { fill: var(--accent); opacity: 0.15; }
  .trend-card .tline { fill: none; stroke: var(--accent); stroke-width: 2;
    stroke-linejoin: round; stroke-linecap: round; }
  .trend-card .tdot { fill: var(--accent); }
  .mist-bar { height: 7px; border-radius: 3px; background: var(--accent); opacity: 0.75;
    min-width: 1px; }
  .mist-bar.good { background: var(--up); }
  .mist-bar.bad { background: var(--down); }
  /* one row per tag: name, a compact rolling-share line, current rate. Tap
     to see the games behind it -- same drill-down every other block on this
     tab uses, because a line going up means nothing without the games. */
  .tag-trend-row { display: flex; align-items: center; gap: 0.6rem; padding: 0.35rem 0.4rem;
    border: 1px solid var(--line); border-radius: 8px; background: var(--panel);
    cursor: pointer; margin-bottom: 0.3rem; }
  .tag-trend-row.filtered { border-color: var(--accent); background: var(--team-a-bg); }
  .tag-trend-row:hover { border-color: var(--accent); }
  .tag-trend-name { flex: 0 0 auto; width: 6.5rem; font-size: 0.78rem; font-weight: 600;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .tag-trend-name.good { color: var(--up); }
  .tag-spark { flex: 1 1 auto; min-width: 0; display: block; height: 32px; }
  .tag-spark .tline { fill: none; stroke: var(--accent); stroke-width: 1.6;
    stroke-linejoin: round; stroke-linecap: round; }
  .tag-spark .band { fill: var(--accent); opacity: 0.15; }
  .tag-spark .tdot { fill: var(--accent); }
  .tag-trend-now { flex: 0 0 auto; width: 3.6rem; text-align: right; font-size: 0.78rem;
    font-variant-numeric: tabular-nums; }
  .tag-trend-now small { color: var(--muted); font-size: 0.62rem; }
  .better { color: var(--up); }
  .worse { color: var(--down); }
  /* the pooled comparison line under the stats summary */
  .group-line { color: var(--muted); font-size: 0.8rem; margin: 0.1rem 0 0.9rem; }
  .group-line b { color: var(--ink); font-weight: 600; }
  /* admin view */
  .admin-head { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; }
  .admin-refresh {
    background: none;
    border: 1px solid var(--line);
    color: var(--muted);
    border-radius: 4px;
    padding: 0.2rem 0.6rem;
    font-size: 0.75rem;
    cursor: pointer;
  }
  .admin-refresh:hover { color: var(--ink); }
  .admin-status { color: var(--muted); font-size: 0.8rem; margin: 0.5rem 0; }
  /* view-as (support): deliberately the loudest thing on the page. Fixed to
     the bottom so it survives every tab, every scroll position, and the auth
     gate itself — the one screen you can land on while impersonating a user
     the server refuses, and the one place you most need the way out. */
  .viewas-bar { position: fixed; left: 0; right: 0; bottom: 0; z-index: 9999;
    display: flex; gap: 0.7rem; align-items: center; justify-content: center; flex-wrap: wrap;
    background: #b45309; color: #fff; font-size: 0.8rem; font-weight: 600;
    padding: 0.35rem 0.7rem; }
  .viewas-bar b { font-weight: 700; }
  .viewas-bar .viewas-note { font-weight: 500; opacity: 0.85; }
  .viewas-bar button { font: inherit; padding: 0.1rem 0.6rem; border: 0; border-radius: 3px;
    background: #7c2d12; color: #fff; cursor: pointer; }
  body.viewing-as { padding-bottom: 2.2rem; }
  .viewas-list { display: flex; flex-wrap: wrap; gap: 0.4rem; margin: 0.5rem 0 0; }
  .viewas-list button { font: inherit; font-size: 0.78rem; padding: 0.15rem 0.55rem;
    border: 1px solid var(--line, #333); border-radius: 3px; background: transparent;
    color: var(--fg, #eee); cursor: pointer; }
  /* Six columns do not fit a phone. Rather than dropping one — every column
     here answers a different question — the table scrolls inside its own box
     so the page itself never moves sideways. */
  .admin-tablewrap { overflow-x: auto; }
  /* group tab: member tagging line + feed rows that reuse .log-list */
  #groupMembers { flex-wrap: wrap; }
  .member-tag { color: var(--muted); font-size: 0.8rem; margin-left: 0.35rem; }
  .feed-name { font-weight: 600; margin-right: 0.4rem; white-space: nowrap; }
  .feed-augs { color: var(--accent, #a78bfa); font-size: 0.78rem; margin-left: 0.4rem; }
  .group-mine { color: var(--muted); }
  /* display:flex outranks the UA's [hidden] rule — without these three the
     "start a group" pitch, the invite row and the compare block stay on
     screen for people the JS has already hidden them from. */
  .group-start[hidden], .group-invite[hidden], .group-leadrow[hidden],
  .stats-line[hidden], .compare-block[hidden], .viewas-bar[hidden] { display: none; }
  .group-start { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; margin: 0.6rem 0; font-size: 0.85rem; color: var(--muted); }
  .group-start input, .group-leadrow input { font: inherit; background: transparent; color: var(--ink); border: 1px solid var(--line); border-radius: 6px; padding: 0.25rem 0.5rem; }
  .group-invite { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; margin-bottom: 0.5rem; font-size: 0.8rem; color: var(--muted); }
  .group-invite code { background: var(--panel, #1a1a2e); padding: 0.2rem 0.4rem; border-radius: 4px; word-break: break-all; }
  .group-leadrow { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; margin-top: 0.5rem; }
  /* The settings cluster sits above the group's numbers, so it needs a
     boundary the eye can stop at; without one the leave button reads as
     part of the totals line under it. */
  .group-admin { border-bottom: 1px solid var(--line); padding-bottom: 0.7rem; margin-bottom: 0.8rem; }
  .group-admin:empty { display: none; }
  #groupNewFields { display: inline-flex; align-items: center; gap: 0.5rem; }
  #groupNewFields[hidden] { display: none; }
  #groupNewFields input { font: inherit; background: transparent; color: var(--ink); border: 1px solid var(--line); border-radius: 6px; padding: 0.25rem 0.5rem; }
  .group-leadrow button.danger { color: #f87171; border-color: #f87171; }
  /* P3.3 reps per line ("who to ask"): one row per comp, names with counts.
     A routing surface — the whole row is a button into the same per-comp
     modal the cards open, because the modal is where the games live. */
  .reps-line { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.15rem 0.45rem; width: 100%; text-align: left; background: transparent; border: 0; border-bottom: 1px solid var(--line); color: var(--ink); font: inherit; padding: 0.35rem 0.15rem; cursor: pointer; }
  .reps-line:hover { background: var(--panel, #1a1a2e); }
  .reps-comp { font-weight: 600; margin-right: 0.3rem; }
  .reps-holder { color: var(--muted); font-size: 0.85rem; white-space: nowrap; }
  .reps-holder b { color: var(--ink); font-weight: 600; }
  .reps-holder.reps-me, .reps-holder.reps-me b { color: var(--accent, #a78bfa); }
  .reps-dot { color: var(--muted); }
  /* P3.3 augment ledger: <details> per augment, the games inside reuse
     .log-list. [hidden] must beat the summary's display for the filter. */
  .ledger-aug { border-bottom: 1px solid var(--line); padding: 0.3rem 0.15rem; }
  .ledger-aug[hidden] { display: none; }
  .ledger-aug summary { cursor: pointer; }
  .ledger-name { font-weight: 600; }
  .group-switch { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.6rem; font-size: 0.8rem; color: var(--muted); }
  .group-switch[hidden] { display: none; }
  /* FEEDBACK #27: sections fold at their title, app-wide. The caret is
     ::before so renderers that rewrite the title's textContent can't wipe
     it. Not scoped to .chart-block — #statsOpenings folds the same way. */
  .chart-title.sect-toggle { cursor: pointer; user-select: none; }
  .chart-title.sect-toggle::before {
    content: '▾'; display: inline-block; width: 1.05em; color: var(--muted);
  }
  .sect-collapsed .chart-title.sect-toggle::before { content: '▸'; }
  .sect-collapsed > *:not(.admin-head):not(.sect-toggle) { display: none; }
  .sect-collapsed .admin-head .env-toggle { display: none; }
  #groupPick { font: inherit; background: transparent; color: var(--ink); border: 1px solid var(--line); border-radius: 6px; padding: 0.2rem 0.4rem; }
  .admin-member-row button { margin-left: 0.4rem; }
  .admin-tablewrap table { min-width: 30rem; }
  /* the compliance cell: two rates, the second recessive because the rewatch
     is always the minority pass and a low number there is not a problem */
  .admin-rate-2 { color: var(--muted); font-size: 0.78rem; margin-left: 0.35em; }
  /* two lines: the Riot ID, and their ranked standing under it when there is
     one. Ellipsis-truncating a Riot ID hides the tag, which is the half that
     disambiguates two friends with the same name. */
  td.admin-name { line-height: 1.35; }
  .admin-sub { display: block; color: var(--muted); font-size: 0.72rem; }
  .admin-idle { color: var(--muted); }
  /* group member table: crest + "Diamond II · 45 LP" on one line. The crest
     rides the text baseline; a fixed box keeps ten rows of different tiers
     from wobbling the column. */
  td.rank-cell { white-space: nowrap; }
  .rank-crest { width: 1.15rem; height: 1.15rem; vertical-align: -0.28rem; margin-right: 0.3rem; }
  .rank-lp { color: var(--muted); font-size: 0.78rem; margin-left: 0.15rem; }
  /* games belonging to a login with several linked accounts that carry no
     account of their own. Recessive on purpose: a remainder to be cleared,
     not a player to be compared against the rows above it. */
  .admin-unsplit td { color: var(--muted); font-style: italic; }
  /* nonzero issues in the usage table — same red as a failed save */
  td.warn-cell { color: var(--down); font-weight: 600; }
  /* The count IS the control. Styled as the number it replaced rather than as
     a button, so the column still scans as a column of figures; the underline
     and the pointer are what say it opens. */
  .issue-toggle {
    background: none;
    border: 0;
    padding: 0;
    font: inherit;
    color: inherit;
    font-weight: 600;
    cursor: pointer;
    text-decoration: underline dotted;
    text-underline-offset: 3px;
  }
  .issue-toggle:hover { text-decoration-style: solid; }
  .issue-row > td { padding-top: 0; }
  .issue-list { list-style: none; margin: 0 0 0.2rem; padding: 0; }
  .issue-list li {
    display: flex;
    flex-wrap: wrap;
    gap: 0 0.6rem;
    padding: 0.22rem 0;
    border-top: 1px solid var(--line);
    font-size: 0.78rem;
  }
  .issue-when { color: var(--muted); font-variant-numeric: tabular-nums; white-space: nowrap; }
  /* When a failure repeated, how far back it goes. Recessive: the message and
     the count are the row, this is the footnote. */
  .issue-first {
    color: var(--muted);
    opacity: 0.75;
    font-size: 0.7rem;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
  }
  .issue-what { color: var(--down); }
  /* The message as the server stored it. Monospace because it is a quoted
     machine string, and it is the half worth reading. */
  .issue-why {
    flex: 1 1 18rem;
    min-width: 0;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 0.72rem;
    color: var(--muted);
    overflow-wrap: anywhere;
  }

  /* ---- admin usage dashboard (views/usage.js) ---- */
  .usage-line { margin-top: 0; }
  /* absorbed-by-design line: present but recessive — it must never read as
     a second error list */
  .usage-handled { margin-top: -0.25rem; opacity: 0.75; font-size: 0.75rem; }
  .usage-subtitle { margin-top: 1.1rem; }
  .usage-chartwrap { max-width: 46rem; }
  .usage-activity .usage-bar { fill: var(--accent); }
  .usage-activity .usage-hit { fill: transparent; }
  .usage-activity .usage-day:hover .usage-bar { opacity: 0.75; }
  .usage-activity .usage-day:hover .usage-hit { fill: var(--line); opacity: 0.25; }
  .adopt-list { max-width: 46rem; }
  .adopt-row { display: flex; align-items: center; gap: 0.7rem; margin: 0.3rem 0; }
  .adopt-name {
    flex: 0 0 7.5rem;
    text-align: right;
    font-size: 0.82rem;
    color: var(--ink);
    white-space: nowrap;
  }
  .adopt-track {
    flex: 1 1 auto;
    position: relative;
    height: 10px;
    border-radius: 5px;
    background: var(--bg);
    border: 1px solid var(--line);
    overflow: hidden;
  }
  /* two segments, same hue: solid = used in the last 30 days, faint = has
     used it at some point — the gap between them is the lapsed users */
  .adopt-ever { position: absolute; inset: 0 auto 0 0; background: var(--accent); opacity: 0.22; border-radius: 4px; }
  .adopt-fill { position: absolute; inset: 0 auto 0 0; background: var(--accent); border-radius: 4px; }
  .adopt-count {
    flex: 0 0 12rem;
    font-size: 0.75rem;
    color: var(--muted);
    font-variant-numeric: tabular-nums;
  }
  .adopt-zero { font-style: italic; }
  /* matrix cells: alive / lapsed / never — three visibly distinct weights */
  td.feat-on { background: var(--team-a-bg); color: var(--team-a-ink); font-weight: 600; }
  td.feat-lapsed { color: var(--muted); }
  td.feat-never { color: var(--muted); opacity: 0.45; }
  .perf-details > summary {
    cursor: pointer;
    list-style: none;
  }
  .perf-details > summary::before { content: '▸ '; }
  .perf-details[open] > summary::before { content: '▾ '; }
  .perf-details > summary::-webkit-details-marker { display: none; }
  .watch-card {
    border: 1px solid var(--line);
    border-radius: 8px;
    padding: 0.6rem 0.75rem;
    margin-bottom: 0.5rem;
  }
  .watch-head { display: flex; align-items: baseline; justify-content: space-between; gap: 0.5rem; }
  .watch-name { font-weight: 600; }
  .watch-pivot { font-size: 0.72rem; color: var(--accent); white-space: nowrap; }
  .watch-units { font-size: 0.82rem; color: var(--ink); margin-top: 0.15rem; }
  .watch-why { font-size: 0.82rem; color: var(--muted); margin-top: 0.35rem; line-height: 1.4; }
  .watch-src { font-size: 0.7rem; color: var(--muted); margin-top: 0.35rem; opacity: 0.8; }
  tbody tr { cursor: pointer; }
  tbody tr:not(.filtered):hover td { background: var(--panel); }
  tr:last-child td { border-bottom: none; }
  tr.filtered td { background: var(--team-a-bg); color: var(--team-a-ink); }
  tr.filtered td:first-child { border-left: 3px solid var(--accent); }

  .chart-block[hidden] { display: none; }
  .chart-title {
    font-size: 0.72rem;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--muted);
    font-weight: 600;
    margin-bottom: 0.35rem;
  }
  .readout-group { margin-top: 1.4rem; }
  .readout-group:first-child { margin-top: 0; }
  /* Reroll runs deeper than the other sections, so it splits AP/AD inside
     itself. Indented and lighter so it reads as a cut within the section
     rather than a section of its own — same intent as the dossier site. */
  .readout-subgroup {
    margin: 0.9rem 0 0 0.6rem;
    font-size: 0.68rem;
    opacity: 0.75;
  }
  .readout-link {
    color: var(--accent);
    text-transform: none;
    letter-spacing: 0;
    font-weight: 600;
    margin-left: 0.4rem;
  }

  .readout-search {
    font: inherit;
    color: var(--ink);
    background: var(--panel);
    border: 1px solid var(--line);
    border-radius: 8px;
    padding: 0.55rem 0.75rem;
    width: 100%;
  }
  .readout-search::placeholder { color: var(--muted); }
  .comp-search { margin: 0.4rem 0 0.6rem; }
  /* the small filter at the end of a stats metric-row — chips first, search
     pushed to the right, sized like a chip rather than a form field */
  .stat-search { margin-left: auto; width: auto; max-width: 11rem;
    padding: 2px 10px; font-size: 0.75rem; border-radius: 999px; }
  .log-search { display: block; width: auto; max-width: 18rem;
    margin: 0.35rem 0 0.5rem; padding: 0.35rem 0.7rem; font-size: 0.8rem; }
  .vod-row { display: flex; align-items: center; gap: 0.4rem; margin-top: 0.35rem; }
  .vod-row input { flex: 1; min-width: 0; }
  .vod-link { font-size: 0.78rem; text-decoration: none; color: var(--accent);
    border: 1px solid var(--line); border-radius: 999px; padding: 1px 8px; white-space: nowrap; }
  /* the pull filed this label from a rough board match — unconfirmed */
  .guessed-mark { color: var(--hot-ink, var(--muted)); font-weight: 700; margin-left: 3px; cursor: help; }

  .readout-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
    gap: 0.6rem;
  }
  .readout-card {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    border: 1px solid var(--line);
    border-radius: 10px;
    background: var(--panel);
    padding: 0.6rem 0.7rem;
    text-decoration: none;
    color: inherit;
    min-width: 0;
  }
  .readout-card:hover { box-shadow: var(--shadow); border-color: var(--accent); }
  .readout-icon {
    width: 36px;
    height: 36px;
    border-radius: 7px;
    border: 1px solid var(--line);
    flex: none;
  }
  .readout-icon.placeholder {
    background: var(--bg);
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--muted);
    font-size: 0.85rem;
    font-weight: 700;
  }
  .readout-card-body { min-width: 0; flex: 1; }
  .readout-name {
    font-weight: 600;
    font-size: 0.92rem;
    line-height: 1.25;
  }
  .readout-stat { font-size: 0.8rem; margin-top: 0.1rem; }
  .readout-stat .n { font-weight: 700; font-variant-numeric: tabular-nums; }
  .readout-stat .n.up { color: var(--up); }
  .readout-stat .n.down { color: var(--down); }
  .readout-stat .l { color: var(--muted); }
  .readout-updated { color: var(--muted); font-size: 0.72rem; margin-top: 0.1rem; }
  .readout-card[hidden], .readout-grid[hidden], .readout-group[hidden],
  .readout-subgroup[hidden] { display: none; }
  .chart-block svg { width: 100%; height: auto; display: block; }
  .chart-block .axis-text { fill: var(--muted); font-size: 10px; }
  .chart-block .grid-line { stroke: var(--line); stroke-width: 1; }
  /* emphasis form: top-4 in accent, bottom-4 de-emphasized. Placement position
     (bars/dots at 1–8) + the axis labels carry identity; color reinforces. */
  .chart-block .bar-top4 { fill: var(--accent); }
  .chart-block .bar-bot4 { fill: var(--muted); opacity: 0.4; }
  .spread-dot.d-top4 { fill: var(--accent); }
  .spread-dot.d-bot4 { fill: var(--muted); opacity: 0.5; }
  /* comp cards: same layout language as .readout-card (icon, name, a stat
     row, hover lift), clickable to the per-comp modal instead of a link. */
  .comp-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); gap: 0.6rem; }
  .comp-card {
    display: flex; align-items: center; gap: 0.6rem;
    border: 1px solid var(--line); border-radius: 10px; background: var(--panel);
    padding: 0.6rem 0.7rem; text-align: left; font: inherit; color: inherit; cursor: pointer;
  }
  .comp-card:hover, .comp-card:focus-visible { box-shadow: var(--shadow); border-color: var(--accent); }
  .comp-card .unit-ic, .comp-card .unit-ic-gap { width: 36px; height: 36px; border-radius: 7px; }
  .comp-card-body { min-width: 0; flex: 1; }
  .comp-card-name { font-weight: 600; font-size: 0.92rem; line-height: 1.25;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .comp-card-stats { display: flex; flex-wrap: wrap; gap: 0.1rem 0.55rem; font-size: 0.78rem;
    color: var(--muted); margin-top: 0.15rem; }
  .comp-card-stats b { color: var(--ink); font-variant-numeric: tabular-nums; font-weight: 600; }
  .comp-card.prov .comp-card-stats .avgv { color: var(--muted); font-weight: 400; }

  /* augment type — horizontal bars, longer = better placement */

  /* A paired row above the columns. flex, not grid: a hidden child takes no
     space, so one chart alone fills the width rather than sitting in half of
     it beside a gap. */
  /* profile tiles: the record as eight big numbers, tactics.tools card
     shape, filling the block. Row-major into four columns, so each COLUMN
     is a metric family: games/avg place, top4/rate, wins/rate, level/death
     round. */
  .profile-tiles { display: grid; grid-template-columns: repeat(4, 1fr); gap: 0.6rem; }
  .chart-title .unit-ic { width: 18px; height: 18px; border-radius: 4px;
    vertical-align: -4px; margin-right: 0.25rem; }
  .profile-tile {
    display: flex; flex-direction: column; align-items: center; gap: 0.1rem;
    border: 1px solid var(--line); border-radius: 10px; background: var(--panel);
    padding: 0.85rem 0.4rem;
  }
  .profile-tile b { font-size: 1.5rem; line-height: 1.1; font-variant-numeric: tabular-nums; }
  .profile-tile span { color: var(--muted); font-size: 0.78rem; }
  .lp-accounts { margin-bottom: 0.5rem; }
  .lp-chip-n { color: var(--muted); font-size: 0.72rem; }
  /* The climb spans the full stats row (2026-08-31 redesign): the chart now
     carries a marker per ranked game, and legible comp icons need the width.
     The block-level 460px svg cap is lifted for this chart only. */
  #lpBlock { flex-basis: 100%; }
  #lpChart svg { max-width: none; }
  /* per-game markers: comp squares in a rail UNDER the plot (never on the
     line -- an icon on the line swallowed exactly the small dips it was
     meant to explain). Placement badge inside the square corner (top-4
     accent, bottom-4 muted, same convention as the distribution bars), 1sts
     get a heavier border; wide windows collapse the rail to bare dots. */
  .lp-sq { fill: var(--panel); stroke: var(--line); stroke-width: 1.5; }
  .lp-sq.up { stroke: var(--up); }
  .lp-g.first .lp-sq { stroke-width: 3; }
  .lp-conn { stroke: var(--line); stroke-width: 1; }
  .lp-badge.up { fill: var(--up); }
  .lp-badge.down { fill: var(--muted); }
  .lp-badge-tx { fill: var(--panel); font-size: 8px; font-weight: 700; }
  .lp-g-dot.up { fill: var(--up); }
  .lp-g-dot.down { fill: var(--muted); opacity: 0.6; }

  .stats-row { display: flex; flex-wrap: wrap; gap: 1.5rem 2.5rem; }
  .stats-row > * { flex: 1 1 20rem; min-width: 0; }

  /* a tag with no games behind it: still listed, not a drilldown */
  #mistakeList tr.tag-unused { opacity: 0.45; }
  #mistakeList tr.tag-unused td { cursor: default; }

  /* the two stats columns -- see the grid rules in the desktop media query */

  /* ---- the games rail ---------------------------------------------------
     Two states, one element. Docked: a block at the bottom of the tab, every
     game in the era, capped so it does not add a hundred cards to the page
     length. Drawer: slid in against the edge OPPOSITE the block that was
     tapped, fixed, so opening it reflows nothing. */
  /* offsetHeight excludes the rail's own margin, so the spacer carries it */
  #statsGamesSpacer { margin-top: 1.5rem; }
  #statsGamesSpacer[hidden] { display: none; }
  .stats-games {
    margin-top: 1.5rem;
    border-top: 1px solid var(--line);
    padding-top: 0.9rem;
    max-height: 60vh;
    overflow-y: auto;
  }
  /* left/width are measured off the opposite column by sizeDrawer() -- a
     viewport-edge drawer sliced the column instead of covering it (73% of it
     at 1470px, and 370px of drawer hanging over blank margin at 1920px).
     `top` clears the sticky tab bar and the account button rather than
     painting over them: at z-index 40 the drawer was swallowing clicks on the
     tabs and the era switch. */
  .stats-games.rail-drawer {
    position: fixed;
    top: var(--drawer-top, 3rem);
    bottom: 0;
    margin-top: 0;
    padding: 0.9rem 1rem;
    border: 1px solid var(--accent);
    border-radius: 10px 10px 0 0;
    border-bottom: 0;
    max-height: none;
    z-index: 22;
    background: var(--panel);
    box-shadow: 0 0 40px rgba(0, 0, 0, 0.45);
  }
  @keyframes rail-in-right { from { transform: translateX(100%); } to { transform: none; } }
  @keyframes rail-in-left  { from { transform: translateX(-100%); } to { transform: none; } }
  .stats-games.rail-drawer.from-right.rail-sliding { animation: rail-in-right 220ms ease-out; }
  .stats-games.rail-drawer.from-left.rail-sliding  { animation: rail-in-left 220ms ease-out; }
  /* declared here rather than in the page's other prefers-reduced-motion
     block: these are (0,4,0) selectors, and the (0,3,0) override written up
     there lost to them regardless of the media query, leaving the rule dead */
  @media (prefers-reduced-motion: reduce) {
    .stats-games.rail-drawer.from-right.rail-sliding,
    .stats-games.rail-drawer.from-left.rail-sliding { animation: none; }
  }
  /* the title stays put while the card list scrolls under it */
  .stats-games .drill-title { position: sticky; top: 0; background: var(--bg); z-index: 1; }
  .stats-games.rail-drawer .drill-title { background: var(--panel); }

  /* comp drill-down: read-only annotated game cards */
  .drill-title { display: flex; align-items: baseline; justify-content: space-between; gap: 0.75rem; }
  .drill-close {
    flex: 0 0 auto; background: none; border: 1px solid var(--line); color: var(--muted);
    border-radius: 6px; padding: 0.05rem 0.45rem; font-size: 0.8rem; cursor: pointer;
  }
  .drill-close:hover { color: var(--ink); border-color: var(--accent); }
  .ac-card {
    border: 1px solid var(--line);
    border-radius: 8px;
    padding: 0.6rem 0.75rem;
    margin-bottom: 0.6rem;
  }
  .ac-head { display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; margin-bottom: 0.4rem; }
  .ac-when { color: var(--muted); font-size: 0.85rem; }
  .ac-enc {
    font-size: 0.75rem; color: var(--hot-ink); background: var(--hot-bg);
    border-radius: 4px; padding: 0.05rem 0.4rem;
  }
  .ac-augs { display: flex; flex-wrap: wrap; gap: 0.3rem 0.8rem; margin-top: 0.5rem; }
  .ac-aug { font-size: 0.85rem; display: flex; align-items: center; gap: 4px; }
  .ac-aug-ic { width: 20px; height: 20px; border-radius: 4px; border: 1px solid var(--line);
    flex: 0 0 auto; display: block; }
  /* these sit after .ac-aug-ic on purpose: its `border` shorthand is declared
     later in the sheet than .tS/.tG/.tP and would otherwise reset the colour */
  .ac-aug-ic.tS { border-color: var(--tier-s); }
  .ac-aug-ic.tG { border-color: var(--tier-g); }
  .ac-aug-ic.tP { border-color: var(--tier-p); }
  .ac-aug-ic.broken { visibility: hidden; }
  .ac-aug-nm { min-width: 0; }
  .ac-slot { color: var(--muted); font-size: 0.72rem; font-weight: 700; }
  .ac-cat {
    font-size: 0.65rem; text-transform: uppercase; letter-spacing: 0.04em;
    color: var(--accent); border: 1px solid var(--line); border-radius: 4px; padding: 0 0.25rem;
  }
  .ac-start, .ac-row { margin-top: 0.4rem; font-size: 0.85rem; color: var(--ink); display: flex; align-items: center; gap: 0.4rem; flex-wrap: wrap; }
  .ac-k { color: var(--muted); font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.04em; }
  .chart-block .series { stroke: var(--accent); stroke-width: 2; fill: none; }
  .chart-block .series-dot { fill: var(--accent); }
  .chart-block .series-fill { fill: var(--accent); opacity: 0.15; }
  /* by-comp scatter: placement x games-played, one marker per comp */
  .chart-block .scat-pt { cursor: pointer; }
  .chart-block .scat-pt:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
  /* keyboard-focus parity for the rows/rects onActivate() wires up: openings
     table rows, tag-trend rows, the mistake-trend verdict line. Not scoped under .chart-block -- #statsOpenings
     (openings table's container) doesn't carry that class, unlike the others. */
  tr[data-open-key]:focus-visible,
  .tag-trend-row:focus-visible,
  #mistakeList tr[data-tag-key]:focus-visible,
  #trendFootBtn:focus-visible {
    outline: 2px solid var(--accent); outline-offset: -2px;
  }
  .chart-block .scat-halo { fill: var(--panel); stroke: var(--line); stroke-width: 1.5; opacity: 0.9; }
  .chart-block .scat-halo.up { stroke: var(--up); }
  .chart-block .scat-halo.down { stroke: var(--down); }
  .chart-block .scat-pt.filtered .scat-halo { stroke-width: 3; }
  /* The line ring. Sits outside the performance halo, thin and slightly
     transparent so it reads as a category band rather than competing with
     the up/down stroke for attention. A comp with no line-bearing games
     gets no ring at all rather than a fifth colour. */
  .chart-block .scat-line-ring { fill: none; stroke-width: 2; opacity: 0.75; }
  .chart-block .scat-line-ring.level { stroke: var(--line-level); }
  .chart-block .scat-line-ring.rr1 { stroke: var(--line-rr1); }
  .chart-block .scat-line-ring.rr2 { stroke: var(--line-rr2); }
  .chart-block .scat-line-ring.rr3 { stroke: var(--line-rr3); }
  .chart-block .scat-legend { display: flex; flex-wrap: wrap; gap: 6px 14px; margin: 8px 0 0; }
  .chart-block .scat-leg { display: inline-flex; align-items: center; gap: 6px;
    font-size: 12px; color: var(--muted); }
  .chart-block .scat-leg .swatch { width: 9px; height: 9px; border-radius: 50%;
    border: 2px solid currentColor; box-sizing: border-box; }
  .chart-block .scat-leg.level .swatch { color: var(--line-level); }
  .chart-block .scat-leg.rr1 .swatch { color: var(--line-rr1); }
  .chart-block .scat-leg.rr2 .swatch { color: var(--line-rr2); }
  .chart-block .scat-leg.rr3 .swatch { color: var(--line-rr3); }
  .chart-block .scat-leg b { color: var(--ink); font-weight: 600; }

  .chart-block .scat-fallback { fill: var(--line); }
  .chart-block .scat-fallback-tx { fill: var(--muted); font-size: 10px; font-weight: 600; }
  .chart-block .scat-breakeven { stroke: var(--muted); stroke-width: 1; stroke-dasharray: 3 3; }
  .chart-block .scat-ylabel { font-size: 9px; }
  /* which elite rate the unit table compares against -- one column, two questions */
  .metric-row { display: flex; gap: 0.4rem; margin: 0.35rem 0 0.1rem; }
  /* [hidden] loses to display:flex — without this the chips outlive their table */
  .metric-row[hidden] { display: none; }
  .metric-chip { background: transparent; color: var(--muted); border: 1px solid var(--line);
    border-radius: 999px; padding: 2px 10px; font: inherit; font-size: 0.75rem; cursor: pointer; }
  .metric-chip.on { color: var(--accent); border-color: var(--accent); }
  .chart-legend { display: flex; gap: 1rem; font-size: 0.78rem; color: var(--muted); margin-top: 0.25rem; }
  .chart-legend .swatch {
    display: inline-block; width: 0.9em; height: 3px; border-radius: 2px;
    vertical-align: middle; margin-right: 0.35em;
  }

  .start-row { display: flex; align-items: center; flex-wrap: wrap; gap: 0.5rem; }
  .start-row .row-label { color: var(--muted); font-size: 0.9rem; }
  .start-comps { display: flex; flex-wrap: wrap; gap: 4px; }
  .start-comp {
    position: relative;
    width: 34px;
    height: 34px;
    padding: 0;
    border: 1px solid var(--line);
    border-radius: 6px;
    background: var(--bg);
  }
  .start-comp img { width: 100%; height: 100%; border-radius: 5px; display: block; filter: grayscale(0.85) brightness(0.55); }
  .start-comp.on {
    border-color: var(--accent);
    box-shadow: 0 0 0 2px var(--accent);
    background: var(--team-a-bg);
  }
  .start-comp.on img { filter: brightness(1.12) saturate(1.15); }
  .start-comp:disabled { cursor: default; opacity: 0.4; }

  /* bright start icons + gold shown inline on each log row */
  .start-badge { display: inline-flex; align-items: center; gap: 2px; }
  .start-ic {
    width: 20px; height: 20px; border-radius: 4px; display: block;
    border: 1px solid var(--accent);
    filter: brightness(1.1) saturate(1.15);
  }
  .start-gold-badge {
    font-size: 0.72rem; font-weight: 700; color: var(--hot-ink);
    background: var(--hot-bg); border-radius: 4px; padding: 0 0.3rem;
    font-variant-numeric: tabular-nums;
  }

  .rev-badge {
    display: inline-flex; align-items: center; justify-content: center;
    width: 1.2rem; font-weight: 700; font-size: 0.85rem;
  }
  .rev-badge.done { color: var(--up); }
  .rev-badge.todo { color: var(--muted); opacity: 0.6; }
  .start-comp .cc {
    position: absolute;
    right: -4px; top: -5px;
    min-width: 15px;
    padding: 0 3px;
    border-radius: 99px;
    background: var(--accent);
    color: var(--accent-ink);
    font-size: 0.62rem;
    font-weight: 700;
    line-height: 15px;
  }
  .start-gold { display: inline-flex; align-items: center; gap: 0.3rem; color: var(--muted); font-size: 0.85rem; }
  .start-gold input {
    width: 3.4rem;
    min-height: 40px;
    font: inherit;
    color: var(--ink);
    background: var(--panel);
    border: 1px solid var(--line);
    border-radius: 8px;
    padding: 0.3rem 0.4rem;
    text-align: center;
    font-variant-numeric: tabular-nums;
  }
  .start-gold input:disabled { border-style: dashed; opacity: 0.6; }

  .place-pill {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 2.4rem;
    padding: 0.12rem 0.4rem;
    border-radius: 6px;
    font-weight: 700;
    font-size: 0.82rem;
    font-variant-numeric: tabular-nums;
    background: var(--line);
    color: var(--ink);
  }
  .place-pill.win { background: var(--accent); color: var(--accent-ink); }
  .place-pill.top4 { background: var(--team-a-bg); color: var(--team-a-ink); }
  .place-pill.bot4 { background: var(--panel); color: var(--muted); border: 1px solid var(--line); }

  .divergence-list { list-style: none; margin: 0.6rem 0 0; padding: 0; }
  .divergence-list li {
    display: flex; align-items: baseline; flex-wrap: wrap; gap: 0.35rem 0.5rem;
    padding: 0.35rem 0 0.45rem; border-bottom: 1px solid var(--line);
    font-size: 0.9rem; cursor: pointer;
  }
  .divergence-list li:last-child { border-bottom: none; }
  .divergence-list li:hover { background: var(--panel); }
  .divergence-date { margin-left: auto; color: var(--muted); font-size: 0.8rem; }

  .log-list { list-style: none; margin: 0; padding: 0; }
  .log-list li {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 0.35rem 0.6rem;
    padding: 0.35rem 0 0.45rem;
    border-bottom: 1px solid var(--line);
    font-size: 0.95rem;
  }
  .log-list li:last-child { border-bottom: none; }
  .log-list li { cursor: pointer; }
  .log-list li:not(.note-target):not(.review-slot):hover { background: var(--panel); }
  .log-list li.note-target {
    background: var(--team-a-bg);
    border-left: 3px solid var(--accent);
    padding-left: 0.4rem;
  }
  .log-list li.note-target .comp, .log-list li.note-target .when,
  .log-list li.note-target .place, .log-list li.note-target .game-note { color: var(--team-a-ink); }
  .log-list li.review-slot {
    display: block;
    cursor: auto;
    padding: 0.6rem 0 0.8rem 0.4rem;
    border-left: 3px solid var(--accent);
  }
  .log-list .when { color: var(--muted); font-size: 0.85rem; white-space: nowrap; font-variant-numeric: tabular-nums; }
  /* field notes — mini posts, collapsed to titles */
  #notesList { display: flex; flex-direction: column; gap: 0.6rem; max-width: 640px; }
  .note-post {
    background: var(--panel);
    border: 1px solid var(--line);
    border-radius: 10px;
    padding: 0.15rem 0.9rem;
  }
  .note-post summary {
    cursor: pointer;
    padding: 0.6rem 0;
    font-weight: 600;
    color: var(--ink);
    list-style: none;
    display: flex;
    justify-content: space-between;
    gap: 0.75rem;
    align-items: baseline;
  }
  .note-post summary::-webkit-details-marker { display: none; }
  .note-post .note-date { color: var(--muted); font-weight: 400; font-size: 0.8rem; white-space: nowrap; }
  .note-post .note-body { padding: 0 0 0.8rem; color: var(--ink); font-size: 0.9rem; line-height: 1.5; }
  .note-post .note-body p { margin: 0 0 0.6rem; }
  .note-post .note-body ul { margin: 0 0 0.6rem; padding-left: 1.1rem; }
  .note-post .note-body li { margin-bottom: 0.2rem; }

  .log-list .comp { font-weight: 600; white-space: nowrap; }
  .log-list .place { font-weight: 700; font-variant-numeric: tabular-nums; }
  /* One line, clipped. Every game now carries a seeded lobby line, and at
     full length that wraps to two or three rows and pushes the boards apart --
     the log is the surface you scan, and the whole note is one tap away in the
     panel (and in the row's tooltip). */
  .log-list .game-note {
    color: var(--muted); font-size: 0.85rem; min-width: 0;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }

  /* linked riot accounts */
  #accountsList { display: flex; flex-direction: column; gap: 0.5rem; margin-bottom: 0.5rem; }
  .acct-row {
    display: flex;
    align-items: center;
    /* wraps so .acct-pull can take a full line of its own beneath the name
       and buttons rather than squeezing them */
    flex-wrap: wrap;
    gap: 0.6rem;
    padding: 0.5rem 0.7rem;
    background: var(--panel);
    border: 1px solid var(--line);
    border-radius: 8px;
  }
  .acct-row .acct-name { font-weight: 600; color: var(--ink); }
  .acct-row .acct-region {
    color: var(--muted);
    font-size: 0.8rem;
    text-transform: uppercase;
    letter-spacing: 0.03em;
  }
  .acct-row .acct-actions { margin-left: auto; display: flex; gap: 0.4rem; }
  .acct-row button {
    background: none;
    border: 1px solid var(--line);
    border-radius: 6px;
    padding: 0.25rem 0.55rem;
    color: var(--muted);
    font-size: 0.8rem;
  }
  .account-form {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.4rem;
    margin-top: 0.4rem;
  }
  .account-form input, .account-form select {
    min-height: 44px;
    font: inherit;
    font-size: 0.85rem;
    color: var(--ink);
    background: var(--panel);
    border: 1px solid var(--line);
    border-radius: 8px;
    padding: 0.3rem 0.45rem;
  }
  #acctGameName { flex: 2; min-width: 100px; }
  #acctTagLine { flex: 1; min-width: 70px; }
  .acct-hash { color: var(--muted); }
  #acctAddBtn {
    min-height: 40px;
    padding: 0.35rem 1rem;
    background: var(--accent);
    color: var(--accent-ink);
    border: none;
    border-radius: 8px;
    font-weight: 600;
  }
  #acctAddBtn:disabled { opacity: 0.4; }
  .acct-limit-note { color: var(--muted); font-size: 0.82rem; margin-top: 0.4rem; }
  /* Full row of its own under the account it belongs to — the row above is a
     flex line and this must not squeeze the name or the buttons. */
  .acct-pull {
    flex-basis: 100%;
    color: var(--muted);
    font-size: 0.75rem;
    margin-top: 0.25rem;
  }
  .acct-pull.err { color: var(--down); }

  /* Standing counterpart to the first-run copy: that's shown once, this stays
     in view for someone writing a self-critical note months into the beta. */
  .group-visible-note {
    color: var(--muted);
    font-size: 0.72rem;
    letter-spacing: 0.04em;
    margin-top: 0.35rem;
  }

  .first-run {
    background: var(--panel);
    border: 1px solid var(--line);
    border-radius: 12px;
    padding: 0.9rem 1rem;
    margin-bottom: 1.1rem;
  }
  .first-run[hidden] { display: none; }
  .first-run-title { font-weight: 600; color: var(--ink); margin-bottom: 0.5rem; }
  .first-run p { color: var(--muted); font-size: 0.88rem; line-height: 1.5; margin: 0 0 0.5rem; }
  .first-run p:last-child { margin-bottom: 0; }
  .first-run-steps { color: var(--muted); font-size: 0.88rem; line-height: 1.5;
    margin: 0 0 0.5rem; padding-left: 1.4em; }
  .first-run-steps li { margin-bottom: 0.25rem; }
  .first-run-steps b { color: var(--ink); font-weight: 600; }
  .first-run-sub { font-size: 0.82rem !important; }

  .refresh-row { display: flex; align-items: center; gap: 0.6rem; margin-bottom: 1rem; }
  .refresh-row[hidden] { display: none; }
  #refreshBtn {
    min-height: 40px;
    padding: 0.35rem 0.9rem;
    background: transparent;
    color: var(--ink);
    border: 1px solid var(--line);
    border-radius: 8px;
    font: inherit;
    font-size: 0.85rem;
  }
  #refreshBtn:disabled { opacity: 0.4; }

  /* screenshot capture zone (views/capture-ui.js) */
  .cap-zone { margin: 0.45rem 0 0; padding: 0.35rem 0.5rem; border: 1px dashed var(--line, #3a4552);
    border-radius: 8px; font-size: 0.85rem; }
  .cap-zone.cap-hot { border-color: #5aa7ff; }
  .cap-btn { font: inherit; cursor: pointer; background: none; border: 0; padding: 0;
    color: inherit; opacity: 0.85; }
  .cap-btn:hover { opacity: 1; text-decoration: underline; }
  .cap-tip { opacity: 0.55; font-size: 0.78rem; margin-top: 2px; }
  .cap-status { margin-left: 0.5rem; }
  .cap-ok { color: #4a9f63; }
  .cap-ask { color: #b8892e; }
  .cap-rev, .cap-bad { opacity: 0.75; }
  .cap-wait { opacity: 0.6; }
  .cap-choice { font: inherit; font-size: 0.82rem; cursor: pointer; margin: 0 2px;
    border: 1px solid currentColor; border-radius: 6px; background: none; color: inherit;
    padding: 1px 8px; }
  .cap-guess { opacity: 0.75; border-style: dashed; }

/* ---- 4v4 league tab ------------------------------------------------------
   Two series only, us and them. The app's own --team-a/--team-b pair fails
   palette validation as a categorical pair (normal-vision deltaE 12.9, under
   the 15 floor; team-b's chroma reads gray), so "us" keeps the accent purple
   and "them" takes a warm orange that clears every check in both themes.
   Both are labelled in text as well, so identity is never colour alone. */
:root {
  --f4-us: #6D4FC4;
  --f4-them: #C77A2B;
  --f4-track: rgba(38, 32, 56, .07);
}
@media (prefers-color-scheme: dark) {
  :root {
    --f4-us: #8368CF;
    --f4-them: #C68238;
    --f4-track: rgba(255, 255, 255, .07);
  }
}

.f4-stats { display: flex; flex-wrap: wrap; gap: 22px; margin: 4px 0 14px; }
.f4-stat { font-size: 12px; color: var(--muted); display: flex; align-items: baseline; gap: 5px; }
.f4-stat b { font-size: 22px; color: var(--ink); font-variant-numeric: tabular-nums; }

.f4-picker { display: inline-flex; align-items: center; gap: 8px; font-size: 12px;
  color: var(--muted); margin-bottom: 10px; }
.f4-picker select { font: inherit; font-size: 14px; color: var(--ink); background: var(--panel);
  border: 1px solid var(--line); border-radius: 6px; padding: 6px 10px; }

/* Coverage: a meter, not a sentence. Full bar = a 1-in-20 habit would show. */
.f4-cov { display: flex; align-items: center; gap: 10px; font-size: 11px;
  color: var(--muted); margin: 0 0 16px; max-width: 620px; }
.f4-covlabel { text-transform: uppercase; letter-spacing: .06em; min-width: 62px; }
.f4-covfill { display: block; height: 100%; border-radius: 3px; background: var(--up); }
.f4-covfill.f4-covthin { background: var(--down); }
.f4-covval { font-variant-numeric: tabular-nums; white-space: nowrap; }
.f4-covnote { opacity: .6; white-space: nowrap; }

.f4-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 12px; margin-bottom: 22px; }
.f4-card { background: var(--panel); border: 1px solid var(--line); border-radius: 10px;
  padding: 12px 14px; box-shadow: var(--shadow); }
.f4-cardhead { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.f4-name { font-weight: 600; font-size: 14px; }
.f4-card .f4-rank { font-size: 11px; color: var(--muted); margin: 2px 0 8px; }
.f4-places { width: 96px; height: 26px; flex: none; }
.f4-bar-win { fill: var(--accent); }
.f4-bar-top4 { fill: var(--team-a-line); opacity: .55; }
.f4-bar-bot4 { fill: var(--muted); opacity: .35; }

/* A pocket pick is marked on its own bar: a dot beside the label, and the
   bar itself in the warning colour. Listing it separately said the same line
   twice, once as a chip and once as the bar underneath. */
.f4-pocketdot { color: var(--down); margin-right: 4px; font-size: 9px; vertical-align: 1px; }
.f4-pocketrow .f4-barfill { background: var(--down); }
.f4-pocketrow .f4-barlabel { font-weight: 600; }

/* Bars: length is the count, so the eye ranks without reading. */
.f4-barrow { display: flex; align-items: center; gap: 8px; padding: 2px 0; font-size: 12px; }
.f4-barlabel { flex: 0 0 auto; min-width: 108px; max-width: 150px; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.f4-bartrack { flex: 1 1 auto; height: 7px; border-radius: 4px; background: var(--f4-track); overflow: hidden; }
.f4-barfill { display: block; height: 100%; border-radius: 4px; background: var(--f4-us); }
.f4-barval { flex: 0 0 auto; min-width: 52px; text-align: right;
  font-variant-numeric: tabular-nums; color: var(--muted); }
.f4-barval i { font-style: normal; opacity: .65; }

/* Contest against value. Reference lines at 4.5 (break-even in this format)
   and half the lobby; a filled dot is a line we already play. */
.f4-scatterwrap { margin: 4px 0 18px; }
.f4-scatter { width: 100%; height: auto; max-width: 660px; display: block; }
.f4-grid { stroke: var(--f4-track); stroke-width: 1; }
.f4-ref { stroke: var(--muted); stroke-width: 1; stroke-dasharray: 3 3; opacity: .45; }
.f4-axis { fill: var(--muted); font-size: 10px; font-family: inherit; }
.f4-axistitle { fill: var(--muted); font-size: 10px; text-transform: uppercase; letter-spacing: .06em; }
.f4-dot { fill: none; stroke: var(--muted); stroke-width: 1.5; opacity: .7; }
.f4-dot-ours { fill: var(--f4-us); stroke: var(--f4-us); opacity: 1; }
.f4-dotlabel { fill: var(--ink); font-size: 11px; font-weight: 600; }
.f4-scatterkey { display: flex; flex-wrap: wrap; align-items: center; gap: 14px;
  font-size: 11px; color: var(--muted); margin-top: 2px; }
.f4-key { width: 9px; height: 9px; border-radius: 50%; display: inline-block; margin-right: 5px; }
.f4-key-ours { background: var(--f4-us); }
.f4-key-not { background: transparent; border: 1.5px solid var(--muted); }
.f4-scatternote { opacity: .75; }

.f4-standrow { display: flex; align-items: center; gap: 8px; padding: 2px 0; font-size: 12px; }
.f4-standrow.f4-us { font-weight: 600; }
.f4-pos { min-width: 16px; color: var(--muted); font-variant-numeric: tabular-nums; }

.f4-more { margin-top: 14px; border-top: 1px solid var(--line); padding-top: 8px; }
.f4-more summary { cursor: pointer; font-size: 12px; text-transform: uppercase;
  letter-spacing: .06em; color: var(--muted); }
.f4-more summary:hover { color: var(--ink); }
.f4-meta { display: flex; flex-wrap: wrap; gap: 26px; margin-top: 10px; }
.f4-metablock { flex: 1 1 300px; min-width: 280px; }
.f4-none { font-size: 12px; color: var(--muted); opacity: .8; }

.f4-series { border-left: 2px solid var(--line); padding: 2px 0 2px 10px; margin: 10px 0 0; }
.f4-series-head { display: flex; flex-wrap: wrap; gap: 10px; align-items: baseline; font-size: 12px; }
.f4-date { color: var(--muted); font-variant-numeric: tabular-nums; }
.f4-won { color: var(--up); }
.f4-lp { color: var(--muted); }
.f4-scrim { opacity: .5; }
.f4-scrim .f4-flag { opacity: 1; }
.f4-games { list-style: none; margin: 5px 0 0; padding: 0; }
.f4-game { display: flex; gap: 8px; align-items: center; padding: 2px 0; font-size: 11px; }
.f4-score { font-weight: 700; min-width: 3.4em; font-variant-numeric: tabular-nums; }
.f4-chips { display: flex; gap: 3px; }
.f4-chip { min-width: 17px; text-align: center; padding: 1px 4px; border-radius: 3px;
  background: var(--f4-track); font-variant-numeric: tabular-nums; }
.f4-chip-us { outline: 1.5px solid var(--f4-us); }

.f4-flag { display: inline-flex; gap: 3px; margin-left: auto; }
.f4-flagbtn { font: inherit; font-size: 10px; text-transform: uppercase; letter-spacing: .05em;
  padding: 2px 7px; border-radius: 4px; cursor: pointer; background: transparent;
  color: inherit; opacity: .45; border: 1px solid var(--line); }
.f4-flagbtn:hover:not(:disabled) { opacity: .85; }
.f4-flagbtn:disabled { cursor: progress; opacity: .3; }
.f4-flagon { opacity: 1; font-weight: 700; border-color: currentColor; }
.f4-flag-scrim .f4-flagon { color: var(--down); }

  /* Round-by-round panel (beta, views/rounds.js). Reuses the lobby
     disclosure's button look; the table scrolls on its own at phone width. */
  .rounds-panel { margin-top: 0.35rem; }
  .rounds-beta { font-size: 0.62rem; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase;
    color: var(--accent); border: 1px solid var(--accent); border-radius: 999px; padding: 0 5px; }
  .rounds-body { margin-top: 0.4rem; display: flex; flex-direction: column; gap: 0.35rem; }
  .rounds-k { color: var(--muted); font-size: 0.72rem; font-weight: 600; margin-right: 0.3rem; }
  .rounds-contest { font-size: 0.8rem; line-height: 1.5; display: flex; align-items: center; flex-wrap: wrap; gap: 0.25rem; }
  .rounds-quiet { color: var(--muted); }
  .rounds-bad { color: var(--down); font-weight: 600; }
  .rounds-good { color: var(--up); font-weight: 600; }
  .rounds-ic { width: 20px; height: 20px; border-radius: 4px; vertical-align: middle; }
  .rounds-ic.broken { visibility: hidden; }
  .rounds-scroll { overflow-x: auto; }
  .rounds-table { border-collapse: collapse; width: 100%; font-size: 0.76rem; font-variant-numeric: tabular-nums; }
  .rounds-table th { color: var(--muted); font-weight: 600; text-align: left; padding: 3px 6px; border-bottom: 1px solid var(--line); white-space: nowrap; }
  .rounds-table td { padding: 3px 6px; border-bottom: 1px solid var(--line); white-space: nowrap; vertical-align: middle; }
  .rounds-table .n { text-align: right; }
  .rounds-table tr.s4 td { background: color-mix(in srgb, var(--accent) 7%, transparent); }
  .rounds-bar { display: inline-block; height: 6px; border-radius: 2px; background: var(--accent); margin-right: 4px; vertical-align: middle; }
  .rounds-bought { white-space: normal; }
  .rounds-buy { display: inline-flex; align-items: center; margin-right: 2px; }
  .rounds-x { font-size: 0.68rem; color: var(--muted); margin-left: 1px; }
  .rounds-board { display: flex; align-items: center; flex-wrap: wrap; gap: 0.3rem; }
  .rounds-board .board-strip { padding-top: 0; }
  .rounds-opener { font-size: 0.8rem; display: flex; align-items: center; flex-wrap: wrap; gap: 0.4rem; }
  /* The panel sits inside .log-list's <li> review slot, whose li rules (flex,
     row borders, padding, pointer, hover fill) would otherwise apply here —
     scoped to #roundsBox so they lose. */
  #roundsBox .rounds-read { margin: 0; padding-left: 1.1rem; display: flex; flex-direction: column; gap: 0.3rem;
    font-size: 0.84rem; line-height: 1.45; list-style: disc; }
  #roundsBox .rounds-read > li { display: list-item; border: 0; padding: 0; color: var(--ink); cursor: default;
    background: none; font-size: inherit; }
  #roundsBox .rounds-read > li:hover { background: none; }
  #roundsBox .rounds-read > li::marker { color: var(--muted); }
  #roundsBox .rounds-read > li.rounds-read-bad::marker { color: var(--down); }
  #roundsBox .rounds-read > li.rounds-read-good::marker { color: var(--up); }
  #roundsBox .rounds-read > li.rounds-read-take { list-style: none; margin: 0.2rem 0 0 -1.1rem; padding: 0.45rem 0.65rem;
    border-radius: 6px; background: color-mix(in srgb, var(--accent) 10%, transparent); }
  .rounds-detail > summary { cursor: pointer; color: var(--muted); font-size: 0.76rem; font-weight: 600; }
  .rounds-detail[open] > summary { margin-bottom: 0.35rem; }
  .rounds-detail { display: flex; flex-direction: column; gap: 0.35rem; }
