/* PickDev's own styles. Colors and geometry come from PickWeb's css/tokens.css (linked first in
   index.html) so the two apps can't drift apart on palette; everything structural is local.

   Deliberately NOT reusing PickWeb's base.css/components.css/screens.css: those are built around
   the player app's 480px phone-shaped shell, and an admin tool showing a dozen games with four
   controls each wants a wider, denser layout. It also avoids inheriting rules that depend on SVG
   filters defined inline in PickWeb's index.html. */

*,
*::before,
*::after {
  box-sizing: border-box;
}

/* Must come before any display rule that would otherwise win — the topbar starts with [hidden] and
   is flex once revealed, and `display: flex` beats `hidden` without this. */
[hidden] {
  display: none !important;
}

html,
body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--text-on-dark);
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  font-size: 16px;
  -webkit-text-size-adjust: 100%;
}

#app {
  max-width: 900px;
  margin: 0 auto;
  padding: 0 12px env(safe-area-inset-bottom) 12px;
}

.muted {
  color: #9a9a9a;
  font-size: 0.85rem;
  font-weight: 400;
}

/* ---------- top bar ---------- */

#topbar {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  padding: 14px 4px 12px;
  border-bottom: 1px solid var(--divider, #333);
  position: sticky;
  top: 0;
  background: var(--bg);
  z-index: 10;
}

.topbar-title {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin-right: auto;
}

.topbar-title strong {
  font-size: 1.1rem;
  letter-spacing: 0.02em;
}

/* The fifth tab (Logs, 2026-08-18) is what pushed this past a 390px phone: five tabs at the old
   16px horizontal padding measured 399px inside a 390px viewport, so the whole PAGE scrolled
   sideways — every screen in the app, not just the new one, since this bar is in the topbar.

   Fixed by letting the bar itself scroll rather than by shrinking the text to fit, because the
   next tab added would only break it again. `flex-shrink: 0` on the buttons keeps the labels
   readable instead of squeezing them, and the tighter padding below means all five fit without
   scrolling at 390px anyway — the overflow rule is the headroom, not the mechanism. */
#tabs {
  display: flex;
  gap: 4px;
  background: #2a2a2a;
  border-radius: 999px;
  padding: 3px;
  overflow-x: auto;
  /* No visible scrollbar eating 15px of a phone's width for a bar that rarely needs to scroll. */
  scrollbar-width: none;
  max-width: 100%;
}

#tabs::-webkit-scrollbar {
  display: none;
}

#tabs button {
  appearance: none;
  border: 0;
  background: transparent;
  color: #b8b8b8;
  font: inherit;
  font-size: 0.9rem;
  padding: 7px 12px;
  border-radius: 999px;
  cursor: pointer;
  flex-shrink: 0;
  white-space: nowrap;
}

#tabs button[aria-selected='true'] {
  background: var(--accent, #00bfff);
  color: #04212c;
  font-weight: 600;
}

.linkish {
  appearance: none;
  border: 0;
  background: transparent;
  color: var(--accent, #00bfff);
  font: inherit;
  font-size: 0.85rem;
  padding: 6px 2px;
  cursor: pointer;
  text-decoration: underline;
}

/* ---------- generic ---------- */

.loading,
.empty,
.error {
  padding: 28px 4px;
  color: #9a9a9a;
}

.error {
  color: var(--fail, #ff5f5f);
}

.btn {
  appearance: none;
  font: inherit;
  font-size: 0.9rem;
  padding: 9px 14px;
  border-radius: 10px;
  border: 1px solid #454545;
  background: #2e2e2e;
  color: var(--text-on-dark);
  cursor: pointer;
  white-space: nowrap;
}

.btn:hover:not(:disabled) {
  border-color: #666;
}

.btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

/* The alerts panel at the top of the Logs screen (0.12.41). Framed like a card rather than
   left loose above the filters, because it is a SETTING sitting on a screen that is otherwise
   entirely a read-only view — the border is what stops "Turn on alerts" reading as another
   filter control. The amber left edge matches the app icon and is the only place this app uses
   that colour, so the one thing on the Logs screen that changes state is also the one thing
   with a colour of its own. */
.alerts-panel {
  border: 1px solid #3a3a3a;
  border-left: 3px solid #f5a623;
  border-radius: 8px;
  padding: 10px 12px;
  margin-bottom: 12px;
  background: #262626;
}
.alerts-panel__title {
  font-weight: 600;
  font-size: 0.9rem;
  margin-bottom: 4px;
}
/* Overrides .muted's own margins so the note sits tight under the title and clear of the
   buttons; .filter-row below it already supplies its own spacing. */
.alerts-panel__note {
  margin: 0 0 8px;
  font-size: 0.8rem;
  line-height: 1.4;
}

.btn-small {
  font-size: 0.82rem;
  padding: 7px 11px;
}

.btn-primary {
  background: var(--accent, #00bfff);
  border-color: var(--accent, #00bfff);
  color: #04212c;
  font-weight: 600;
}

/* Destructive actions are outlined red rather than filled — a filled red button reads as the
   primary action, and here it never is. */
.btn-danger {
  border-color: var(--fail, #ff5f5f);
  color: var(--fail, #ff5f5f);
  background: transparent;
}

.btn-danger:hover:not(:disabled) {
  background: rgba(255, 95, 95, 0.12);
}

.toolbar {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  padding: 14px 4px;
}

.toolbar-label,
.field {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: #9a9a9a;
}

.field {
  flex-direction: column;
  align-items: flex-start;
  gap: 3px;
}

.field-check {
  align-items: center;
}

input[type='number'],
input[type='text'] {
  font: inherit;
  font-size: 0.95rem;
  background: #262626;
  color: var(--text-on-dark);
  border: 1px solid #454545;
  border-radius: 8px;
  padding: 8px 10px;
}

input:disabled {
  opacity: 0.45;
}

input[type='number']:focus,
input[type='text']:focus {
  outline: 2px solid var(--accent, #00bfff);
  outline-offset: 1px;
}

.week-input {
  width: 74px;
}

/* ◀ [ 3 ] ▶ — stepping through weeks during curation is one tap rather than select-type-Go.
   Kept as one flex group so the arrows stay glued to the input when the toolbar wraps on a phone. */
.week-stepper {
  display: flex;
  align-items: center;
  gap: 6px;
}

/* A real 44x44 target, not .btn's text-shaped padding: these are the only controls on this screen
   a thumb hits repeatedly. Sized by min-width/min-height and centred with flex rather than by
   padding alone — .btn's 0.9rem/line-height:1 glyph only reaches ~36px tall on its own padding. */
.btn-step {
  min-width: 44px;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0 12px;
  line-height: 1;
}

.spread-input,
.worth-input {
  width: 82px;
}

.toggle {
  width: 22px;
  height: 22px;
  accent-color: var(--accent, #00bfff);
}

/* ---------- rows ---------- */

/* ---------- staged-week banner ---------- */

/* Amber, matching the `candidate` badge, so "staged" reads as the same concept in both places. */
.staged-banner {
  margin: 0 4px 4px;
  padding: 12px 14px;
  border: 1px solid #6b5220;
  border-left: 3px solid #d8a13a;
  border-radius: var(--radius, 16px);
  background: rgba(216, 161, 58, 0.08);
  font-size: 0.88rem;
  line-height: 1.5;
}

.staged-banner strong {
  color: #e0a83c;
}

.staged-banner span {
  color: #d0d0d0;
}

/* ---------- filters ---------- */

.filters {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 0 4px 14px;
}

.filter-row {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  align-items: center;
}

.filter-row-meta {
  gap: 12px;
}

.filter-search {
  flex: 1 1 160px;
  min-width: 0;
}

.filter-select {
  font: inherit;
  font-size: 0.88rem;
  background: #262626;
  color: var(--text-on-dark);
  border: 1px solid #454545;
  border-radius: 8px;
  padding: 8px 9px;
  max-width: 100%;
}

.filter-select:focus {
  outline: 2px solid var(--accent, #00bfff);
  outline-offset: 1px;
}

.rows {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.row {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 12px;
  border: 1px solid var(--divider, #333);
  border-radius: var(--radius, 16px);
  background: #232323;
}

/* A candidate game is not live for players. Marked with a left edge rather than a background tint
   so it stays legible next to the badges. */
.row-candidate {
  border-left: 3px solid #d8a13a;
}

.row-nonmember {
  opacity: 0.62;
}

/* An unselected game is one you've deliberately dropped. Dimmed rather than hidden — you need to be
   able to see and undo the removal, since "selected" is the default state and being unselected is
   the exception. */
.row-unselected {
  opacity: 0.55;
}

.row-main {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.teams {
  font-size: 1rem;
  font-weight: 600;
}

.kickoff {
  font-size: 0.8rem;
}

.row-controls {
  display: flex;
  align-items: flex-end;
  gap: 12px;
  flex-wrap: wrap;
}

.badges {
  display: flex;
  gap: 5px;
  flex-wrap: wrap;
  margin-top: 2px;
}

.badge {
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding: 3px 7px;
  border-radius: 999px;
  border: 1px solid currentColor;
}

.badge-final {
  color: var(--success, #4ede8a);
}
.badge-locked {
  color: var(--accent, #00bfff);
}
.badge-candidate {
  color: #d8a13a;
}
.badge-unselected {
  color: #9a9a9a;
}
/* The hand-set Pick of the Week. Given its own colour rather than sharing one, because the whole
   reason this badge exists is that a locked worth is indistinguishable from an automatic one by
   looking at the number — so it has to be findable at a glance down a 60-row list. */
.badge-worthlocked {
  color: #c58ae0;
}

/* --- live book line (week screen) -------------------------------------------------------------
   Only rendered on rows where the book DISAGREES with the stored line, so this is a rare, loud
   element rather than a column. Real colour values, not var() — PickDev shares almost none of
   PickWeb's tokens, and a var() typo silently takes its fallback (see the 0.8.8 note-textarea
   incident) rather than failing where anyone would notice. */
.live-line {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin-top: 6px;
  font-size: 0.78rem;
  color: #cfcfcf;
}

.live-line-value {
  font-variant-numeric: tabular-nums;
  padding: 2px 8px;
  border-radius: 999px;
  border: 1px solid #5a5a5a;
}

/* A flip or a tier crossing changes what ALREADY-MADE picks mean or pay, which is categorically
   worse than a line drifting half a point. Coloured to be noticed across a 60-row week. */
.live-line-severe .live-line-value {
  color: #1c1408;
  background: #e0a83c;
  border-color: #e0a83c;
  font-weight: 700;
}

.live-line-warn {
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-size: 0.68rem;
  font-weight: 700;
  color: #e0a83c;
}

.live-line-use {
  padding: 2px 10px;
  font-size: 0.75rem;
}

/* --- audit log (History tab) ------------------------------------------------------------------
   Reuses .row/.row-main/.badges from the week screen so the two read as one app. Only the
   before/after pair needs new styling, and it is the thing worth optimising for: the previous value
   is the entire reason the log exists. */
.audit-change {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.85rem;
  margin-top: 0.35rem;
}
.audit-old {
  color: #9a9a9a;
  text-decoration: line-through;
}
.audit-arrow {
  color: #6a6a6a;
}
.audit-new {
  color: var(--accent, #00bfff);
}
.audit-deleted {
  color: #e0645c;
  font-weight: 600;
}
.audit-context {
  margin-top: 0.15rem;
}
/* Automated entries are dimmed rather than hidden — the log holds very few of them (only spread
   changes), and the point of showing them at all is that a line moving is worth seeing regardless
   of who moved it. Dimming keeps human decisions visually dominant. */
.audit-row.audit-service {
  opacity: 0.72;
}
.badge-score {
  color: #cfcfcf;
}
.badge-picks {
  color: #b98cff;
}
/* Filled rather than outlined — this is the one badge that marks something still to DO, so it
   should read as an action item next to the purely descriptive ones. */
.badge-needsline {
  color: #1c1408;
  background: #e0a83c;
  border-color: #e0a83c;
  font-weight: 700;
}

/* ---------- danger zone ---------- */

.danger-zone {
  margin: 28px 0 40px;
  padding: 14px;
  border: 1px dashed #5a4040;
  border-radius: var(--radius, 16px);
}

/* Back and Advance sit side by side and wrap together on a narrow screen. Deliberately not spaced
   far apart or styled differently from each other — they are the same write in two directions, and
   the confirm dialog is what distinguishes them. */
.danger-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

.danger-zone h3 {
  margin: 0 0 10px;
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: #c98b8b;
}

/* ---------- gate (sign-in / not-admin) ---------- */

.gate {
  padding: 48px 8px;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 14px;
}

.gate h1 {
  margin: 0;
  font-size: 1.5rem;
}

.gate p {
  margin: 0;
  max-width: 46ch;
  line-height: 1.5;
}

.gsi-host {
  margin-top: 8px;
  min-height: 44px;
}

/* ---------- dialog ---------- */

.backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.66);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 18px;
  z-index: 100;
}

.dialog {
  background: #262626;
  border: 1px solid #454545;
  border-radius: var(--radius, 16px);
  padding: 20px;
  max-width: 460px;
  width: 100%;
  max-height: 88vh;
  overflow-y: auto;
}

.dialog h2 {
  margin: 0 0 12px;
  font-size: 1.1rem;
  line-height: 1.35;
}

.dialog-line {
  margin: 0 0 10px;
  font-size: 0.9rem;
  line-height: 1.5;
  color: #d8d8d8;
}

.confirm-label {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin: 14px 0 4px;
  font-size: 0.82rem;
  color: #9a9a9a;
}

.confirm-input {
  width: 100%;
}

/* chooseDialog's option list (ui.js). Two lines per option because the detail line — pick count,
   weeks, seasons — is the only thing that identifies a legacy account whose owner nobody has seen
   in a year. Real values from the dark palette, not var(--surface, #fff) fallbacks: tokens.css
   defines no --surface, so a light-theme guess renders unreadably here (see CLAUDE.md). */
.choice-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin: 14px 0 4px;
  /* The 2025 league was 15 accounts, and a bowl-season list could be longer still, so the list
     scrolls inside the dialog rather than pushing the Cancel button off a phone screen. */
  max-height: 46vh;
  overflow-y: auto;
}

.choice {
  display: flex;
  flex-direction: column;
  gap: 3px;
  width: 100%;
  text-align: left;
  padding: 11px 13px;
  background: #2e2e2e;
  color: var(--text-on-dark, #f0f0f0);
  border: 1px solid #454545;
  border-radius: 12px;
  font: inherit;
  cursor: pointer;
}

.choice:hover,
.choice:focus-visible {
  background: #383838;
  border-color: #5a5a5a;
}

.choice-label {
  font-size: 0.95rem;
  font-weight: 600;
}

.choice-detail {
  font-size: 0.8rem;
  color: #b0b0b0;
}

.dialog-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 18px;
}

/* ---------- update overlay ---------- */

/* The full deploy string from version.json, including the +<sha> suffix — that suffix is the part
   that actually identifies a deploy between hand-written semver bumps, so it's worth showing. */
.update-version {
  margin: 0 0 12px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.82rem;
  color: var(--accent, #00bfff);
  word-break: break-all;
}

.update-notes {
  margin: 0;
  padding-left: 18px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.update-notes li {
  font-size: 0.88rem;
  line-height: 1.5;
  color: #d8d8d8;
}

/* ---------- toasts ---------- */

#toast-host {
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  bottom: calc(16px + env(safe-area-inset-bottom));
  display: flex;
  flex-direction: column;
  gap: 8px;
  z-index: 200;
  width: min(440px, calc(100vw - 24px));
  pointer-events: none;
}

.toast {
  padding: 11px 14px;
  border-radius: 10px;
  font-size: 0.88rem;
  line-height: 1.4;
  background: #333;
  border: 1px solid #4d4d4d;
  color: var(--text-on-dark);
  animation: toast-in 180ms ease-out;
}

.toast-success {
  border-color: var(--success, #4ede8a);
}

.toast-error {
  border-color: var(--fail, #ff5f5f);
  background: #3a2626;
}

.toast.leaving {
  opacity: 0;
  transition: opacity 300ms ease-in;
}

@keyframes toast-in {
  from {
    opacity: 0;
    transform: translateY(8px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* Wider viewports get the game rows on one line — the controls sit to the right of the matchup
   rather than wrapping underneath it. Checked at both 390px and 1280px. */
@media (min-width: 700px) {
  .row {
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
  }

  .row-main {
    flex: 1 1 auto;
    min-width: 0;
  }

  .row-controls {
    flex: 0 0 auto;
  }
}

/* --- Week recap note (migrations/015) ---------------------------------------------------------
   Sits between the game list and the danger-zone advance controls: it is part of closing out a
   week, but it is not destructive, so it should not look like the advance button. */
/* NOTE ON COLOURS HERE: this panel originally referenced var(--rule), var(--surface) and
   var(--surface-2), none of which exist in tokens.css or in this file — so every fallback fired and
   the textarea rendered as #fff with inherited #f0f0f0 text, i.e. near-white on white. Real values
   only below. PickDev is a dark app (body uses --bg / --text-on-dark); the only deliberate
   exception is the writing surface itself, which stays light because it holds prose. */
.note-panel {
  margin: 22px 0 8px;
  padding: 14px 16px;
  border: 1px solid #454545;
  border-radius: 4px;
  background: rgba(127, 127, 127, 0.06);
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.note-head { display: flex; flex-direction: column; gap: 2px; }
.note-head .muted { font-size: 0.82rem; }
.note-body {
  width: 100%;
  font: inherit;
  line-height: 1.5;
  padding: 10px 12px;
  border: 1px solid #454545;
  border-radius: 3px;
  background: #f4f4f4;
  /* Explicit dark ink, NOT `inherit` — inheriting picks up --text-on-dark from body, which is what
     made this unreadable against the light writing surface. */
  color: #1c1c1c;
  resize: vertical;
  min-height: 5.5em;
}

.note-body::placeholder {
  color: #6b6b6b;
  opacity: 1; /* Firefox dims placeholders by default; the explicit grey above is already muted. */
}

.note-body:focus {
  outline: 2px solid var(--accent, #00bfff);
  outline-offset: 1px;
}
/* Status first, buttons pushed right — the operator reads "is this published?" before deciding
   which button to press, so it has to come first in the reading order too. */
.note-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.note-status { flex: 1 1 220px; font-size: 0.8rem; }

/* WHEN the recap is released (migrations/028) — the per-week override and the league default under
   it. Sits inside .note-panel--primary, separated by a hairline rather than by a heading, because
   it is not a second topic: the hour exists to leave time for the note above to be written.

   The selects copy .filter-select's REAL values rather than reaching for a token. PickDev defines
   no --surface, so `var(--surface, #fff)` silently renders a light box that inherits --text-on-dark
   for its ink and cannot be read; that shipped once in 0.8.8 and the note-body comment above is the
   other half of the same lesson. */
.note-release {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding-top: 10px;
  border-top: 1px solid #3a3a3a;
}
/* Label and control on ONE line, and the label takes the free space so both selects line up with
   each other. It wraps rather than squashing on a narrow phone — the same failure the league
   switcher's button row had before 0.12.16 made it a select. */
.release-row {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.release-row label {
  flex: 1 1 auto;
  font-size: 0.86rem;
}
.release-select {
  font: inherit;
  font-size: 0.88rem;
  background: #262626;
  color: var(--text-on-dark);
  border: 1px solid #454545;
  border-radius: 8px;
  padding: 7px 9px;
  max-width: 100%;
}
.release-select:focus {
  outline: 2px solid var(--accent, #00bfff);
  outline-offset: 1px;
}
.release-select:disabled { opacity: 0.55; }
.release-hint, .release-note { font-size: 0.78rem; line-height: 1.4; }
.release-note:empty { display: none; }

/* An optional decision rendered as a checkbox — "tell players about this" on a spread change, and
   the two delivery choices in the Messages composer. Laid out as a row so the box and its label
   read as one control.

   `flex-direction: row` is NOT redundant, and leaving it out was a real bug (found 2026-08-18 by
   reading the computed style, not the source). These elements carry BOTH classes —
   `class="confirm-label confirm-check"` — and `.confirm-label` above sets `flex-direction: column`
   for its stacked "type this to continue" case. Same specificity, and `.confirm-check` never
   overrode the property, so the declaration simply carried through: every one of these rendered
   with the checkbox stacked ABOVE its own label and centred, which reads as a heading rather than
   as a control. `align-items: center` made it worse by centring the box in the column. */
.confirm-check {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 8px;
  font-weight: 400;
}

.dialog-checkbox {
  width: 18px;
  height: 18px;
  accent-color: var(--accent, #00bfff);
  margin: 0;
}

/* --- Messages / broadcasts (js/messages.js, migrations/017) -------------------------------------
   The composer reuses .note-panel and .note-body from the week-recap note block above, which is
   deliberate: both are "write some prose a player will read", and they should look identical.

   COLOURS HERE ARE REAL VALUES, NEVER var(--surface) / var(--rule) / var(--surface-2). None of those
   tokens exist in PickWeb's tokens.css or in this file, so a var() referencing one silently takes
   its fallback — and a fallback written with light-theme instincts renders near-white text on a
   near-white box. That exact bug shipped in 0.8.8 on the note textarea; see the comment above
   .note-panel for the full story. */

/* The message title. Full width rather than input[type='text']'s natural size, because it is the
   headline of the thing being written, not a field in a form row. */
.broadcast-title {
  width: 100%;
  font-size: 1rem;
}

/* "Send to [ everyone ]" — label and select on one line, wrapping together on a phone. */
.broadcast-field {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

/* The explanation under the disabled "show when the app opens" checkbox. Sits at the same indent as
   the checkbox labels above it so it reads as attached to them rather than as a new section. */
.broadcast-hint {
  font-size: 0.78rem;
  line-height: 1.4;
  padding-left: 2px;
}

/* The message body as it appears in the LIST — clamped to three lines so a long message cannot push
   every other row off the screen. The full text is always one tap away in the composer. */
.broadcast-body {
  font-size: 0.85rem;
  line-height: 1.45;
  margin: 2px 0 4px;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
  /* A pasted URL has no break opportunity and would otherwise widen the row past the viewport. */
  overflow-wrap: anywhere;
}

/* --- the rolling API log (Logs tab) ------------------------------------------------------------
   Denser than every other list in this app, and monospaced, because it is the one screen you scan
   rather than read: the question is almost always "is there anything red in the last few minutes",
   which is answered by shape and colour before any of the text is read.

   Colours are all real values, not var(--surface)/var(--rule) — those tokens DO NOT EXIST in this
   app's tokens.css, and a var() with a light-theme fallback silently renders unusably here (that
   shipped once, in 0.8.8). These match input[type='text']'s existing #262626 / #454545 pair. */
.log-list {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-top: 10px;
}

.log-status {
  margin: 8px 0 0;
  font-size: 0.8rem;
}

.log-row {
  padding: 7px 9px;
  border-radius: 7px;
  background: #262626;
  /* The left edge carries the severity, so the level is readable from the shape of the list even
     when it has scrolled past the point of reading any individual row. */
  border-left: 3px solid #454545;
  font-size: 0.8rem;
  line-height: 1.4;
}

.log-row.log-error {
  border-left-color: #e0645c;
  background: #2c2020;
}
.log-row.log-warn {
  border-left-color: #e0a83c;
}
.log-row.log-info {
  border-left-color: #4a4a4a;
}
/* Debug rows are the bulk of the file at the default level, and they are context rather than
   signal — dimmed so they recede behind anything that actually went wrong. */
.log-row.log-debug {
  opacity: 0.62;
  border-left-color: #3a3a3a;
}

.log-head {
  display: flex;
  align-items: baseline;
  gap: 8px;
  flex-wrap: wrap;
}

.log-time {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.74rem;
  color: #9a9a9a;
}

.log-level {
  text-transform: uppercase;
  font-size: 0.64rem;
  font-weight: 700;
  letter-spacing: 0.06em;
}
.log-level-error { color: #e0645c; }
.log-level-warn { color: #e0a83c; }
.log-level-info { color: #8fa3b0; }
.log-level-debug { color: #6a6a6a; }

/* The scope is what turns this into a navigable log rather than a wall — `picks`, `client`,
   `signup`, `espn`, `db`. Accented because it is the most useful thing to search on. */
.log-scope {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.74rem;
  color: var(--accent, #00bfff);
}

.log-message {
  margin: 2px 0 0;
  /* A pasted URL or an unbroken token would otherwise widen the row past a phone's viewport. */
  overflow-wrap: anywhere;
}

.log-context {
  display: flex;
  flex-wrap: wrap;
  gap: 3px 10px;
  margin-top: 3px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.72rem;
}

.log-pair {
  display: inline-flex;
  gap: 3px;
  min-width: 0;
}

.log-key {
  color: #7d7d7d;
}
.log-key::after {
  content: '=';
}

.log-value {
  color: #cfcfcf;
  overflow-wrap: anywhere;
  /* pre-wrap so a stack trace — whose newlines logs.js restores from the escaped form the file
     stores them in — renders as the multi-line thing it is. This is the single most useful piece
     of text this screen ever shows; collapsing it to one line would waste it. */
  white-space: pre-wrap;
}

/* A line the parser could not read. Shown, never hidden — an unparseable line is more likely to be
   the interesting thing than not. */
.log-raw {
  border-left-color: #6a6a6a;
  opacity: 0.8;
}
.log-raw .log-message {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.72rem;
  margin: 0;
  white-space: pre-wrap;
}

/* A stack trace is multi-line by nature; inline in the pair flow it would sit squeezed between
   `player=26` and `ua=ios/safari`. Given its own full-width row it reads as the traceback it is. */
.log-pair-block {
  flex-basis: 100%;
  flex-direction: column;
  gap: 1px;
}

/* Outright scoring rules — the two halves of the per-league multiplier (migrations/022, 0.11.9).
   Lives inside a .note-panel, so only the checkbox row itself needs styling.

   flex-direction: row IS THE LOAD-BEARING LINE. PickDev's .confirm-label sets column and had every
   checkbox in the app rendering stacked above its own label until that was found by reading the
   computed style (see CLAUDE.md, 0.9.6). Stating the direction explicitly here means this block
   cannot inherit that again from whatever it is nested inside.

   Colors are real values, never var(--surface)/var(--rule): those tokens do not exist in this app
   and a light-theme fallback renders unusably on #1f1f1f. */
.scoring-rules {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.scoring-rule {
  display: flex;
  flex-direction: row;
  align-items: flex-start;
  gap: 10px;
  cursor: pointer;
}
/* align-self rather than a margin: the box lines up with the first line of the label text however
   many lines the detail wraps to at 390px, which is the width this is actually curated at. */
.scoring-rule__box {
  flex-shrink: 0;
  align-self: flex-start;
  margin-top: 2px;
  width: 18px;
  height: 18px;
  accent-color: #7fb3ff;
}
.scoring-rule__text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.scoring-rule__label { font-weight: 600; }
.scoring-rule__detail { font-size: 0.82rem; color: #a8a8a8; }
.scoring-example { font-size: 0.82rem; margin: 0; }

/* The three numeric scoring rules (migrations/024) — spread points per tier, and the two rates.
   Sits between the checkbox rows and the worked example.

   A ROW, NOT A COLUMN, unlike .scoring-rule: these are short labels against a narrow field, and
   stacking them the way the checkboxes stack would push the worked example — the part that actually
   tells the operator what they just changed — off a 390px screen. The label column takes the slack
   and the field stays a fixed width so the three line up in a readable column of numbers.

   NOTE .scoring-number IS A <label> AND MUST SET ITS OWN flex-direction. PickDev's .confirm-check
   has been caught twice inheriting `flex-direction: column` from an enclosing label rule and
   rendering a control stacked above its own text; the row direction here is written explicitly
   rather than relied on as a default. Verify against the COMPUTED style, not the source.

   Colors are real values, never var(--surface)/var(--rule): those tokens do not exist in this app
   and a light-theme fallback renders unusably on #1f1f1f. */
.scoring-numbers {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding-top: 12px;
  margin-top: 2px;
  border-top: 1px solid #383838;
}
.scoring-number {
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
.scoring-number__text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.scoring-number__label { font-weight: 600; }
.scoring-number__hint { font-size: 0.82rem; color: #a8a8a8; }
/* Fixed width so the three fields form a column. flex-shrink: 0 because the hint text beside it
   wraps to two lines at 390px and would otherwise squeeze the field down to unusable. */
.scoring-number__input {
  flex-shrink: 0;
  width: 92px;
  text-align: right;
}

/* --- The Recap screen's fun-facts feed -------------------------------------------------------

   COLOURS HERE ARE REAL VALUES, NEVER var(--surface) / var(--rule) / var(--surface-2). None of
   those tokens exists in PickWeb's tokens.css or in this file, so a var() referencing one silently
   takes its fallback, and a fallback written with light-theme instincts renders near-white on
   near-white. That exact bug shipped in 0.8.8; see the comment above .note-panel. The values below
   are copied from controls on this page that already render correctly. */

/* The note editor is the primary thing on this screen — the facts exist to be read while writing
   into it — so it loses .note-panel's top margin and takes a slightly stronger edge. */
.note-panel--primary {
  margin-top: 8px;
  border-color: #5a5a5a;
}

.fact-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

/* One fact per row: the sentence, and the button that copies it. The button is pinned to the top
   rather than centred because a fact can wrap to three lines on a phone and a vertically-centred
   button then floats away from the row it belongs to. */
.fact-row {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 9px 11px;
  border: 1px solid #3a3a3a;
  border-radius: 4px;
  background: rgba(127, 127, 127, 0.05);
}

/* Selectable, and deliberately so: Copy is the fast path, but half the value of a fact is pulling
   three words out of the middle of it. */
.fact-text {
  flex: 1 1 auto;
  margin: 0;
  line-height: 1.45;
  font-size: 0.92rem;
  user-select: text;
}

.fact-copy {
  flex: 0 0 auto;
  min-width: 5.2em; /* Holds its width through the Copy -> Copied -> Copy swap so the row cannot jump. */
  text-align: center;
}

@media (hover: hover) {
  .fact-row:hover {
    border-color: #4d4d4d;
    background: rgba(127, 127, 127, 0.09);
  }
}
