/* app.css — sized for an iPad held in landscape, outdoors, in a hurry.
   Two rules drive most of the choices here:
     1. Touch targets are large. A mis-tap during a game is a corrupted stat.
     2. Contrast is high. This gets used in direct sunlight. */

:root {
  --bg: #f4f6f5;
  --panel: #ffffff;
  --ink: #14211c;
  --muted: #5d6b65;
  --line: #d3dbd7;
  --brand: #0b6e4f;
  --good: #1a7f37;
  --bad: #b3261e;
  --warn: #8a5a00;
  --file: #b8560f;   /* the CSV/JSON marks on the export cards */
  --file-ink: #ffffff;
  --sel: #0b6e4f;
  --radius: 14px;
  --tap: 4.25rem; /* minimum comfortable button height */
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #101512; --panel: #182018; --ink: #e8f0ea; --muted: #9aa8a1;
    --line: #2b3630; --brand: #39b487; --good: #4cc38a; --bad: #f2726a; --warn: #d9a441; --file: #e08a4b; --file-ink: #1a1207;
    --sel: #39b487;
  }
}

/* The normal preference follows the device. The button in the top bar is a
   deliberate override for a bright sideline, and wins over that preference. */
:root[data-theme="dark"] {
  --bg: #101512; --panel: #182018; --ink: #e8f0ea; --muted: #9aa8a1;
  --line: #2b3630; --brand: #39b487; --good: #4cc38a; --bad: #f2726a; --warn: #d9a441; --file: #e08a4b; --file-ink: #1a1207;
  --sel: #39b487;
}

* { box-sizing: border-box; }

/* A class like .selection-bar { display: flex } would otherwise beat the
   browser's own rule for [hidden], leaving "hidden" elements visible. */
[hidden] { display: none !important; }

body {
  margin: 0;
  font: 16px/1.4 -apple-system, BlinkMacSystemFont, "SF Pro Text", system-ui, sans-serif;
  background: var(--bg);
  color: var(--ink);
  /* Stop iOS from treating fast repeated taps as double-tap-to-zoom, and stop
     the rubber-band scroll that makes tapping feel unreliable mid-game. */
  touch-action: manipulation;
  -webkit-user-select: none;
  user-select: none;
  overscroll-behavior: none;
  padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
}

h2 { font-size: 1.1rem; margin: 0 0 .6rem; }
h3 { font-size: .95rem; margin: 0; }
.hint { color: var(--muted); font-size: .82rem; }
/* The browser default is #0000EE, which is unreadable on the dark panel. */
a { color: var(--brand); }
.spacer { flex: 1; }
.row { display: flex; gap: .5rem; align-items: center; }
.wrap { flex-wrap: wrap; }
/* A heading in a row is one item among several, not a block with something
   under it. Left with its bottom margin it sat above the middle of everything
   beside it, which is what made those header rows look tipped — and where the
   baseline hid that, the margin still padded the row out by its own depth.
   Descendant rather than child, so a heading grouped with its neighbours (see
   .season-head) is covered too. */
.row h2 { margin-bottom: 0; }

/* ---------- chrome ---------- */
#tabs {
  display: flex; gap: .25rem; align-items: center;
  padding: .4rem .6rem; background: var(--panel); border-bottom: 1px solid var(--line);
  position: sticky; top: 0; z-index: 20;
}
/* Set apart from the storage note it sits beside: they are two unrelated facts,
   and at .25rem of gap they read as one sentence ending "each game v101". */
#app-version {
  margin-left: .8rem; padding-left: .8rem;
  border-left: 1px solid var(--line);
  font-variant-numeric: tabular-nums;
}

.tab {
  background: none; border: none; color: var(--muted);
  font-size: .95rem; font-weight: 600; padding: .55rem .9rem; border-radius: 10px;
}
.tab.active { background: var(--brand); color: #fff; }
/* A compact global setting, kept beside the tabs so it is reachable before a
   game begins without taking room from the large live-game controls. Sun on the
   left means the bright-field palette; moon on the right is the dark palette. */
.theme-toggle {
  position: relative; display: inline-flex; align-items: center; justify-content: space-between;
  width: 4.7rem; min-height: 2.35rem; padding: .22rem .42rem;
  border-color: var(--line); border-radius: 999px; background: var(--panel);
  color: var(--muted); font-size: 1.05rem; line-height: 1;
}
.theme-icon { position: relative; z-index: 1; width: 1.25rem; text-align: center; }
.theme-icon.sun { color: #a76800; }
.theme-icon.moon { color: var(--muted); }
.theme-thumb {
  position: absolute; top: .2rem; bottom: .2rem; left: .2rem; width: 1.85rem;
  border-radius: 50%; background: #ffe08a; box-shadow: 0 1px 3px rgba(0,0,0,.25);
  transition: transform .16s ease, background .16s ease;
}
.theme-toggle[aria-checked="false"] .theme-thumb { transform: translateX(2.45rem); background: #52615a; }
.theme-toggle[aria-checked="false"] .theme-icon.moon { color: #d6e0db; }
@media (prefers-reduced-motion: reduce) { .theme-thumb { transition: none; } }

.screen { display: none; padding: .8rem; }
.screen.active { display: block; }
#screen-live.active { display: flex; flex-direction: column; gap: .55rem; height: calc(100vh - 3.2rem); padding: .55rem; }

.panel {
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--radius); padding: .9rem; margin-bottom: .8rem;
}

/* A panel title with a button parked at the far end of the same line, so the
   button doesn't cost a whole row of vertical space. */
.panel-head { display: flex; gap: .5rem; align-items: center; margin-bottom: .6rem; }
.panel-head h2 { margin: 0; }

/* A tab strip inside a panel, for choosing between alternatives. Distinct from
   the top-level #tabs, which navigates between screens. */
.subtabs {
  display: flex; gap: .25rem; flex-wrap: wrap;
  border-bottom: 1px solid var(--line); margin-bottom: .8rem;
}
.subtab {
  background: none; border: none; border-bottom: 3px solid transparent;
  color: var(--muted); font-size: .95rem; font-weight: 600;
  padding: .55rem .8rem; border-radius: 8px 8px 0 0;
  margin-bottom: -1px;   /* the active underline sits on the strip's own border */
}
.subtab.active { color: var(--ink); border-bottom-color: var(--brand); }

.import-pane p { margin: 0 0 .7rem; }
.import-pane textarea { margin-bottom: .6rem; }
.import-columns { margin: 0 0 .9rem; }

/* ---------- forms & buttons ---------- */
/* .field is a label that can't be one: a group of checkboxes has no single
   control to point at, so the caption is a span and the wrapper is a div. Same
   look as every other field. */
label, .field { display: inline-flex; flex-direction: column; gap: .2rem; font-size: .78rem; color: var(--muted); }
/* As tall as an input, for two reasons: the caption then lines up with the
   captions either side of it instead of floating half a line high, and each
   checkbox gets a full-height strip to be tapped in rather than the 1.15rem the
   box itself occupies. */
.field > .row { min-height: 2.75rem; }
.field > .row > .pos-box { align-self: stretch; }
input, select, textarea {
  font: inherit; padding: .55rem .6rem; border: 1px solid var(--line);
  border-radius: 10px; background: var(--panel); color: var(--ink); min-height: 2.75rem;
}
textarea {
  width: 100%; resize: vertical;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .88rem;
  -webkit-user-select: text; user-select: text;   /* body turns selection off */
}
button {
  font: inherit; font-weight: 600; padding: .7rem 1rem; min-height: 2.9rem;
  border: 1px solid var(--line); border-radius: 12px;
  background: var(--panel); color: var(--ink);
  -webkit-tap-highlight-color: transparent;
}
button.primary { background: var(--brand); color: #fff; border-color: transparent; }
button.warn { background: var(--warn); color: #fff; border-color: transparent; }
button.big { min-height: var(--tap); font-size: 1.05rem; padding-inline: 1.4rem; }
button:disabled { opacity: .38; }
/* A field that's there to be read, not filled in. Dimmed enough to say so, but
   not to .38 like a disabled button — the number in it is the point. */
input:disabled, select:disabled {
  color: var(--muted); background: var(--bg); cursor: default; -webkit-text-fill-color: var(--muted);
}

/* The visual flash that stands in for the haptic tap iOS Safari won't give us.
   A team action changes nothing else on screen — record a Them Shot On and the
   pitch, the score and the bench all look exactly as they did — so this is the
   only confirmation there is. It fills the button with its own colour rather
   than just pulsing a ring, because a ring is easy to miss with a hand over it. */
button:active:not(:disabled) { transform: scale(.96); filter: brightness(1.12); }
.flash { animation: flash .5s ease-out; }
@keyframes flash {
  0%, 35% {
    background: color-mix(in srgb, currentColor 45%, var(--panel));
    box-shadow: 0 0 0 0 currentColor;
  }
  100% { background: var(--panel); box-shadow: 0 0 0 16px transparent; }
}

/* ---------- lists ---------- */
.list { display: flex; flex-direction: column; gap: .3rem; }
.list-row {
  display: flex; align-items: center; gap: .7rem;
  padding: .6rem .7rem; border: 1px solid var(--line); border-radius: 10px;
}
.list-row .num { font-weight: 700; min-width: 2.2rem; }
/* Us first, so the colour and not the order says how it went: green won, red
   lost, grey drew. Tabular figures keep the scores in a column down the season
   even though the opponents' names either side of them aren't aligned. */
/* An action tapped before a player, waiting for one. Same treatment a selected
   player gets, because it's the same idea: this is in hand. */
.action-btn.armed {
  border-color: var(--sel); border-style: solid; color: var(--ink);
  box-shadow: inset 0 0 0 2px var(--sel);
}
/* Answers to a fixed-choice question, in the bar beside the question itself.
   Sized like the action buttons because they're tapped in the same hurry. */
.choice-btn { border-color: var(--brand); color: var(--brand); font-weight: 700; }
.game-score { font-weight: 700; font-variant-numeric: tabular-nums; }
.game-score.won  { color: var(--good); }
.game-score.lost { color: var(--bad); }
.game-score.tied { color: var(--muted); }
/* The record belongs to the heading, but it's a fact about the list rather than
   part of its name, so it doesn't take the heading's weight. */
#season-record { font-weight: 400; font-size: .9rem; color: var(--muted); }
/* Players who've left the team stay listed, dimmed, so their history is still
   reachable and they can be put back. */
.list-row.off-roster { opacity: .5; }
/* One export card per screen, offering whatever that screen is showing. The
   label names the thing and the tiles name the format, so "what's in the file"
   and "which kind of file" are two questions instead of one long button title
   per combination. The card is a card so the two tiles read as alternatives
   rather than as two unrelated buttons that happen to sit together. */
.export-card {
  display: inline-flex; align-items: center; gap: .4rem;
  border: 1px solid var(--line); border-radius: 14px;
  padding: .3rem .3rem .3rem .8rem;
}
.export-what { font-weight: 600; white-space: nowrap; }
/* Heading, record and note are one line of prose at three sizes, so they line
   up on the baseline they'd share if they were one sentence — centring each on
   its own height is what left the small grey note floating. */
.season-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: .1rem .5rem; }

/* The season's record, at the weight and size of a stat tile's number, because
   that is what it is — the first figure anyone looks for on the page. */
.record-big {
  font-size: 1.5rem; font-weight: 800; font-variant-numeric: tabular-nums;
  line-height: 1;
}

/* The format mark is the word itself, set in a solid orange tile — no glyph
   beside it, because that said the same thing twice, and no outline, because a
   filled shape doesn't need one. The ink flips with the theme: the light
   palette's orange is dark enough to take white, the dark palette's is pale
   enough to need black. Still a full-size tap target: min-height comes from the
   button rule and is not overridden here. */
.export-format {
  padding: .5rem .75rem;
  border: none; border-radius: 10px;
  background: var(--file); color: var(--file-ink);
  font-weight: 800; font-size: .82rem; letter-spacing: .07em;
}

.icon-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 3rem; padding: 0;
}
.icon-btn svg { display: block; }

/* The roster is read off the field, not during play, so rows can be compact and
   sit in as many columns as the screen fits.
   Multi-column rather than grid, because the roster should read DOWN each column
   — #2, #3, #4 … — the way a team sheet does. CSS grid can only fill column-first
   with a hard-coded row count, which breaks the moment the window resizes;
   `columns` fills top-to-bottom and still fits however many columns there's room
   for. The trade is that gap doesn't apply, hence the margin on rows. */
.roster-grid {
  display: block;
  columns: 17rem;
  column-gap: .5rem;
}
.roster-grid .list-row {
  gap: .45rem; padding: .45rem .5rem;
  break-inside: avoid;          /* never split a player across a column break */
  margin-bottom: .4rem;
  align-items: flex-start;      /* number beside the name, not beside the middle */
}
/* The pencil is a target rather than a line of text, so it stays centred in the
   card the number and name are anchored to the top of. */
.roster-grid .list-row .icon-btn { align-self: center; }
.roster-grid .list-row .num { min-width: 2rem; }
/* Two lines between the number and the pencil: the name, then what they play and
   what year they're in. */
.roster-grid .pstack {
  flex: 1; min-width: 0;                                  /* or the name won't shrink */
  display: flex; flex-direction: column; gap: .05rem;
}
.roster-grid .list-row .pfull {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Holds its line whether or not there's anything in it, so every card is the
   same height and the columns line up. */
.roster-grid .list-row .hint { white-space: nowrap; min-height: 1.3em; }
/* ---------- overlays ---------- */
#player-editor, #clock-editor, #confirm-dialog {
  padding: 1rem 1.1rem 1.1rem;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--panel); color: var(--ink);
  box-shadow: 0 12px 40px rgba(0,0,0,.35);
}
#player-editor { width: min(46rem, 92vw); }
#clock-editor { width: min(22rem, 92vw); }
#clock-editor input { font-size: 1.4rem; font-variant-numeric: tabular-nums; }
#player-editor::backdrop, #clock-editor::backdrop,
#confirm-dialog::backdrop { background: rgba(0,0,0,.5); }
#player-editor h2, #clock-editor h2, #confirm-dialog h2 { margin-bottom: .8rem; }
#confirm-dialog { width: min(30rem, 92vw); }
/* The itemised list is the whole reason this isn't a confirm(), so it gets to
   look like a list rather than a run-on sentence. */
.confirm-list { margin: .6rem 0 .2rem 1.1rem; padding: 0; }
.confirm-list li { margin: .15rem 0; font-variant-numeric: tabular-nums; }
.confirm-list:empty { display: none; }
#player-editor label { flex: 1; min-width: 7rem; }
/* # and Grade hold two characters at most. Left in the flex: 1 crowd they took a
   full share of the row each, and that share came out of Position — which has
   the most to fit and the least room to give it up. */
#player-editor label.narrow { flex: 0 0 auto; min-width: 0; }
#player-editor label.narrow input, #player-editor label.narrow select { width: 5rem; }
#player-editor input, #player-editor select { width: 100%; }
/* The position checkboxes are the exception: full width would stretch each box
   across the dialog. The id beats .pos-box on specificity, so it has to be said
   again here. */
/* Never narrower than the checkboxes inside it. At a fixed 11rem — set when
   there were four of them — five needed 16, and W and F painted straight over
   the Grade menu next door. max-content means adding a position can't bring that
   back: the group keeps its width and the row wraps instead. */
#player-editor .field { flex: 1; min-width: max-content; }
#player-editor .pos-box { flex: 0 0 auto; min-width: var(--pos-w); }
#player-editor .pos-box input { width: 1.15rem; }
.roster-toggle { border-color: var(--bad); color: var(--bad); }
/* Outlined rather than filled, and smaller than the button beside it: it should
   be findable without ever being the thing your thumb lands on by accident. */
.danger-quiet {
  border-color: var(--bad); color: var(--bad);
  min-height: 2.4rem; padding: .35rem .8rem; font-size: .85rem;
}

/* ---------- match status (score, clock, possession) ---------- */
/* The old scoreboard, unchanged apart from living beside the pitch instead of
   spanning the screen above it. The clock keeps its own controls: Start, End
   period and the corrections all belong to the thing they act on. */
.match-status {
  display: flex; align-items: center; gap: .55rem; flex-wrap: wrap;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--radius); padding: .5rem .55rem;
}
.score-block { text-align: center; min-width: 3.2rem; max-width: 4.4rem; }
/* The opponent's name is the label, and it can be anything. Held to one line so
   a long one pushes possession off the row instead of quietly widening. */
.score-label {
  display: block; font-size: .75rem; color: var(--muted);
  text-transform: uppercase; letter-spacing: .04em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.score { font-size: 2.4rem; font-weight: 800; line-height: 1; font-variant-numeric: tabular-nums; }
.clock-block { text-align: center; }
/* Start, the time, End period — in that order, because the button that starts
   the clock should be against the thing it starts. */
.clock-row { display: flex; align-items: center; justify-content: center; gap: .45rem; }
/* The period line lives under this row, not inside it. It's the longest string
   in the panel — "Period 1 of 2 — stopped" is half again as wide as 40:00 — and
   beside the clock it set the width of the whole status bar, which is what
   pushed possession onto a second row. Below, it costs nothing. */
#period-label { line-height: 1.15; }
#clock { font-size: 2.2rem; font-weight: 800; font-variant-numeric: tabular-nums; line-height: 1.05; }
/* Past 0:00 the clock counts up into stoppage. Colour says "this is added time",
   so a glance doesn't read +2:14 as 2:14 remaining. */
#clock.past-zero { color: var(--warn); }
/* One width whichever of its three faces the button is wearing. It used to be
   about not shoving "End period" sideways; now it's the clock itself that would
   shift, which is worse — a number you're reading should not move. Narrow,
   because two of its three faces are a single symbol. */
#clock-toggle { min-width: 3.4rem; padding-left: .5rem; padding-right: .5rem; font-size: .95rem; }
#clock-toggle.glyph { font-size: 1.35rem; line-height: 1; }
/* Twice a game, against a clock that's read constantly: narrow enough to leave
   the row for the things beside it, and wrapped onto two lines rather than
   abbreviated to something nobody says out loud. */
#period-next {
  width: 4.3rem; padding: .3rem .35rem;
  font-size: .78rem; line-height: 1.15; white-space: normal;
}
/* A live game with the clock not running: blink the number away entirely, so
   what's wrong is that the clock is *missing*, not that it's a funny colour.
   About 110bpm — a bit faster than a resting pulse, which is what makes it read
   as urgent rather than decorative. Overrides past-zero deliberately: "not
   running" is the more urgent of the two things the clock can tell you. */
#clock.stopped { animation: clock-stopped .545s steps(1, end) infinite; }
@keyframes clock-stopped {
  0%, 49%   { color: var(--bad); }
  50%, 100% { color: transparent; }
}
/* Still unmissable without the blinking, for anyone who'd rather not have it. */
@media (prefers-reduced-motion: reduce) {
  #clock.stopped { animation: none; color: var(--bad); }
}

/* Corrections are secondary to Start/Stop, and sit below them. Deliberately
   smaller than the minimum tap target used elsewhere: mis-tapping a nudge is
   cheap and reversible, and these must not compete with the action buttons. */
.nudge-row { justify-content: center; gap: .3rem; margin-top: .35rem; }
.nudge {
  min-height: 2.1rem; padding: .25rem .5rem;
  font-size: .8rem; font-weight: 600; color: var(--muted);
  font-variant-numeric: tabular-nums;
}
/* Pushed to the right edge, so whatever width is left over becomes a gap
   between possession and the score rather than padding the panel out. It also
   stops the layout depending on the screen being an exact width: on a wider
   iPad the gap grows, and nothing has to be re-tuned. */
.poss-block { text-align: center; margin-left: auto; }
.poss-readout { font-size: 1.05rem; font-weight: 700; font-variant-numeric: tabular-nums; }
/* Us green, Them red, matching the action buttons: whose it is should be the
   colour, not something you read. Outlined while it isn't the case, filled in
   when it is, so which way the ball is going is legible from arm's length. */
.poss-btn { min-width: 3.2rem; min-height: 2.6rem; padding: .3rem .5rem; border-width: 2px; }
#poss-us    { border-color: var(--good); color: var(--good); }
#poss-them  { border-color: var(--bad);  color: var(--bad); }
#poss-us.on   { background: var(--good); color: #04150e; border-color: transparent; }
#poss-them.on { background: var(--bad);  color: #1a0605; border-color: transparent; }

/* ---------- selection & prompts ---------- */
.selection-bar {
  display: flex; align-items: center; gap: .8rem;
  padding: .3rem .9rem; border-radius: 12px;
  background: var(--panel); color: var(--muted);
  font-weight: 600;
  /* One height and one border width in every state. This bar sits above the
     pitch, so growing by a line shifts every position down — right as a finger
     is already on its way to one. The armed state changes only style and color;
     the height leaves room for the buttons whether or not they're showing. */
  border: 2px dashed var(--line);
  min-height: 2.85rem;
}
/* Its buttons don't need the full tap height — they're rare, deliberate actions,
   not things tapped at speed during play. */
.selection-bar button { min-height: 2rem; padding: .25rem .7rem; font-size: .85rem; }
.selection-bar.armed { border: 2px solid var(--sel); color: var(--ink); background: color-mix(in srgb, var(--sel) 12%, var(--panel)); }

/* ---------- player grid ---------- */
.field-area { flex: 1; overflow-y: auto; display: flex; flex-direction: column; gap: .55rem; }
.pitch-col, .side-col { display: flex; flex-direction: column; gap: .5rem; min-width: 0; }

/* Landscape: the shape on the left, the buttons on the right. Below this the
   two columns stack, which is what a phone or a portrait iPad gets. */
@media (min-width: 56rem) {
  /* Stretch, so the pitch can fill the column rather than guess at a height. */
  .field-area { flex-direction: row; align-items: stretch; gap: .8rem; }
  /* The right column carries the score, clock and possession as well as the
     buttons now, so it gets the wider share. */
  .pitch-col { flex: 1 1 45%; min-height: 0; }
  .side-col  { flex: 1 1 55%; }
  /* Take whatever height the column has left, and derive the width from the
     ratio. A vh guess can't know how tall the footer or the not-placed strip
     turned out to be, so it always left space unused. */
  .pitch {
    flex: 1 1 auto; height: auto; min-height: 0;
    width: auto; max-width: 100%; margin-inline: auto;
  }
}
.section-head { margin: .5rem 0 .35rem; color: var(--muted); text-transform: uppercase; letter-spacing: .05em; font-size: .74rem; }
.player-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(7.2rem, 1fr)); gap: .5rem; }
.player-btn {
  position: relative;   /* for the sent-off card in the corner */
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  min-height: 5.2rem; gap: .1rem; padding: .4rem;
  border: 2px solid var(--line); border-radius: var(--radius); background: var(--panel);
}
.player-btn .pnum { font-size: 1.9rem; font-weight: 800; line-height: 1; font-variant-numeric: tabular-nums; }
.player-btn .pname { font-size: .8rem; font-weight: 600; }
.player-btn .ppos { font-size: .68rem; color: var(--muted); }
.player-btn.selected { border-color: var(--sel); background: color-mix(in srgb, var(--sel) 18%, var(--panel)); }
/* Inactive for this game: shown rather than hidden, so tapping puts them back.
   Dimmed as a group and again individually, so the whole section reads as "not
   in play" and the eye skips it while a game is running. Not struck through —
   the section they sit in is headed Inactive, which says it once already. */
.inactive-group { opacity: .8; }
.player-btn.not-active { opacity: .45; border-style: dashed; }
.player-grid.bench .player-btn { min-height: 4.2rem; opacity: .82; }
.player-grid.bench .player-btn .pnum { font-size: 1.5rem; }
/* Sent off: still on the bench, because that's where you'd look for them, but
   plainly out of the game — red edge, red card, and greyed right down.
   Below the bench rules on purpose: it ties with them on specificity, so it has
   to come second to win, or their .82 leaves these at nearly full strength. */
.player-grid .player-btn.sent-off {
  border-color: var(--bad); border-style: solid; opacity: .45;
}
/* In the corner, out of the flow, so the chip is the size of every other one. */
.player-btn.sent-off .card-chip { position: absolute; top: .28rem; right: .32rem; }

/* ---------- shot map ---------- */
/* The same 68:105 box as the live pitch, so the markings sit where the eye
   expects them and a location means the same thing on both screens. Capped in
   height because on the box score it's one panel among several, not the thing
   you're working in. */
.shot-map-pitch {
  /* Sized from the width it's given, with the height following from the ratio.
     It used to be the other way round — a fixed height, width from the ratio —
     which worked while it had a whole row to itself and broke the moment it had
     to share one: the column shrank, the height didn't, and the pitch came out
     tall and thin with every dot on it an oval.
     The real ratio is set inline, since the map shows the attacking third or
     further back depending on the game. This is only the shape it starts at. */
  aspect-ratio: 68 / 35;
  width: 100%; max-width: 22rem; max-height: 60vh;
  margin: 0 auto;
  border-radius: 10px;
  overflow: hidden;             /* the rest of the pitch is below the frame */
  background: color-mix(in srgb, var(--good) 7%, var(--panel));
}
.shot-map-pitch svg { width: 100%; height: 100%; color: var(--muted); }
/* Tappable, and big enough to be tapped: the dots themselves are a few pixels
   across, so each carries an invisible disc around it that takes the tap. */
/* The line from where a shot was struck to where it crossed the line. Faint on
   purpose: it's the context, and the dot is the fact. */
.shot-trail { opacity: .45; pointer-events: none; }
.shot-trail.dimmed { opacity: .1; }
.shot-trail.picked { opacity: 1; }
.shot-dot { cursor: pointer; }
.shot-dot.dimmed { opacity: .18; }
/* The picked dot gets a halo drawn in the SVG, not a filter here — see
   renderShotMap. It stays lit even when a player filter would have dimmed it. */
.shot-dot.dimmed.picked { opacity: 1; }
.shot-caption {
  margin: .7rem 0 0; text-align: center;
  font-size: .85rem; color: var(--muted); min-height: 1.3em;
}
/* The named shot is the answer to a question just asked, so it reads as content
   rather than as the hint it replaces. */
.shot-caption.picked { color: var(--ink); font-weight: 600; }

/* A row that points the map at one player. Only rows that can do it get the
   affordance. */
#box-table tr.shooter { cursor: pointer; }
#box-table tr.shooter.picked { background: color-mix(in srgb, var(--sel) 18%, transparent); }
/* The first column is sticky and paints its own panel background over the row,
   so the tint has to be given to it separately or the name stays uncoloured. */
#box-table tr.shooter.picked td:first-child,
#box-table tr.shooter.picked td:nth-child(2) {
  background: color-mix(in srgb, var(--sel) 18%, var(--panel));
}

/* The two maps read left to right the way the ball travelled: struck from the
   pitch on the left, ended up in the goal on the right. They wrap to a single
   column when the screen can't hold both, which on an iPad means portrait. */
.shot-maps {
  display: flex; flex-wrap: wrap; gap: 1rem 1.6rem;
  align-items: center; justify-content: center;
}
/* Both halves share the row and both can shrink. Without a basis the pitch was
   the one that gave way, because the goal beside it asked for a width and the
   pitch didn't. */
.shot-map-side { display: flex; flex-direction: column; align-items: stretch; min-width: 0; flex: 1 1 16rem; }
.shot-map-side .section-head { align-self: flex-start; }
/* The goal, read afterwards rather than tapped. Two goal widths by two goal
   heights, so it's wider than it is tall and takes the room the portrait pitch
   beside it doesn't want. */
.net-map { flex: 1 1 20rem; max-width: 34rem; }
.net-map-goal {
  width: 100%; margin: .3rem auto 0;
  color: var(--ink);
  background: color-mix(in srgb, var(--good) 5%, var(--panel));
  border-radius: 10px; padding: .6rem .5rem;
}
.net-map-goal svg { width: 100%; height: auto; display: block; }
.net-map .hint { text-align: center; margin: .4rem 0 0; }
.shot-map-key {
  display: flex; flex-wrap: wrap; gap: .3rem 1rem;
  justify-content: center; align-items: center;
  margin-top: .7rem; font-size: .8rem; color: var(--muted);
}
.shot-key { display: inline-flex; align-items: center; gap: .35rem; }
/* Drawn the same way as the marks on the pitch — a ring for a shot, filled for
   one that went in — so the key is the thing itself, not a colour swatch. */
.shot-key-dot {
  width: .85rem; height: .85rem; border-radius: 50%;
  border: 3px solid var(--dot);   /* as heavy as the rings on the pitch */
}
.shot-key-dot.filled { background: var(--dot); }

/* ---------- the pitch ---------- */
.section-head-row { display: flex; align-items: center; gap: .5rem; }
/* No width of its own: this row is exactly full on a landscape iPad, so the
   select takes what its longest shape name needs and no more. */
#formation-picker { font-size: .85rem; font-weight: 600; }

/* A 3:2 box with the markings drawn in CSS rather than an image, so it stays
   crisp at any size and costs nothing to cache. Slots are placed by percentage,
   which is what lets the same numbers work on any screen. */
.pitch {
  position: relative;
  /* Height first, width from the ratio. Sizing off the width instead made the
     pitch tall enough to push the back four and the bench off the screen, and
     during a game everything has to be reachable without scrolling.
     True proportions: 68m by 105m. The SVG stretches with preserveAspectRatio
     "none", so matching the viewBox ratio exactly here is what keeps the centre
     circle a circle — the markings and the slot percentages then share one
     coordinate system. */
  height: clamp(12rem, 60vh, 40rem);
  aspect-ratio: 68 / 105;
  width: auto;
  max-width: 100%;
  margin: 0 auto .6rem;
  /* No border. box-sizing is border-box everywhere, so aspect-ratio shapes the
     border box — and the SVG fills the box inside it, which a 2px frame left
     0.4% short of square. Small, but it made the centre circle an ellipse and
     the paragraph above claim otherwise. The touchline the SVG draws is the
     edge now, which is what you want to see anyway. */
  border-radius: 10px;
  background: color-mix(in srgb, var(--good) 7%, var(--panel));
}
/* While the pitch is asking where a shot came from it is one big button, so it
   says so: a crosshair instead of an arrow, and the grass brightened a shade so
   the change of mode is visible from arm's length. */
.pitch.picking { cursor: crosshair; background: color-mix(in srgb, var(--good) 16%, var(--panel)); }
/* The goal, laid over the pitch while that question is being asked. Centred in
   the box the pitch already occupies, so nothing below it moves when the
   question appears and nothing moves back when it's answered. */
.goal-target {
  position: absolute; left: 0; top: 50%;
  transform: translateY(-50%);
  width: 100%; height: auto;
  color: var(--ink);
  /* Opaque, and lifted off the pitch with a shadow. Half-seeing the markings
     through it made the frame ambiguous, which is the one thing this picture
     cannot afford — every tap is judged against those two posts and the bar. */
  background: var(--panel);
  border-radius: 10px;
  box-shadow: 0 6px 24px rgba(0,0,0,.35);
  /* No padding and no border on purpose. An SVG's user space maps to its content
     box while getBoundingClientRect returns the border box, so anything between
     the two would offset every tap by that much — the picture would say one
     thing and the stored number another. The room around the goal is built into
     the drawing instead: half a goal width beyond each post. */
  cursor: crosshair;
}

/* The markings live in an SVG in index.html, drawn to real proportions. Our goal
   is at the bottom, so the team attacks up the screen.
   pointer-events: none is load-bearing: this covers the whole pitch, and without
   it the lines would swallow every tap meant for a player — which is exactly how
   the goal area used to eat every tap on the keeper. */
.pitch-lines {
  position: absolute; inset: 0; width: 100%; height: 100%;
  /* --muted, not --line: line is a border colour a shade off the background, so
     against the grass it vanished. These want to read like chalk. */
  color: var(--muted);
  /* The touchlines and goal lines are drawn on the very edge of the viewBox, so
     half of each stroke falls outside it. Left to clip, they came out half the
     width of every other line — which is why they used to be drawn a decimetre
     inside instead, dragging the penalty mark and both boxes in with them. */
  overflow: visible;
  pointer-events: none;
}
#pitch-slots { position: absolute; inset: 0; }

/* Two elements on purpose: the wrapper is placed on the pitch, the button
   inside it is just a button. They were one element until a press-effect
   `transform: scale()` replaced the centring `translate()` and shifted the chip
   half its own size out from under your finger — releasing then landed outside
   it, so the tap was silently lost. Separated, neither can clobber the other. */
/* z-index lifts the players above the markings. Both are positioned children of
   .pitch with automatic stacking, so they'd otherwise paint in DOM order and the
   ::after goal area — being last — would draw its lines across the defenders. */
/* A share of the pitch, not a fixed size: a five-man midfield is five of these
   across, so at a fixed rem width they overlapped as soon as the pitch narrowed. */
.slot-pos { position: absolute; transform: translate(-50%, -50%); z-index: 1; width: var(--slot-w, 17.5%); }
.slot {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  min-height: 0; width: 100%; padding: .25rem .15rem; gap: .05rem;
  border: 2px solid var(--line); border-radius: 10px;
}
.slot.filled { background: var(--panel); }
.slot .pnum { font-size: 1.2rem; font-weight: 800; line-height: 1; font-variant-numeric: tabular-nums; }
.slot .pname { font-size: .7rem; font-weight: 600; line-height: 1.1; }
.slot.selected { border-color: var(--sel); background: color-mix(in srgb, var(--sel) 20%, var(--panel)); }
/* An empty position reads as a gap to fill, not as a player. */
/* An empty position reads as a gap to fill, not as a player — but it still has
   to be hittable with a thumb, so it keeps a 44px-ish target rather than
   shrinking to fit its one letter. */
.slot.empty {
  border-style: dashed; background: transparent; color: var(--muted);
  width: 82%; margin: 0 auto; min-height: 2.85rem;
}
.slot.empty .slot-label { font-size: .8rem; font-weight: 700; }
.slot.armed { border-color: var(--sel); border-style: solid; color: var(--ink); }
/* A position lost to a red card. Solid rather than dashed: a dashed edge is this
   app's word for "something goes here", and nothing goes here. Still a button —
   placing someone in it moves the hole to the position they left. */
.slot.vacancy {
  border-style: solid; border-color: var(--bad);
  background: color-mix(in srgb, var(--bad) 12%, transparent);
  color: var(--bad);
}
/* The card is on its own now, so it can be the size the label used to be. */
.slot.vacancy .card-chip { width: 1.25rem; height: 1.72rem; }

/* Editing the shape. The marker is the thing being handled now, so it looks
   handled: dashed like an empty position, but lifted off the grass. */
.slot-pos.editing { cursor: grab; touch-action: none; z-index: 3; }
.slot-pos.editing.dragging { cursor: grabbing; z-index: 4; }
.slot.shape-marker {
  border-style: dashed;
  border-color: var(--sel);
  color: var(--ink);
  background: color-mix(in srgb, var(--sel) 14%, var(--panel));
}
.slot-pos.dragging .slot.shape-marker {
  border-style: solid;
  transform: scale(1.08);
  box-shadow: 0 .4rem .9rem rgb(0 0 0 / .45);
}


/* ---------- action toolbar ---------- */
.action-bar { display: grid; grid-template-columns: repeat(auto-fit, minmax(6.4rem, 1fr)); gap: .45rem; }
/* Full width of the grid, so what we did and what they did never share a row. */
.action-split { grid-column: 1 / -1; border-top: 1px dashed var(--line); margin: .2rem 0 .05rem; }
.action-btn {
  min-height: var(--tap); font-size: .95rem; border-width: 2px;
  /* A column so "Them" can sit on its own line above the noun. The noun is
     still free to wrap under itself if the button is narrow. */
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  line-height: 1.15;
}
.action-btn .side { font-weight: 800; }
/* The cards are the one action with a real shape of their own, and the shape
   reads quicker than the word does. Proportioned like the real thing, upright.
   The dark edge keeps the yellow off a light background from vanishing. */
.card-chip {
  display: block; width: .78rem; height: 1.08rem;
  border-radius: 2px; border: 1px solid rgba(0, 0, 0, .5);
}
.card-chip.yellow { background: #f0c020; }
.card-chip.red    { background: #d8352c; }
/* Color coding: green reads as "good thing happened", red as "bad thing happened".
   This is the cheapest accuracy win in the whole UI. */
.action-btn.good { border-color: var(--good); color: var(--good); }
.action-btn.bad  { border-color: var(--bad);  color: var(--bad); }
.action-btn.team { border-style: dashed; }
.action-btn:disabled { border-color: var(--line); color: var(--muted); }

#live-footer { display: flex; gap: .5rem; align-items: center; flex-wrap: wrap; }

/* ---------- box score ---------- */
/* Each tile is as wide as what's written in it, not a share of the row. An
   equal-width grid made "SAVE %" as wide as "OPP SHOTS (ON)", which spread eight
   tiles past the width and left the eighth stranded on a row of its own. Sized
   to content they mostly fit on one line, and when they don't the wrap lands
   somewhere that looks deliberate. */
.team-summary { display: flex; flex-wrap: wrap; gap: .5rem; }
.team-summary .stat-tile { flex: 0 0 auto; }
.stat-tile { border: 1px solid var(--line); border-radius: 12px; padding: .6rem .7rem; }
.stat-tile .v { font-size: 1.5rem; font-weight: 800; font-variant-numeric: tabular-nums; }
.stat-tile .k { font-size: .74rem; color: var(--muted); text-transform: uppercase; letter-spacing: .04em; }

/* What a game is, on its row on the public page. Shown only when a game doesn't
   count, so it reads as a note about that game rather than a label on every
   row. The app says it in the editor behind the pencil instead. */
.kind-tag {
  font-size: .74rem; color: var(--muted); text-transform: uppercase;
  letter-spacing: .04em; border: 1px solid var(--line);
  border-radius: 999px; padding: .1rem .5rem;
}

/* The public page: the same screens with everything that writes taken out of
   sight. The handlers behind these aren't attached either (see PUBLIC in
   app.js) — this is so the page doesn't show doors that lead nowhere, not the
   thing stopping anyone going through them. */
.read-only #new-game-panel,
.read-only #import-btn,
.read-only #undo-btn,
.read-only #clear-roster,
.read-only #add-player-panel,
.read-only #import-roster-panel { display: none; }
/* Wording that differs between the two — an <h2>, a <p>, a <span> inside a
   footer. `revert` puts each back to whatever it would have been, rather than
   forcing a span to be a block. */
.public-only { display: none; }
.read-only .public-only { display: revert; }
.read-only .private-only { display: none; }

/* Wide tables scroll inside their own box; the page never scrolls sideways. */
.table-scroll { overflow-x: auto; }
table { border-collapse: collapse; width: 100%; font-variant-numeric: tabular-nums; }
th, td { padding: .45rem .55rem; text-align: right; border-bottom: 1px solid var(--line); white-space: nowrap; }
/* Who the row is about: the jersey number and the name. Both stay put while the
   stat columns scroll sideways, and both read left like a team sheet — the rest
   are numbers and line up on the right.
   The number column is given a fixed width so the name column knows what to
   park itself against; box-sizing is border-box, so 3rem is the whole cell. */
th:first-child, td:first-child,
th:nth-child(2), td:nth-child(2) {
  text-align: left; position: sticky; background: var(--panel);
}
th:first-child, td:first-child { left: 0; width: 3rem; min-width: 3rem; }
th:nth-child(2), td:nth-child(2) { left: 3rem; }
thead th { font-size: .72rem; color: var(--muted); text-transform: uppercase; }
/* Every column sorts, which turns the box score into a leaderboard. A little
   more height than a plain header so it can be tapped with a thumb. */
thead th.sortable { cursor: pointer; padding-top: .6rem; padding-bottom: .6rem; -webkit-user-select: none; user-select: none; }
thead th.sorted { color: var(--ink); }
thead th .arrow { font-size: .8em; margin-left: .2rem; }

.event-log { max-height: 24rem; overflow-y: auto; font-size: .85rem; }
.event-row {
  /* Wraps because a goal with nothing filled in offers three ways to fill it,
     and three buttons plus Delete don't fit beside the description. The extra
     line only appears on rows that have something to offer. */
  display: flex; flex-wrap: wrap; gap: .4rem .7rem; align-items: center;
  padding: .4rem .4rem; border-bottom: 1px solid var(--line);
}
.event-row .time-context {
  display: flex; align-items: baseline; gap: .25rem; min-width: 5.7rem;
  color: var(--muted); white-space: nowrap;
}
.event-row .t { font-variant-numeric: tabular-nums; }
.event-row .what { flex: 1; }
.event-row .per { font-size: .7rem; font-weight: 600; }
/* The newest event, so a glance at the drawer confirms the tap just landed. */
.event-row.newest { background: color-mix(in srgb, var(--sel) 14%, transparent); border-radius: 8px; }
/* Queued for a substitution and waiting for the other half of the pair. Green
   for coming on, amber for going off — the same two meanings the arrows have on
   a fourth official's board, and readable at arm's length in sunlight.
   Both work on a bench chip and on a chip out on the pitch, because either side
   can be the one tapped first. */
.player-btn.subbing-on, .slot.subbing-on {
  border-color: var(--good); border-style: solid;
  box-shadow: inset 0 0 0 2px var(--good);
}
.player-btn.subbing-out, .slot.subbing-out {
  border-color: var(--warn); border-style: solid;
  box-shadow: inset 0 0 0 2px var(--warn);
}
/* A button that's currently a mode, not a one-shot: substitution stays open
   until it's pressed again. */
#sub-btn.active { background: var(--sel); color: #fff; border-color: transparent; }
.event-row .del,
.event-row .add-answer { min-height: 2.1rem; padding: .2rem .55rem; font-size: .78rem; font-weight: 600; }
/* Reads as an addition rather than a removal: same size and shape as Delete
   beside it, opposite colour. */
.event-row .add-answer { border-color: var(--good); color: var(--good); white-space: nowrap; }
.event-row.system { opacity: .7; font-style: italic; }

/* ---------- drawer ---------- */
.scrim { position: fixed; inset: 0; background: rgba(0,0,0,.4); z-index: 39; }
.drawer {
  position: fixed; top: 0; right: 0; z-index: 40;
  height: 100%; width: min(27rem, 92vw);
  display: flex; flex-direction: column;
  background: var(--panel); border-left: 1px solid var(--line);
  box-shadow: -8px 0 26px rgba(0,0,0,.22);
  transform: translateX(100%);
  transition: transform .22s ease;
  padding-right: env(safe-area-inset-right);
}
/* The drawer must stay in the layout while it slides, so it can't use [hidden]
   for the animation — .open drives the transform, and hidden only applies once
   it's fully off-screen. */
.drawer.open { transform: translateX(0); }
.drawer-head, .drawer-controls, .drawer-foot {
  display: flex; align-items: center; gap: .6rem;
  padding: .7rem .9rem; border-bottom: 1px solid var(--line);
}
.drawer-foot { border-bottom: none; border-top: 1px solid var(--line); display: block; line-height: 1.35; }
.drawer-body { flex: 1; overflow-y: auto; padding: .3rem .7rem; font-size: .88rem; }
.drawer-empty { padding: 1.4rem .6rem; text-align: center; }
label.inline { flex-direction: row; align-items: center; gap: .45rem; font-size: .85rem; color: var(--ink); }
label.inline input { min-height: 0; width: 1.15rem; height: 1.15rem; }

/* Name over box, not beside it. Side by side, the gap between a name and its own
   box was about the same as the gap to the next pair, so with five of them you
   had to count along the row to see which two were ticked. Stacked, each pair
   reads as one object and there is nothing to work out.
   Centred and full height, so the whole column takes the tap rather than the
   18px the box itself occupies. */
.pos-box {
  /* Named, because the dialog has to restate it: #player-editor label sets a
     7rem minimum, and a plain 0 there would leave the two forms' boxes different
     sizes. */
  --pos-w: 2.4rem;
  display: inline-flex; flex-direction: column; align-items: center;
  justify-content: center; gap: .25rem;
  /* Wide as well as tall: stacking made each pair only as wide as its name, and
     "D" is one character. A thumb needs more than that in both directions. */
  min-height: 2.75rem; min-width: var(--pos-w); padding: 0 .2rem;
  font-size: .8rem; font-weight: 600; color: var(--ink);
}
.pos-box input { min-height: 0; width: 1.15rem; height: 1.15rem; margin: 0; }

/* Offered rather than forced, and only during a live game — see showUpdateReady. */
#update-ready {
  position: fixed; z-index: 90;
  left: 50%; transform: translateX(-50%);
  bottom: calc(1rem + env(safe-area-inset-bottom));
  box-shadow: 0 4px 18px rgba(0,0,0,.3);
}

/* ---------- on-device error banner ---------- */
/* Deliberately loud and hard to miss. If this is visible, something is broken and
   the stats being recorded may be wrong — that's worth interrupting for. */
#fatal-error {
  position: fixed; top: 0; left: 0; right: 0; z-index: 100;
  display: flex; align-items: flex-start; gap: .6rem;
  background: var(--bad); color: #fff;
  font: 600 .8rem/1.35 ui-monospace, SFMono-Regular, Menlo, monospace;
  padding: calc(.6rem + env(safe-area-inset-top)) .8rem .6rem;
  box-shadow: 0 4px 14px rgba(0,0,0,.3);
}
/* The scrolling belongs to the text, not the bar: with it on the bar, a long
   list of errors carried the close button off the top of the screen. */
#fatal-error-text {
  flex: 1; min-width: 0;
  max-height: 40vh; overflow-y: auto;
  white-space: pre-wrap; word-break: break-word;
  -webkit-user-select: text; user-select: text;   /* so you can read/copy it back to me */
}
/* White on the red bar rather than red on it, which would be invisible. Round
   and set apart so it reads as a control and not as part of the message. */
#fatal-error-close {
  flex: 0 0 auto;
  /* Square, and min-height cleared: the global button rule's 2.9rem was taller
     than the width and turned the circle into an oval. 2.75rem is the 44px
     Apple asks for on a touch target. */
  width: 2.75rem; height: 2.75rem; min-height: 0; padding: 0;
  border: none; border-radius: 50%;
  background: #fff; color: var(--bad);
  font: 700 1.4rem/1 system-ui, sans-serif;
  display: flex; align-items: center; justify-content: center;
}

@media (prefers-reduced-motion: reduce) {
  .drawer { transition: none; }
}

@media print {
  #tabs, .action-bar, #live-footer, .export-card, #print-sheet,
  #open-log, .event-log, .drawer, .scrim { display: none !important; }
  .panel { border: none; page-break-inside: avoid; }
  body { background: #fff; color: #000; }
}
