/* Cobalt Pool — app.css
 *
 * Hand-authored, not a Tailwind build.
 *
 * SPEC §19 says "Tailwind CSS". This app ships a single hand-written stylesheet
 * instead, and the reason is a build one rather than a design one: Tailwind needs
 * a Node toolchain and a build step, and this project has neither (AGENTS.md: "JS:
 * Vanilla, zero build steps"). Downloading a toolchain at build time would add a
 * network dependency to a deployment that is otherwise a single static binary,
 * and the design in docs/UX-PLAN.md is deliberately small — one accent colour, a
 * handful of semantic colours, and four components. The tokens below are that
 * design written down directly.
 *
 * What is preserved from the Tailwind decision: the design tokens are CSS custom
 * properties in one place, so the visual language is still centrally defined, and
 * dark mode is a variable swap rather than a second stylesheet.
 */

/* --- design tokens (docs/UX-PLAN.md "Visual design") --------------------- */
:root {
  --surface:        #ffffff;
  --surface-2:      #f9fafb;
  --text:           #111827;
  --text-2:         #4b5563;
  --text-muted:     #626b79;   /* 5.39:1 on white. Was #9ca3af = 2.54:1 (WCAG 1.4.3 fail). */
  --text-dim:       #4b5563;   /* "closed" rows are dimmed by COLOUR, never by opacity: a row's
                                   opacity multiplies every colour inside it, so one value cannot
                                   keep four text colours above 4.5:1 (it drove muted text to 1.57:1). */
  --text-dim-2:     #626b79;
  --border:         #e5e7eb;
  --accent:         #1d4ed8;   /* the primary action, as TEXT and as a line */
  /* --accent-fill is the surface BEHIND --accent-ink. It is separate from --accent because in
     dark mode the two roles pull opposite ways: the text role must be light enough to read on a
     dark surface, the fill must be dark enough for white ink on top. One token cannot be both. */
  --accent-fill:    #1d4ed8;
  --accent-ink:     #ffffff;
  --accent-soft:    #eff4ff;   /* the accent as a surface, for headers and badges */
  --accent-edge:    #c7d7fe;   /* a 1px accent border */
  --win:            #15803d;
  --win-soft:       #ecfdf5;
  --loss:           #b91c1c;
  --loss-soft:      #fef2f2;
  --pending:        #b45309;
  --pending-soft:   #fffbeb;
  --rink:           #0f766e;   /* a second accent, for the schedule and cycle */

  --radius:         10px;
  --radius-sm:      6px;
  --gap:            16px;
  /* The app's controls are 48px. The floor is 44px. Nothing goes below it.

     WCAG 2.2 SC 2.5.8 asks for 24px. Apple asks for 44pt. Material asks for 48dp.
     The owner decided on 2026-09-29: 44 is a satisfactory minimum. There was no
     complaint about tap comfort. Thus the controls that were 48 STAY 48. A working
     target must not shrink to match a minimum.

     The defect was not the size. This token said 48 while eight declarations
     hardcoded 44. Also, one document called 48 a minimum and another called 44 a
     minimum. Those two disagreements were the defect. */
  --tap:            48px;
  --maxw:           640px;
}

/* Dark mode from the start, as a variable swap: retrofitting it means touching
   every component, and this app is mostly used in the evening. */
@media (prefers-color-scheme: dark) {
  :root {
    --surface:    #0b0b0c;
    --surface-2:  #17181b;
    --text:       #f3f4f6;
    --text-2:     #cbd5e1;
    --text-muted: #8b93a1;   /* 6.36:1 on --surface. Was #6b7280 = 4.07:1. */
    --text-dim:   #cbd5e1;
    --text-dim-2: #8b93a1;
    --border:     #2a2c31;
    --accent:      #60a5fa;  /* TEXT/line role. Was #3b82f6, which failed 4.5:1 as text on
                                --accent-soft (4.35:1) and --win-soft (4.46:1). */
    --accent-fill: #2563eb;  /* FILL role. Was #3b82f6 = 3.68:1 against white ink. */
    --accent-soft: #16213a;
    --accent-edge: #1e3a8a;
    --win:         #4ade80;
    --win-soft:    #0b2417;
    --loss:        #f87171;
    --loss-soft:   #2a1214;
    --pending:     #fbbf24;
    --pending-soft:#2a1f08;
    --rink:        #2dd4bf;
  }
}

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

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--surface);
  color: var(--text);
  font-family: Inter, system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: 16px;            /* 16px is where Apple (17pt) and Material (16sp) converge */
  line-height: 1.45;
  /* Tabular figures: in a game about token counts, digits that change width
     make a balance look broken as it animates. */
  font-variant-numeric: tabular-nums;
}

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

h1 { font-size: 20px; margin: 0; }
h2 { font-size: 18px; margin: 0; }

.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* Inline SVG icons (see src/app/icons.go).

   SIZED IN em, NOT px. The path is drawn in a 24x24 viewBox and carries no width or
   height attribute, so the size is a presentation decision the stylesheet makes: at the
   inherited font-size a 13px countdown and an 18px tab bar both get an icon that fits,
   and a single icon serves both. vertical-align keeps it sitting on the text baseline
   rather than floating above it.

   currentColor is what makes the colour rules work. The Unicode glyphs these replace were
   emoji-block characters, which iOS and Android paint from the colour-emoji font - ignoring
   the CSS color entirely - so .lock and .countdown could not tint them. An SVG stroke in
   currentColor follows the element it sits in. */
.icon {
  width: 1em; height: 1em;
  display: inline-block; vertical-align: -0.125em;
  flex: none;
}
.subtle, .fig-muted { color: var(--text-muted); }

/* --- shell --------------------------------------------------------------- */
.app {
  max-width: var(--maxw);
  margin: 0 auto;
  min-height: 100vh;
  /* Room for the fixed tab bar plus the iPhone home indicator. */
  padding-bottom: calc(72px + env(safe-area-inset-bottom));
}

.topbar {
  display: flex; align-items: center; justify-content: space-between;
  padding: 12px var(--gap);
  border-bottom: 1px solid var(--border);
  position: sticky; top: 0; background: var(--surface); z-index: 5;
}
/* The brand is a link, so it is a target too: give it the full height of the
   bar rather than letting the text box be 23px tall. */
/* The gap between the orb and the wordmark is deliberately wider than the 8px the rest of
 * the layout uses. At 16px the wordmark's cap height is small, and 8px read as the mark
 * being crowded against the text rather than paired with it. 17px is roughly one character
 * of space at this size, which is what the owner asked for - enough that the orb reads as a
 * separate mark rather than as the first glyph of the name. */
.brand { font-weight: 700; color: var(--text); letter-spacing: -0.01em; display: inline-flex; align-items: center; gap: 17px; min-height: 44px; }

/* The orb beside the wordmark.
 *
 * Fixed 26px rather than a relative size: it sits next to 18px text in a 56px bar, and
 * a proportional height would drift with the font. The explicit width/height in the
 * template reserve the space before the image loads, so the wordmark does not jump
 * sideways on a slow connection.
 *
 * Slight negative margin on the left so the ORB's visual edge lines up with the page
 * gutter rather than its bounding box - the icon is a circle inside a square, so
 * ignoring this leaves the header looking a few pixels indented compared with the
 * content beneath it. */
.brand-orb {
  width: 26px; height: 26px;
  margin-left: -3px;
  flex: 0 0 auto;
  /* A faint drop shadow gives the object a little depth against the bar without
   * drawing a container around it. */
  filter: drop-shadow(0 1px 2px rgb(0 0 0 / 0.25));
}

/* On a dark bar the orb's darker facets can merge into the background, so it gets a
 * touch more presence. Subtle on purpose: the image has its own shading and heavy
 * treatment would fight it. */
@media (prefers-color-scheme: dark) {
  .brand-orb { filter: drop-shadow(0 1px 3px rgb(0 0 0 / 0.6)) brightness(1.08); }
}
.topbar-actions { display: flex; gap: 4px; align-items: center; }
.topbar-actions a {
  min-width: 44px; min-height: 44px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 50%; color: var(--text-2); font-weight: 600;
}
.topbar-actions a:hover { background: var(--surface-2); text-decoration: none; }

/* The bottom padding clears the FIXED tab bar.
 *
 * The tab bar is position:fixed, so it takes no space in the flow and the page's last
 * element renders underneath it. Most pages happened to end with enough incidental content
 * to hide this; the settings page's sign-out button is the first control to land squarely
 * behind the bar, where it was partly unclickable.
 *
 * 56px is the tab bar's own height plus the safe-area inset it also applies, plus the usual
 * gap - so the last element sits clear of it on a phone with a home indicator and on one
 * without. Expressed with a custom property so the two cannot drift apart if the bar's
 * height ever changes.
 */
:root { --tabbar-height: 56px; }
.content {
  padding: var(--gap);
  padding-bottom: calc(var(--tabbar-height) + env(safe-area-inset-bottom) + var(--gap));
}

/* --- bottom navigation (thumb zone; 4 primary destinations) --------------- */
.tabbar {
  position: fixed; left: 0; right: 0; bottom: 0;
  display: grid; grid-template-columns: repeat(4, 1fr);
  background: var(--surface);
  border-top: 1px solid var(--border);
  padding-bottom: env(safe-area-inset-bottom);
  z-index: 10;
}
.tabbar a {
  min-height: 56px;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 2px; font-size: 12px; color: var(--text-2);
}
.tabbar a .icon { font-size: 18px; }
.tabbar a.active { color: var(--accent); font-weight: 600; }
.tabbar a:hover { text-decoration: none; }
/* The My Bets notification count. It is a badge, not a link, so it takes the filled-accent
   treatment the .pill uses on a page head. tabular-nums (set on body) keeps the tab bar from
   shifting as the count changes.

   It renders ONLY when there is news: render.go sets ShowMyBetsBadge only for a count above
   zero. A badge reading "0" claims there is something to look at when there is not. */
.tabbar-count {
  min-width: 18px; margin-left: 4px; padding: 0 4px; border-radius: 999px;
  background: var(--accent-fill); color: var(--accent-ink);
  font-size: 12px; font-weight: 700; line-height: 18px; text-align: center;
}
/* Keep "My Bets 3" on one line; the label is a flex item in the column tab bar. */
.tabbar-label { white-space: nowrap; }


/* --- flash messages ------------------------------------------------------ */
.flash { margin: 0 var(--gap); padding: 10px 12px; border-radius: var(--radius-sm); font-size: 14px; }
.flash-ok  { background: color-mix(in srgb, var(--win) 12%, transparent);  color: var(--win); }
.flash-err { background: color-mix(in srgb, var(--loss) 12%, transparent); color: var(--loss); }

/* --- home ---------------------------------------------------------------- */
.hero { padding: 8px 0 20px; }
.balance { display: flex; align-items: baseline; gap: 8px; }
.balance-num { font-size: 32px; font-weight: 600; letter-spacing: -0.02em; }
.balance-unit { color: var(--text-2); }
.rank { margin: 4px 0 0; color: var(--text-2); }
.delta { margin: 2px 0 0; font-size: 14px; }
.delta.up { color: var(--win); }
.delta.down { color: var(--loss); }
/* The hero's inline equity change (e.g. (+12)) beside the headline number. Size matches the old
   .delta line so the hero's rhythm is unchanged; colours reuse the win/loss tokens. The rank
   arrow reuses the existing .delta up/down classes. */
.balance-delta { font-size: 14px; }
.balance-delta.up { color: var(--win); }
.balance-delta.down { color: var(--loss); }

.cta {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--gap);
  margin-bottom: var(--gap);
}
.cta-lead { font-size: 18px; font-weight: 600; margin: 0; }
.cta-sub { margin: 4px 0 12px; color: var(--text-2); font-size: 14px; }

.panel { border-top: 1px solid var(--border); padding-top: var(--gap); margin-top: var(--gap); }
.panel-title { font-size: 13px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-2); margin-bottom: 8px; }
.panel-more { margin-top: 12px; font-size: 14px; }
/* Home's one entry point to the Schedule (Phase 3, D2). A standalone navigation link, so
   it gets a real 44px target like .panel-more; the class stands alone so the whole
   affordance is one partial + one class and is trivial to move or remove. */
.schedule-link { margin: 0 0 var(--gap); font-size: 14px; }
.schedule-link a { display: inline-flex; align-items: center; min-height: 44px; padding-right: 8px; }
/* A navigation link is a control, so it gets a real target: WCAG 2.2 SC 2.5.8
   exempts a link inside a sentence of text, but "All my bets ->" and "Show all
   games" are standalone navigation, not prose. */
.panel-more a { display: inline-flex; align-items: center; min-height: 44px; padding-right: 8px; }
.week-line { margin: 0 0 8px; }

.cycle { margin-top: 8px; }
.cycle-bar { display: flex; gap: 2px; flex-wrap: wrap; }
.cycle-seg { width: 6px; height: 14px; border-radius: 2px; background: var(--border); }
.cycle-seg.used { background: var(--accent); }
.cycle-label { margin: 6px 0 0; font-size: 13px; color: var(--text-2); }

/* --- buttons ------------------------------------------------------------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: var(--tap); padding: 0 16px;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--surface); color: var(--text);
  font: inherit; font-weight: 600; cursor: pointer;
}
.btn-primary { background: var(--accent-fill); color: var(--accent-ink); border-color: var(--accent-fill); }
.btn-block { width: 100%; }
.btn-small { min-height: 44px; padding: 0 14px; font-size: 14px; }
.btn-ghost { background: transparent; }
.btn:disabled, .btn[aria-disabled="true"] { opacity: 0.5; cursor: not-allowed; }

.pill {
  display: inline-block; padding: 2px 10px; border-radius: 999px;
  background: var(--surface-2); border: 1px solid var(--border);
  font-size: 13px; color: var(--text-2);
  /* Not stretched by a flex parent, and not shrunk by a long title.

     align-self is what keeps the pill level with the FIRST line of a title that has
     wrapped to two lines. Without it, a flex parent using `align-items: center`
     places the pill at the vertical middle of the whole title block - measured at
     17px below the title's top on a 29px line - so it floats beside the second line
     instead of next to the words it labels. That misalignment is the bug that was
     reported from production on a long player name. */
  align-self: flex-start;
  /* The pill keeps its intrinsic width. A long name wraps; it does not squash this. */
  flex: none;
  white-space: nowrap;
}

/*
  The title and the pill share one row.

  `align-items: center` was wrong once titles could wrap: a two-line heading ("X's
  past picks" for a long name such as "SouthFrance Riviera") is 58px tall, so a
  centered pill floats half-way down it and looks detached from the text it belongs
  to. `flex-start` keeps the pill level with the FIRST line, which is where the eye
  reads it.

  min-width: 0 lets the h1 wrap rather than forcing the row wider than the screen;
  the pill keeps its intrinsic width because .pill is flex: none.
*/
.page-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 12px; margin-bottom: var(--gap);
}
.page-head h1 { min-width: 0; overflow-wrap: anywhere; }
.group { margin-bottom: var(--gap); }
.group-title { font-size: 13px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-2); margin-bottom: 8px; }
.group-note { font-size: 13px; color: var(--text-2); margin: 0 0 8px; }
.empty { color: var(--text-2); padding: 24px 0; text-align: center; }

/* --- picks --------------------------------------------------------------- */
.teamlist { list-style: none; margin: 0; padding: 0; }
.teamrow {
  display: flex; align-items: center; gap: 8px;
  min-height: var(--tap); border-bottom: 1px solid var(--border);
  /* No fixed heights anywhere: WCAG 1.4.4/1.4.10 require the layout to survive
     200% zoom and reflow at 320px, and a fixed row height clips zoomed text. */
}
.teamrow-main {
  flex: 1; display: flex; align-items: center; gap: 10px;
  padding: 12px 0; color: var(--text);
}
.teamrow-main:hover { text-decoration: none; }
.teamrow-opp { color: var(--text-2); font-size: 14px; flex: 1; }
.teamrow-time { color: var(--text-2); font-size: 14px; text-align: right; }
/* "Dimmed" means closed / locked / unavailable. It is expressed in COLOUR, never with opacity.

   A row's opacity multiplies every colour inside it at once, so no single value can hold four
different text colours above 4.5:1. Measured on a closed row: opacity .55 drove muted text to
   1.57:1 - the worst contrast failure in the app.

   The state is still signalled twice over (the "closed" lock badge, and the struck-through
tricode), so colour is the third cue rather than the only one. Both dim levels are real tokens
   checked against every surface, not a hand-tuned grey. */
.teamrow.dimmed .tricode,
.denserow.dimmed .tricode,
.fixture.dimmed .tricode { color: var(--text-dim); }
/* Picks puts a <details class="teampick"> directly inside li.teamrow, and .teamrow is a flex
   container, so the details hugged its own content (299px of a 606px column) and drew its own
   border 1px above the li's - a divider that doubled in weight halfway across the row.
   flex:1 1 auto fixes both the hug (B5) and the narrow-viewport overflow (B3). */
.teamrow > .teampick { flex: 1 1 auto; min-width: 0; }
.teamrow:has(.teampick) { border-bottom: 0; }   /* .teampick's own border is the divider */
.teamrow.dimmed .teamrow-opp,
.teamrow.dimmed .teamrow-time,
.denserow.dimmed .when,
.fixture.dimmed .fixture-opp,
.fixture.dimmed .fixture-when { color: var(--text-dim-2); }
/* Genuinely DISABLED controls keep their opacity: WCAG 1.4.3 exempts inactive UI components,
   and a control that cannot be used should not look as though it can. */
/* The bet sheet in a row is a sibling of the row's link, in a flex container, so
   it would otherwise shrink to its summary's width. */
.teamrow > .sheet { flex: 0 0 auto; }
.teamrow > .sheet[open] { flex: 1 1 100%; }

.tricode {
  font-size: 14px; font-weight: 700; letter-spacing: 0.05em;
  text-transform: uppercase;
}
.tricode.spent { text-decoration: line-through; color: var(--text-muted); }

.countdown { color: var(--pending); font-weight: 600; display: block; font-size: 13px; }
.lock { color: var(--text-muted); font-size: 13px; }

.chiplist { list-style: none; display: flex; flex-wrap: wrap; gap: 6px; margin: 0; padding: 0; }
.chip {
  padding: 4px 10px; border-radius: var(--radius-sm);
  border: 1px solid var(--border); font-size: 13px; font-weight: 600;
  letter-spacing: 0.05em;
}
.chip-used { color: var(--text-muted); text-decoration: line-through; }

/* --- bet sheet ----------------------------------------------------------- */
.sheet { position: relative; }
.sheet > summary { list-style: none; cursor: pointer; }
.sheet > summary::-webkit-details-marker { display: none; }
/* A closed <details> shrink-wraps its content, which would make the controls
   inside a bet sheet measure 12px wide. Forcing the panel to the full width of
   its container keeps the grid honest whether the sheet is open or closed. */
.sheet[open] { width: 100%; }
.sheet-body {
  width: 100%;
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: var(--radius); padding: var(--gap); margin: 8px 0;
}
.sheet-title { font-weight: 600; margin: 0; }
.sheet-sub { color: var(--text-2); font-size: 13px; margin: 4px 0 12px; }
.sheet-form { display: block; }

.field { border: 0; padding: 0; margin: 0 0 12px; }
.field legend { font-size: 13px; color: var(--text-2); padding: 0 0 6px; }

.teamchoices { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.choice {
  position: relative;              /* anchors the visually-hidden input */
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  min-height: var(--tap); width: 100%;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--surface); cursor: pointer; gap: 2px;
}
.choice input { position: absolute; opacity: 0; pointer-events: none; }
.choice:has(input:checked) { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }
/* The radio itself is visually hidden, so the wrapper must carry the focus indicator. Three of
the 46 sheets on /games had none at all (WCAG 2.4.7). :has(input:focus-visible) rather than
   :focus-within, so clicking a tile with a mouse does not draw a keyboard ring. */
.choice:has(input:focus-visible),
.stake:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
.choice.disabled { opacity: 0.45; cursor: not-allowed; }
.choice-sub { font-size: 12px; color: var(--text-2); text-transform: uppercase; letter-spacing: 0.05em; }

/* Three big buttons, not a slider: the whole range is 1-3, so a slider would be
   a worse control with a smaller target. */
.stakes { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.stake {
  position: relative;              /* anchors the visually-hidden input */
  display: flex; align-items: center; justify-content: center;
  min-height: var(--tap); width: 100%;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm); background: var(--surface); cursor: pointer;
  font-weight: 600; font-size: 18px;
}
.stake input { position: absolute; opacity: 0; pointer-events: none; }
.stake:has(input:checked) { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }

/* A bet sheet inside an expanded team row is nested inside TWO details elements,
   and a closed <details> shrink-wraps its content - so the stake grid collapsed to
   23px per button. The panel is forced to a sensible width wherever it appears. */
.sheet-body { min-width: 260px; }
.fixture .sheet, .fixture .sheet-body { width: 100%; }
.teampick .sheet-body { min-width: 0; }
.sheet-inline .teamchoices { grid-template-columns: 1fr 1fr; }

.return-line { font-size: 14px; color: var(--text-2); margin: 0 0 12px; }

/* --- team context on the bet sheet --------------------------------------- */
/* The facts a player reasons with, below Place bet: record, points, division
   standing, and last-5 form (feature request §3.1). Text-first - the W/L chips
   carry the letter, so the form is readable without colour (WCAG 1.4.1). */
.teamcontexts {
  margin-top: 12px; padding-top: 10px;
  border-top: 1px solid var(--border);
  display: flex; flex-direction: column; gap: 6px;
}
.teamcontext {
  display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px;
  font-size: 13px; color: var(--text-2);
}
.tc-team { color: var(--text); font-weight: 600; letter-spacing: 0.05em; }
.tc-record { color: var(--text); font-variant-numeric: tabular-nums; }
.tc-form { display: inline-flex; align-items: center; gap: 4px; }
.tc-form-label { color: var(--text-muted); }
.teamcontext .chip { padding: 1px 6px; font-size: 12px; }
.chip-won { color: var(--win); border-color: color-mix(in srgb, var(--win) 40%, var(--border)); }
.chip-lost { color: var(--loss); border-color: color-mix(in srgb, var(--loss) 40%, var(--border)); }

/* --- bets ---------------------------------------------------------------- */
.betlist { list-style: none; margin: 0; padding: 0; }
.bet { border-bottom: 1px solid var(--border); }
.bet-main { display: flex; align-items: center; gap: 10px; padding: 12px 0; color: var(--text); }
.bet-main:hover { text-decoration: none; }
/* A 20px column keeps the status icon aligned down the list. min-width rather than a
   fixed width because a payout-pending row carries two icons (outcome plus clock) and a
   fixed box would clip them; every other row sits on the 20px floor. */
.bet-icon { min-width: 20px; display: inline-flex; align-items: center; justify-content: center; gap: 1px; }
/* The token amount that replaced the clock on a payout-pending row. It inherits the row's
   outcome colour from .bet-won/.bet-lost .bet-icon, so it sets only the weight and size
   needed to read as a figure beside the outcome icon. */
.bet-amount { font-size: 12px; font-weight: 700; }
.bet-won  .bet-icon { color: var(--win); }
.bet-lost .bet-icon { color: var(--loss); }
.bet-pending .bet-icon { color: var(--pending); }
.bet-void .bet-icon { color: var(--text-muted); }
.bet-team { flex: 1; display: flex; flex-direction: column; }
.bet-match { font-size: 13px; color: var(--text-2); }
/* The figures column holds a label and a time; keep them from wrapping apart
   from each other, and keep "1 -> 2" on one line. */
.bet-figures { text-align: right; display: flex; flex-direction: column; align-items: flex-end; white-space: nowrap; }
.bet-when { font-size: 12px; color: var(--text-2); }
.bet-actions { display: flex; align-items: center; gap: 8px; justify-content: flex-end; padding-bottom: 10px; }

.fig-up { color: var(--win); font-weight: 600; }
.fig-down { color: var(--loss); font-weight: 600; }
/* Amber is reserved for TIME and URGENCY - a countdown, an editable window. It used to
   colour the stake figure as well ("2 to win 4"), which gave the app's most-read number
   a second meaning to decode and made it compete with the genuine deadlines. The stake
   is information, not urgency, so it is ink. */
.fig-pending { color: var(--text); font-weight: 600; font-size: 13px; }
/* The urgency case keeps amber, and carries the weight as well as the hue. */
.fig-urgent { color: var(--pending); font-weight: 700; font-size: 13px; }
.fig-muted { color: var(--text-muted); font-size: 13px; }

/* The week picker: a "This week" link plus a dropdown of scoring weeks, as one no-JS GET
   form. The link is an <a> so it always works, including when the current week has no bets
   and so is absent from the dropdown; the select submits on change when script is present,
   and the <noscript> button covers the no-JS case. */
.weekpick { display: inline-flex; align-items: center; gap: 6px; }
.weekpick-now {
  display: inline-flex; align-items: center; min-height: var(--tap);
  padding: 0 14px; font-size: 14px; color: var(--text-2);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
}
.weekpick-now.on { background: var(--accent-fill); color: var(--accent-ink); }
.weekpick select {
  min-height: var(--tap); padding: 0 8px; font-size: 14px;
  color: var(--text); background: var(--surface);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
}

/* --- dense game list (the ESPN lesson: 54 games need a compact row) -------- */
.daynav { display: flex; justify-content: space-between; margin-bottom: 12px; font-size: 14px; }
.daynav a { display: inline-flex; align-items: center; min-height: 44px; }
.dense { width: 100%; border-collapse: collapse; }
.dense th { text-align: left; font-size: 12px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-2); padding: 6px 4px; }
.dense td { padding: 10px 4px; border-top: 1px solid var(--border); vertical-align: middle; }
.denserow.dimmed .when { color: var(--text-dim-2); }

/* The dense row's bet sheet opens as an OVERLAY instead of inside the table's sizing.

   .sheet-body sets min-width: 260px because a sheet nested in a shrink-wrapped <details>
   collapsed to 23px buttons (see .teampick .sheet-body). Inside a table cell that floor
   becomes the TABLE's min-content, which pushed the whole document 72px wide at a 320px
   viewport. Position the open panel out of flow and the table sizes without it - and the
   controls keep their width, which the naive `min-width: 0` does NOT (it collapses .choice
   to 44px and .stake to 27px). */
.dense td.pick { position: relative; }
.dense .sheet[open] .sheet-body {
  position: absolute; right: 0; top: calc(100% + 6px);
  width: 260px; min-width: 0; margin: 0;
  z-index: 3;
  box-shadow: 0 8px 24px rgb(0 0 0 / 0.12);
}
.when { color: var(--text-2); font-size: 14px; white-space: nowrap; }
.pick { text-align: right; }

.tag { display: inline-block; padding: 2px 6px; border-radius: 4px; font-size: 12px; font-weight: 600; }
.tag-final { background: var(--surface-2); color: var(--text-2); }
.tag-live  { background: color-mix(in srgb, var(--loss) 15%, transparent); color: var(--loss); }
.tag-bet   { background: color-mix(in srgb, var(--accent) 15%, transparent); color: var(--accent); }

/* The final score beside a team on the schedule. Tabular figures keep the columns aligned
   down a day's list, and the weight matches the team code it sits beside. */
.score { font-weight: 600; font-variant-numeric: tabular-nums; }

/* --- standings ----------------------------------------------------------- */
.standings { list-style: none; margin: 0; padding: 0; }
/*
  The <li> is a PLAIN BLOCK and the <a> is the flex row.

  It was briefly a flex row containing a flex row, which broke the layout: the
  outer flex gave the anchor `flex: 0 1 auto` instead of filling the width, and the
  anchor ended up 25px SHORTER than the row it lived in - so 12px of dead padding
  sat above and below the tap target on every one of the 32 rows.

  Only one of these two elements may own the row layout. The anchor owns it now,
  because the whole row is the tap target; the <li> only draws the divider and the
  "you" highlight.

  Padding moved to the anchor for the same reason: a 32-row list wants the whole
  row tappable, and padding on the <li> put 12px of dead space above and below the
  link on every row.
*/
.standing { border-bottom: 1px solid var(--border); }
.standing-link {
  display: flex; align-items: center; gap: 12px;
  padding: 12px 0;
  /* min-height keeps the tap target at least --tap even for a one-line name, which
     is the same floor .teamrow and .bet-main use. */
  min-height: var(--tap);
  /* colour: inherit is required, not cosmetic: an unstyled anchor renders in the
     browser's default link colour, which would turn the entire standings table blue
     and make every value look like a link. */
  color: inherit; text-decoration: none;
}
/* A hover cue that is a SURFACE change, not a fade. The previous rule faded the row with
   opacity: .78, which multiplied every colour inside it - the delta of a winning row dropped from
   about 5.7:1 to 3.4:1, under the 4.5:1 floor, in a state no automated check measures because
   nothing hovers. Tinting matches the idiom already used by .topbar-actions a:hover. */
.standing-link:hover { background: var(--surface-2); }
.standing.me { background: color-mix(in srgb, var(--accent) 7%, transparent); }
.rank-num { width: 28px; color: var(--text-2); font-weight: 600; text-align: right; }
.who { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.who-name { font-weight: 500; overflow-wrap: anywhere; }
/* The second line is not decoration: SPEC §7 breaks ties on Net Cycles then win
   percentage, so all three numbers must be visible to explain the order. */
.who-why { font-size: 13px; color: var(--text-2); }
/* flex: none stops the figures being squeezed to their minimum width. They are the
   headline number of the row and must not shrink to make room for a long name;
   min-width: 0 on .who above is the other half of the same fix. */
.pool-figures {
  flex: none;
  display: flex; flex-direction: column; align-items: flex-end; gap: 1px;
  text-align: right;
}
.tokens { font-weight: 600; font-size: 18px; }
/* Movement this week. Suppressed at zero, so the column is a signal rather than a
   field - and a "+5" the player can see against a "-5" is what makes the weekly
   penalty and floor legible in the standings. */
.pool-delta { font-size: 12px; font-weight: 600; }
.pool-delta.up { color: var(--win); }
.pool-delta.down { color: var(--loss); }
/* The movement figure and the bonus badges share one row: both describe the week being
   viewed, so they sit together while the season record stays on the left. The badges come
   first and the signed movement last, so the +x/-x is the right-most thing on the row. */
.pool-week { display: flex; align-items: center; gap: 6px; }
/* A bonus badge earned in the week being viewed. It is a filled CHIP, not a bare glyph: at
   14px of muted grey the four bonuses were four grey outlines, which is the opposite of
   what a badge is for (the owner's words: "too plain and don't stand out").

   Each kind paints itself from the token its meaning already uses elsewhere in the app -
   --rink for a cycle, --win for a perfect week, --pending for the money bonus, --loss for
   the perfect 32 - so the colour reads as a category. The ink is --surface, so dark mode
   swaps fill and ink together and the contrast is the token's own: white ink on the dark
   light-mode tokens, near-black ink on the light dark-mode ones. */
.pool-badge, .badge-legend-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 24px; height: 24px; flex: none;
  border-radius: 50%;
  color: var(--surface);
  font-size: 14px; line-height: 1;
}
.pb-cycle { background: var(--rink); }
.pb-star  { background: var(--win); }
.pb-coin  { background: var(--pending); }
.pb-flame { background: var(--loss); }
/* The no-bet penalty. It is the one badge that is NOT a prize, so it does not take the
   filled treatment the bonuses share: a soft-red chip with red ink and a red ring reads
   as a deduction rather than an award, and keeps it visibly apart from the red filled
   "perfect 32" flame it can sit beside in a row. */
.pb-idle { background: var(--loss-soft); color: var(--loss); box-shadow: inset 0 0 0 1.5px var(--loss); }
/* The badge owns its own hover target: the ::before widens it to about 34px without
   touching the layout, because 24px is easy to see and awkward to point at - and "hard to
   hover over them" was the complaint. cursor: help also says this is information and not
   another link, inside a row that is itself one big link.

   The inset MUST stay under .pool-week's 6px gap: at -6px two adjacent badges' hover areas
   meet exactly, so pointing at the gap shows the second badge's tooltip, and anything wider
   would reach the movement figure and swallow its own tooltip. */
.pool-badge { position: relative; cursor: help; }
.pool-badge::before { content: ""; position: absolute; inset: -5px; border-radius: 50%; }
/* What the badge means, on hover. It is a child element and NOT a `title`: a native title
   tooltip waits about a second, is drawn by the OS at a fixed small size, and had to
   compete with the row's own link cursor, so hovering a badge mostly felt like hovering
   the link.

   display: none, not visibility: hidden - an absolutely positioned tooltip that is merely
   invisible still counts toward scrollable overflow, and the mobile smoke run measures
   exactly that. aria-hidden keeps it out of the accessibility tree, so the aria-label is
   announced once rather than twice. */
.pool-tip {
  display: none; position: absolute; bottom: calc(100% + 8px); right: -4px; z-index: 5;
  padding: 6px 10px; border-radius: var(--radius-sm);
  background: var(--text); color: var(--surface);
  font-size: 12px; font-weight: 500; line-height: 1.3; white-space: nowrap;
  box-shadow: 0 4px 12px rgb(0 0 0 / 0.25);
}
.pool-badge:hover .pool-tip { display: block; }
/* The no-bet penalty chip on Home. Unlike a standings badge it carries a VISIBLE label:
   Home has no "what the badges mean" legend, so the words must be readable without a
   pointer, and a bare glyph would put the whole meaning on the shape and the colour.
   It is a filled icon disc inside a soft-red pill - the same loss palette the Pool badge
   uses, so one meaning keeps one colour across both screens. */
.penalty {
  display: inline-flex; align-items: center; gap: 8px;
  margin: 12px 0 0; padding: 6px 12px 6px 7px;
  border-radius: 999px;
  background: var(--loss-soft); color: var(--loss);
  box-shadow: inset 0 0 0 1px var(--loss);
  font-size: 13px; font-weight: 600; line-height: 1;
}
.penalty-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; flex: none; border-radius: 50%;
  background: var(--loss); color: var(--surface);
}
.penalty-icon .icon { width: 16px; height: 16px; }
/* "What the badges mean": the no-JS legend at the bottom of the Pool page. A plain
   <details> is tappable on touch and needs no script (SPEC §19). */
.badge-legend { margin-top: 16px; font-size: 13px; color: var(--text-2); }
.badge-legend summary { cursor: pointer; color: var(--text); font-weight: 500; padding: 10px 0; }
.badge-legend ul { list-style: none; margin: 4px 0 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.badge-legend li { display: flex; align-items: center; gap: 8px; }

/* --- ledger -------------------------------------------------------------- */
/* The note above a filtered ledger, and the route back to the full history. UX-PLAN.md
   promises a bet row opens the ledger filtered to that bet; a filter with no way out
   traps the player in one bet's history. */
.ledger-note {
  margin: 8px 0 0; padding: 10px 12px; border-radius: var(--radius-sm);
  background: var(--surface-2); color: var(--text-2); font-size: 14px;
}
.ledger { list-style: none; margin: 0; padding: 0; }
.ledger-row {
  display: grid; grid-template-columns: 84px 1fr auto auto;
  gap: 10px; align-items: baseline;
  padding: 10px 0; border-bottom: 1px solid var(--border);
  font-size: 15px;
}
.ledger-when { color: var(--text-2); font-size: 13px; }
.ledger-label { color: var(--text); }
.ledger-amount { text-align: right; font-weight: 600; }
.ledger-balance { color: var(--text-2); font-size: 13px; min-width: 44px; text-align: right; }

/* --- rules ---------------------------------------------------------------
 *
 * The rules page mixes a CORE mechanic (every bet pays double) with OPTIONAL
 * awards (the bonuses). Presenting them identically made the page read as a list
 * of bonuses, so the mechanic was invisible. The two now look different: the
 * mechanic is a highlighted callout, the bonuses are an ordinary list under a
 * heading that says they are extra.
 */
.prose h2 {
  margin: 28px 0 8px;
  font-size: 17px;
  color: var(--rink);
  padding-bottom: 4px;
  border-bottom: 2px solid var(--accent-soft);
}
.prose h2:target { color: var(--accent); }
.prose p, .prose li { color: var(--text-2); }
.prose ul { padding-left: 20px; }
.prose strong { color: var(--text); }

/* The core mechanic gets a callout, so it cannot be skimmed past. */
.prose .rules-core {
  background: var(--accent-soft);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius-sm);
  padding: 10px 12px;
  margin: 12px 0;
}
.prose .rules-core strong { color: var(--accent); }

/* Bonuses: a scannable list where the amount is the anchor. */
.rules-list { list-style: none; padding-left: 0; }
.rules-list li {
  position: relative;
  padding: 8px 0 8px 16px;
  border-bottom: 1px solid var(--border);
}
.rules-list li:last-child { border-bottom: 0; }
.rules-list li::before {
  content: "";
  position: absolute;
  left: 0; top: 16px;
  width: 5px; height: 5px;
  border-radius: 50%;
  background: var(--rink);
}

/* --- sign-in ------------------------------------------------------------- */
.signin { text-align: center; padding: 48px 0; }
.signin h1 { font-size: 28px; margin-bottom: 8px; }
.tagline { color: var(--text-2); margin: 0 0 28px; }
.fineprint { font-size: 13px; color: var(--text-muted); margin-top: 16px; }
.field-label { display: block; text-align: left; font-size: 14px; margin-bottom: 6px; color: var(--text-2); }
.input {
  width: 100%; min-height: var(--tap); padding: 0 12px; margin-bottom: 12px;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--surface); color: var(--text); font: inherit;
}

/* --- desktop: the same column, centred ------------------------------------ */
@media (min-width: 720px) {
  .app { border-left: 1px solid var(--border); border-right: 1px solid var(--border); }
}

/* --- A:B logo experiment (temporary, /picks-logos) ------------------------
 *
 * The crest is intentionally SMALL and sits beside the tricode rather than
 * replacing it. Two reasons:
 *
 *   1. The tricode is what a hockey fan reads fastest at a glance, and it is the
 *      identity that survives a 14px chip. A crest at this size is decoration.
 *   2. This is the variable under test. If the logo arm also changed the layout,
 *      a reviewer's preference could not be attributed to the logos.
 *
 * The height is fixed and the width is derived from the viewBox ratio (960x640 =
 * 3:2), so rows do not shift as 40 images load.
 */
.teamlogo {
  display: inline-block;
  width: 24px;
  height: 16px;
  object-fit: contain;
  flex: 0 0 auto;
  vertical-align: middle;
}
.teamlogo-chip { width: 16px; height: 11px; margin-right: 4px; }

/* The chips already have tight padding, so a crest inside one needs the text
   nudged back to centre. */
.chip .teamlogo-chip { vertical-align: -1px; }

/* A banner naming the experiment, so a reviewer never mistakes the variant for
   the real screen. */
.ab-banner {
  margin: 0 0 var(--gap);
  padding: 8px 12px;
  border: 1px dashed var(--pending);
  border-radius: var(--radius-sm);
  font-size: 13px;
  color: var(--text-2);
}

/* --- schedule week navigator --------------------------------------------- */
.weeknav {
  display: flex; align-items: center; justify-content: space-between;
  gap: 8px; margin-bottom: 12px; font-size: 14px;
}
.weeknav a, .weeknav span {
  display: inline-flex; align-items: center; min-height: 44px; padding: 0 4px;
}
.weeknav-now { color: var(--text-2); font-weight: 600; }
/* A disabled control must look disabled, not absent: its absence would read as
   "there is no next week" rather than "we have not synced that far". */
.weeknav-end { color: var(--text-muted); cursor: not-allowed; }

/* A read-only week: one notice, and a faint tint across the whole table so the state
   is legible from anywhere on the page rather than only at the top. */
.week-readonly { margin: 0 calc(-1 * var(--gap)); padding: 0 var(--gap); background: var(--surface-2); }
.week-readonly .week-note { background: transparent; }

.week-note {
  margin: 0 0 var(--gap);
  padding: 8px 12px;
  border-left: 3px solid var(--pending);
  background: var(--surface-2);
  border-radius: var(--radius-sm);
  font-size: 13px;
  color: var(--text-2);
}

.daygroup { margin-bottom: var(--gap); }
/* The day header is sticky so a long slate stays anchored to its date while
   scrolling - the same reason the original design used sticky day headers. */
.dayhead {
  position: sticky; top: 56px;
  display: flex; align-items: center; gap: 8px;
  font-size: 13px; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--text-2);
  background: var(--surface);
  padding: 8px 0;
  margin: 0;
  font-weight: 600;
}
.dayhead-today { color: var(--accent); }
.daycount {
  margin-left: auto;
  font-weight: 400;
  text-transform: none;
  letter-spacing: 0;
  color: var(--text-muted);
}

/* --- simulated-time banner ------------------------------------------------
 *
 * Deliberately loud. A tester who cannot tell whether they are looking at real or
 * simulated time will misread every deadline on the page, and the failure is
 * silent - the numbers all look plausible.
 */
.simtime {
  margin: 0 var(--gap) var(--gap);
  padding: 10px 12px;
  border: 2px dashed var(--pending);
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--pending) 10%, transparent);
  color: var(--pending);
  font-size: 13px;
}

/* ---------------------------------------------------------------------------
 * COLOUR, AND WHY THERE IS NOW MORE OF IT
 *
 * The first version used the accent in exactly one place per page - the primary
 * button - and everything else was grey. The owner's report was "I do not see any
 * colour in this app, it's all black and white". They were right: the restraint in
 * docs/UX-PLAN.md was aimed at not looking like a gambling app, and it overshot
 * into looking like a wireframe.
 *
 * The principle now: colour carries MEANING, and is used generously for that.
 * Nothing here is decoration.
 *
 *   accent (blue)   the primary action, and the current/active thing
 *   rink (teal)     the schedule and cycle progress - the "where am I in the season"
 *                   family, deliberately distinct from the action family
 *   win/loss/pending  outcomes, always with an icon AND a signed number, never
 *                   colour alone (WCAG 1.4.1 - and in a 32-person pool, two or
 *                   three players will not distinguish red from green)
 * ------------------------------------------------------------------------- */

/* --- header and section colour ------------------------------------------- */

/* The page header gets an accent underline, so every screen opens with a colour
   cue rather than a field of grey text. */
.page-head {
  padding-bottom: 10px;
  border-bottom: 3px solid var(--accent-soft);
}
.page-head h1 { color: var(--accent); }

/* Section headings are the rink family: they label the season, not an action. */
.panel-title, .group-title {
  color: var(--rink);
  display: flex; align-items: center; gap: 8px;
}
.panel-title::before, .group-title::before {
  content: "";
  width: 3px; height: 12px; border-radius: 2px;
  background: var(--rink);
  flex: 0 0 auto;
}

/* The balance is the headline; give it the accent so Home opens with colour. */
.balance-num { color: var(--accent); }

/* --- pills and badges: filled, not outlined ------------------------------ */
.pill {
  background: var(--accent-soft);
  border-color: var(--accent-edge);
  color: var(--accent);
  font-weight: 600;
}

/* --- the call to action block -------------------------------------------- */
.cta {
  background: var(--accent-soft);
  border-color: var(--accent-edge);
}
.cta-lead { color: var(--accent); }

/* --- the cycle bar: the single most colourful element, deliberately ------- */
.cycle-seg { background: var(--border); height: 16px; }
.cycle-seg.used { background: var(--rink); }

/* --- team rows and fixtures ---------------------------------------------- */

/* The tricode is the identity a hockey fan reads fastest, so it is the style
   anchor for the whole list. */
.tricode { color: var(--text); }

.teampick { border-bottom: 1px solid var(--border); }
.teamrow:last-child .teampick { border-bottom: 0; }

.teamrow-summary {
  display: flex; align-items: center; gap: 10px;
  min-height: var(--tap);
  padding: 10px 0;
  cursor: pointer;
  list-style: none;
  /* Allow the metadata line to move to a second line rather than widening the page.
     At 195px (200% zoom) the nowrap "next Tue 29 Sep · 8:00pm" is what pushes /picks past
     the viewport; wrapping it is what lets the page reflow instead of pan. */
  flex-wrap: wrap;
}
.teamrow-summary::-webkit-details-marker { display: none; }
.teamrow-summary .tricode { font-size: 15px; min-width: 44px; }

/* How many games the team plays this week - the information the screen was
   missing entirely. */
.teamrow-games {
  font-size: 12px;
  font-weight: 600;
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--accent-soft);
  color: var(--accent);
  border: 1px solid var(--accent-edge);
  white-space: nowrap;
}
/* The collapsed row reports what the player can DO: how many games are still
   open, how many already have a bet, and when the next one is. A bare time was
   removed because a time with no day is meaningless in a week's list. */
.teamrow-games-none { background: var(--surface-2); color: var(--text-muted); border-color: var(--border); }
.teamrow-bet {
  font-size: 12px; font-weight: 600; padding: 2px 8px; border-radius: 999px;
  background: var(--win-soft); color: var(--win); border: 1px solid color-mix(in srgb, var(--win) 30%, transparent);
  white-space: nowrap;
}
.teamrow-next { margin-left: auto; font-size: 13px; color: var(--text-2); white-space: nowrap; }
.teamrow-caret { color: var(--text-muted); font-size: 12px; transition: transform .15s; }
details[open] > .teamrow-summary .teamrow-caret { transform: rotate(180deg); }
details[open] > .teamrow-summary { border-bottom: 1px solid var(--border); }

/* The fixtures, one per game, on a tinted background so the expanded group reads
   as belonging to the row above it. */
.fixturelist {
  list-style: none; margin: 8px 0 12px; padding: 0;
  background: var(--surface-2);
  border-radius: var(--radius-sm);
  overflow: hidden;
}

/* Each day gets a heading, because a fixture list spanning a week is unreadable
   as bare times - the owner's third report was exactly this. */
.fixtureday + .fixtureday { border-top: 1px solid var(--border); }
/* The day heading is the key missing information on this screen, so it is set at the
   same weight as the other section headings rather than as a micro-label. At 11px it
   was the smallest text in the app while carrying the most important fact on the row -
   the eye had no anchor in a list of identical teams.
   13px matches .group-title and .dayhead, so the app has one heading size, not three. */
.fixtureday-head {
  margin: 0;
  padding: 10px 12px 6px;
  font-size: 13px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--rink);
}
.fixtureday-list { list-style: none; margin: 0; padding: 0; }
.fixture {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 12px;
  min-height: var(--tap);
  border-bottom: 1px solid var(--border);
}
.fixture:last-child { border-bottom: 0; }
.fixture.dimmed .fixture-opp, .fixture.dimmed .fixture-when { color: var(--text-dim-2); }
.fixture-when { color: var(--text-2); font-size: 13px; min-width: 74px; }
.fixture-opp { flex: 1; font-size: 14px; }
.fixture-opp .tricode { font-size: 13px; }
.fixture-action { display: flex; align-items: center; }

/* --- schedule link at the top of picks ----------------------------------- */
.quicklinks { margin-bottom: var(--gap); }
.quicklinks-sub { margin: 6px 0 0; font-size: 13px; color: var(--pending); text-align: center; }

/* --- day headers in the schedule ---------------------------------------- */
.dayhead { color: var(--rink); }
.dayhead-today { color: var(--accent); }
.dayhead-today .pill { background: var(--accent-fill); color: var(--accent-ink); border-color: var(--accent-fill); }

/* --- outcome colours as fills, so a won bet looks won at a glance --------- */
.bet-won  { background: linear-gradient(90deg, var(--win-soft), transparent 60%); }
.bet-lost { background: linear-gradient(90deg, var(--loss-soft), transparent 60%); }
.bet-pending { background: linear-gradient(90deg, var(--pending-soft), transparent 60%); }
/* A withdrawn bet is dimmed by colour, not opacity - see the .dimmed note above. */
.bet-void .tricode, .bet-void .bet-match, .bet-void .bet-when { color: var(--text-dim-2); }

.tag-final { background: var(--surface-2); color: var(--text-2); border: 1px solid var(--border); }
.tag-bet { background: var(--accent-soft); color: var(--accent); border: 1px solid var(--accent-edge); }
/* A placed bet on a FINAL game: green when the pick won, red when it lost, so the schedule
   reads its own results at a glance. It stays a tag-bet - the settled/awaiting distinction
   lives in the text on My Bets - so only the hue changes. */
.tag-bet.won  { background: var(--win-soft);  color: var(--win);  border-color: color-mix(in srgb, var(--win) 30%, transparent); }
.tag-bet.lost { background: var(--loss-soft); color: var(--loss); border-color: color-mix(in srgb, var(--loss) 30%, transparent); }

/* --- ledger: income green, outgoing red, notes muted --------------------- */
.ledger-amount.fig-up { background: var(--win-soft); }
.ledger-amount.fig-down { background: var(--loss-soft); }

/* --- the tab bar: colour on the active tab, so the app is never all grey -- */
.tabbar a.active { color: var(--accent); }
.tabbar a.active .icon { transform: translateY(-1px); }

/* The top-of-page progress strip: a thin accent line that makes the app feel
   alive without adding chrome. */
.topbar { border-bottom-color: var(--accent-edge); }

/* --- a placed bet links through to My Bets --------------------------------
 *
 * The tag was a dead end: it showed that a bet existed but gave the player no way
 * to act on it, and the reason to act is to cancel. The link goes to the bet's own
 * row via an anchor, so following it lands on that bet rather than at the top of
 * the list.
 */
.tag-bet-link {
  display: inline-flex; align-items: center; min-height: var(--tap); padding: 0 10px;
  text-decoration: none;
  cursor: pointer;
}
.tag-bet-link:hover { text-decoration: underline; }

/* A "back" control is a link, so it owes the same target as any other control. As plain prose
   it measured 18px tall - the smallest target in the app. min-height plus inline-flex gives it
the full tap height without changing where it sits. */
.backlink { display: inline-flex; align-items: center; min-height: var(--tap); }

/* The anchor target is highlighted so the player can see which bet they arrived
   at, rather than scanning a list of thirty for a match. */
.bet:target {
  background: var(--accent-soft);
  box-shadow: inset 3px 0 0 var(--accent);
  border-radius: var(--radius-sm);
}
.bet:target .bet-main { font-weight: 600; }

/* --- the simulated-clock brand -------------------------------------------
 *
 * Replaces the app title while a fake clock is active, so the date is the first
 * thing on the screen. Set in the urgency colour and monospace-ish tabular figures
 * so it reads as an instrument reading rather than as branding.
 */
.brand-clock {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--pending);
  font-variant-numeric: tabular-nums;
}

/* --- admin dashboard (SPEC §27) -------------------------------------------
 *
 * Desktop-shaped, deliberately. The mobile-first rule governs what a player uses on a
 * phone twice a week; the operator is at a keyboard, and compressing twenty figures into
 * 390px would make the one screen built for diagnosis unreadable. The content column
 * widens past the player-facing 640px for the same reason.
 */
.app:has(.admin-section) { max-width: 1000px; }

.admin-section {
  margin: 0 0 28px;
  padding-top: 16px;
  border-top: 1px solid var(--border);
}
.admin-section h2 {
  font-size: 13px; text-transform: uppercase; letter-spacing: 0.08em;
  color: var(--rink); margin: 0 0 12px;
}

/* Settings is a PLAYER screen, so it must not opt into the admin console's 1000px column.

   It borrows the admin section class deliberately NOT: :has() matches on descendant class
   presence ANYWHERE on the page, not on "this is the console", so sharing the name silently
   widened Settings from 640px to 998px and restyled its headings as a 13px admin eyebrow.
   Its own heading size is the documented 18px section heading. */
.settings-section {
  margin: 0 0 28px;
  padding-top: 16px;
  border-top: 1px solid var(--border);
}
.settings-section h2 { font-size: 18px; font-weight: 600; margin: 0 0 12px; }

/* The narrow-viewport contract for the header.

   The bar is a two-item space-between flex row in which neither item could shrink: the
   wordmark is a single unbreakable token (min-content 88.9px) and the actions hold three fixed
   tap targets, giving a ~285px floor. At 200% zoom on a 390px phone the layout width is 195px,
   so every page panned horizontally. Let both items shrink, and ellipsise the wordmark. */
.topbar { gap: 8px; }
.brand { min-width: 0; }
.brand > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.topbar-actions { flex: 0 0 auto; }
@media (max-width: 360px) {
  .topbar { padding: 12px 12px; }
  .brand { gap: 8px; }
  .topbar-actions { gap: 0; }
  .topbar-actions a { min-width: 32px; }   /* still well above WCAG 2.5.8's 24px floor */
}

/* The loudest thing on the page when something is wrong, and absent when it is not -
   a warning that is always present is one nobody reads. */
.admin-alert {
  margin: 0 0 20px; padding: 12px 14px;
  border: 2px solid var(--loss);
  border-left-width: 5px;
  border-radius: var(--radius-sm);
  background: var(--loss-soft);
  color: var(--loss);
  font-size: 14px;
}
.admin-alert ul { margin: 8px 0 0; padding-left: 20px; font-size: 13px; }
.admin-ok {
  margin: 0 0 20px; padding: 10px 14px;
  border-left: 4px solid var(--win);
  background: var(--win-soft);
  color: var(--win);
  border-radius: var(--radius-sm);
  font-size: 14px;
  font-weight: 600;
}

.admin-table { width: 100%; border-collapse: collapse; font-size: 14px; }
.admin-table th {
  text-align: left; font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--text-2); padding: 6px 8px; border-bottom: 2px solid var(--border);
}
.admin-table td { padding: 9px 8px; border-bottom: 1px solid var(--border); vertical-align: top; }
.admin-table-compact td { padding: 6px 8px; font-size: 13px; }
.admin-bad { background: var(--loss-soft); }

.admin-good-text { color: var(--win); font-weight: 600; }
.admin-bad-text { color: var(--loss); font-weight: 600; }
.admin-warn-text { color: var(--pending); }
.admin-dim { color: var(--text-muted); font-size: 12px; }

.admin-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
  gap: 10px;
}
.admin-stat {
  display: flex; flex-direction: column; gap: 2px;
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-left: 4px solid var(--accent);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
}
.admin-stat.admin-warn { border-left-color: var(--pending); background: var(--pending-soft); }
.admin-num { font-size: 26px; font-weight: 700; color: var(--accent); line-height: 1.1; }
.admin-stat.admin-warn .admin-num { color: var(--pending); }
.admin-label { font-size: 13px; font-weight: 600; }
.admin-sub { font-size: 12px; color: var(--text-2); }
.admin-note { margin: 12px 0 0; font-size: 13px; color: var(--text-2); }
.admin-note code {
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: 4px; padding: 1px 5px; font-size: 12px;
}

/* --- admin console navigation and actions -------------------------------- */
.admin-nav { display: flex; gap: 4px; margin: 0 0 20px; border-bottom: 1px solid var(--border); }
.admin-nav a {
  padding: 8px 14px; font-size: 14px; font-weight: 600;
  color: var(--text-2); text-decoration: none;
  border-bottom: 3px solid transparent;
}
.admin-nav a.on { color: var(--accent); border-bottom-color: var(--accent); }
.admin-nav a:hover { color: var(--accent); }

/* Corrections sit side by side on a desktop: they are alternatives, and putting them in
   one row makes that visible. They stack on a narrow window. */
.admin-actions { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 16px; }
.admin-action {
  padding: 14px 16px;
  border: 1px solid var(--border);
  border-top: 3px solid var(--accent);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
}
.admin-action h3 { margin: 0 0 4px; font-size: 15px; }
.admin-action .input { margin-bottom: 8px; }
.admin-action .btn { margin-top: 4px; }

.admin-avatar {
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; border-radius: 50%;
  color: #fff; font-size: 11px; font-weight: 700;
  margin-right: 6px; vertical-align: middle;
}
.admin-dim-row .admin-id, .admin-dim-row td { color: var(--text-dim-2); }

/* --- legal pages and console sign-in ------------------------------------- */

/* legal-links sits under the sign-in button. It is separated by a rule so it reads as
   "terms apply" rather than as part of the button's own label, which matters because it is
   the text that carries consent. */
.legal-links {
  border-top: 1px solid var(--border);
  padding-top: 12px;
  margin-top: 20px;
  font-size: 12px;
}

/* .prose is shared with the rules page; the legal pages only need a tighter heading rhythm
   because they are longer and read as one document. */
.prose h2 { margin-top: 28px; }

/* The console sign-in form. Block labels above full-width fields: the same shape as the
   development sign-in, so the two forms feel like the same application. */
.signin-form { display: flex; flex-direction: column; gap: 6px; }
.signin-form label { font-size: 13px; font-weight: 600; margin-top: 8px; }
.signin-form input {
  width: 100%;
  padding: 12px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface);
  color: var(--text);
  /* 16px minimum stops iOS Safari zooming the viewport when the field is focused, which
     otherwise makes the form jump on every sign-in from a phone. */
  font-size: 16px;
}
.signin-form button { margin-top: 16px; }

/* The unconfigured-console panel carries a shell snippet, which must not overflow on a
   narrow screen. */
.signin-form pre, .card pre {
  overflow-x: auto;
  background: var(--surface-alt, rgba(127, 127, 127, 0.12));
  border-radius: 8px;
  padding: 10px 12px;
  font-size: 13px;
}

/* The avatar palette. These must agree with avatarClass in src/app/security.go, which is what
   turns a stored colour into one of these class names. A mismatch shows as an uncoloured
   circle, not as a broken page. */
.admin-avatar.av0 { background: #1d4ed8; }
.admin-avatar.av1 { background: #b45309; }
.admin-avatar.av2 { background: #15803d; }
.admin-avatar.av3 { background: #b91c1c; }
.admin-avatar.av4 { background: #7c3aed; }
.admin-avatar.av5 { background: #0e7490; }
.admin-avatar.av6 { background: #a21caf; }
.admin-avatar.av7 { background: #4d7c0f; }

/* The email on the admin player list. Monospace and small so it reads as data rather than as
   part of the name, and it wraps rather than overflowing the column on a narrow screen. */
.admin-email {
  font-size: 12px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  overflow-wrap: anywhere;
}

/* The user ID on the player list and player page. Same treatment as the email and for the
   same reason: it is an opaque identifier an operator copies, not prose they read. */
.admin-id {
  font-size: 11px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  overflow-wrap: anywhere;
  color: var(--text-muted);   /* was opacity: 0.8 - an operator copies this id, so it must stay legible */
}

/* The build id, pinned to the right end of the console tab strip.

   margin-left:auto is what pushes it away from the tabs, so it reads as page metadata rather
   than as a fifth tab that happens to be unclickable. It is deliberately muted: it is the
   answer to "which version is this?" during an incident, not something to compete with the
   navigation for attention. */
.admin-version {
  margin-left: auto;
  align-self: center;
  padding: 4px 10px;
  font-size: 11px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  color: var(--text-2);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 999px;
  white-space: nowrap;
}

/* The player-list filter and search strip.

   It WRAPS rather than scrolling horizontally. The console is a desktop screen, but an
   operator on a laptop with a narrow window is common enough that a strip which scrolled
   sideways would hide the search box - the one control on it that cannot be guessed from a
   count alone. */
.admin-filters {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin: 0 0 16px;
}
.admin-filters a {
  padding: 5px 12px;
  font-size: 13px;
  font-weight: 600;
  color: var(--text-2);
  text-decoration: none;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface-2);
}
.admin-filters a.on { color: var(--accent-ink); background: var(--accent-fill); border-color: var(--accent-fill); }
.admin-filters a:hover { border-color: var(--accent); color: var(--accent); }
.admin-filters a.on:hover { color: var(--accent-ink); }

/* The search form sits at the end of the strip and must not stretch. */
.admin-search { display: flex; gap: 6px; margin-left: auto; }
.admin-search .input { width: 200px; margin: 0; padding: 6px 10px; font-size: 13px; }

/* The orb on the sign-in screen. Larger than the header's, because this is the one
   screen with room to show the object properly, and it is the only thing a stranger
   sees before deciding whether to sign in. */
.signin-orb {
  display: block;
  width: 96px; height: 96px;
  margin: 0 auto 12px;
  filter: drop-shadow(0 4px 14px rgb(0 0 0 / 0.28));
}
@media (prefers-color-scheme: dark) {
  .signin-orb { filter: drop-shadow(0 4px 16px rgb(0 0 0 / 0.65)) brightness(1.08); }
}

/* --- settings ------------------------------------------------------------ */

/* The rename form. Constrained to a readable measure rather than the full width of a
   desktop column: a name field 600px wide suggests a longer answer than 20 characters. */
.settings-form { max-width: 320px; }
.settings-form .input { margin-bottom: 4px; }
.settings-form .btn { margin-top: 12px; }

/* Sign-out is deliberately understated. It sits on the settings page beside the useful
   controls, and a prominent destructive button is the one people press by accident. */
.settings-signout { margin-top: 4px; }

/* --- home: the welcome line ---------------------------------------------- */

/* Sits above the balance, so it is the first thing read: it names the player and offers
   the way to change that name. Muted and small, because the balance is still the headline
   figure on this screen and two competing large elements would fight. */
.welcome {
  display: flex; align-items: center; justify-content: center; gap: 6px;
  margin: 0 0 4px;
  font-size: 14px;
  color: var(--text-2);
}
.welcome-text { overflow-wrap: anywhere; }

/* The pencil. Sized and spaced to meet the 44px touch-target minimum without drawing a
   44px circle, which would visually outweigh the name it sits beside - the padding does
   the work, and the visible glyph stays small. */
.welcome-edit {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 44px; min-height: 44px;
  margin: -12px -8px -12px -6px;
  padding: 0;
  border-radius: 50%;
  color: var(--text-muted);
}
.welcome-edit:hover, .welcome-edit:focus-visible {
  color: var(--accent);
  background: var(--surface-2);
  text-decoration: none;
}

/* --- alert acknowledgement -------------------------------------------------

   The acknowledgement form sits inline under each alert's detail text. It is intentionally
   NOT a single "dismiss" button: it asks for a reason and a number of days, because an alert
   silenced without either is a mute nobody can audit, and the alert has to be able to come
   back on its own. */

.admin-alert-list { margin: 8px 0 0; padding-left: 18px; }
.admin-alert-list li { margin-bottom: 12px; }
.admin-alert-head { margin-bottom: 2px; }

.admin-ack {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: center;
  margin-top: 8px;
}
.admin-ack .input { margin: 0; padding: 5px 9px; font-size: 13px; }
.admin-ack .input[type="text"] { flex: 1 1 220px; min-width: 180px; }
/* The day count is narrow on purpose: it is a small integer and a wide field invites a typo. */
.admin-ack .input-days { width: 72px; flex: 0 0 auto; }

/* Acknowledged alerts get their own panel rather than sharing the red one. They are not
   "wrong right now" - they are "reviewed and expected" - and putting them in the same visual
   container would make the reassuring case indistinguishable from the alarming one at a
   glance, which is the only thing this panel is for. */
.admin-ack-panel {
  border-left: 3px solid var(--border);
  background: var(--surface-2);
  border-radius: var(--radius-sm);
  padding: 12px 14px;
  margin: 0 0 16px;
  font-size: 14px;
  color: var(--text-2);
}

/* --- the CSV export row ------------------------------------------------------

   Given its own row rather than sharing the filter strip's. The filters narrow this page; the
   export downloads something else entirely (the whole roster, as a file). Side by side, the
   reasonable reading is "this exports what I have filtered", which is wrong and would go
   unnoticed until it mattered. */

.admin-export {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  margin: 0 0 18px;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  font-size: 13px;
}
.admin-export .btn { flex: 0 0 auto; }
