:root {
  /* Dark, near-neutral ground. An earlier version used a saturated forest green
     for the whole page, which turned everything - page, panels, photograph -
     the same murky green. The brand green is now reserved for accents. */
  --bg: #0c1210;
  --panel: #161d1a;
  --panel-2: #1e2723;
  --line: #2c3833;
  --text: #eef2f0;
  --muted: #98a8a1;
  --accent: #35d07f;
  --accent-2: #4aa3ff;
  --danger: #ff5c6c;
  --warn: #ffc44d;
  --radius: 10px;

  /* ---- Background image -------------------------------------------------
     A file at web/background.webp (or .jpg/.png) is picked up automatically.
     If there is none the plain dark gradient is used and nothing breaks.

       --bg-image-zoom   1.00 shows the whole photograph, uncropped
       --bg-image-dim    how much the photograph is darkened
       --bg-image-panel  how opaque the cards are. Kept high on purpose: a
                         translucent card over a photo of similar colour reads
                         as a smudge, not as a card.
  ------------------------------------------------------------------------ */
  --bg-image-zoom: 1;
  --bg-image-dim: 0.30;
  --bg-image-panel: 0.96;
  --bg-image-blur: 0px;
  --bg-image-wide: none;
  --bg-image-tall: none;

  /* ---- The photograph's shape -----------------------------------------
     How wide the picture is against how tall: 2000 x 1472 for the shipped
     file. It is MEASURED from the file itself by background.js and written
     back as --photo-aspect, so swapping the photograph for one of a different
     shape needs no code change. The value below is only the fallback used for
     the instant before the image loads, and by anything that never runs the
     script.

     tools/measure-welcome.mjs reports where the picture's sky and its grass
     begin, taken from the picture's own pixels. */
  --photo-aspect: 1.3587;   /* 2000 / 1472 - fallback; measured at runtime */
  --photo-grass-top: 0.70;  /* the green field starts 70% down the picture */

  /* ---- Layout constants -----------------------------------------------
     Named once because the photograph arithmetic below is stated in terms of
     them. Change one here and the alignment follows; that is the whole point
     of not typing pixel offsets. */
  --layout-max: 1560px;
  --layout-pad: 22px;
  --cards-w: 380px;
  --col-gap: 34px;

  /* ---- The app's own column ---------------------------------------------
     Everything the player reads, types into or clicks lives in this left-hand
     column, on the page's dark green. The photograph is given everything to the
     right of it and nothing is ever laid over it. This replaced a centred
     layout with a full-width top bar, which measured out as 1127px of panels
     and the whole account menu sitting on top of the picture.

     ⚠ AND IT CAN NEVER BE NARROWER THAN THE CARDS IT HAS TO HOLD.
     `--ui-col` is a fraction of the WINDOW; `--cards-w` is a FIXED 380px. Two independent numbers, and nothing
     stopped them disagreeing - so on a window where 44% is less than the card column, the dark green panel ended
     IN THE MIDDLE OF THE CARDS and the rest of the card sat on the photograph.

     Colin, with a screenshot, twice: *"about a third of the grey background card needs to disappear, it's about a
     third too wide"*, and then *"have a look at the width of the dark green left panel and put the card inside
     that."* He also said the right way to fix it: *"why do you want to do everything in pixels ... you should be
     inside the division statement and it stays in the division."*

     So it is a RELATIONSHIP rather than a tuned number: the panel is the wider of the window's share and the card
     column plus the page's own padding either side. It cannot come out narrower than what it holds at ANY window
     width, and there is no breakpoint arithmetic to keep in step. The card's own `max-width` below is the other
     half of the same guarantee. */
  --ui-col: max(clamp(420px, 44%, 880px), calc(var(--cards-w) + 2 * var(--layout-pad)));
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: radial-gradient(1200px 600px at 50% -10%, #172030 0%, var(--bg) 60%);
  color: var(--text);
  font: 15px/1.5 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  min-height: 100vh;
}

/* A fixed, full-bleed background layer. Kept separate from the gradient above
   so the image sits behind everything and never affects layout.

   The photo is pushed to the RIGHT of the screen and the interface is kept to
   the left, so nothing is ever laid over the picture itself. On a 1920 screen
   the photo starts around x=890 and the cards end around x=380, so there is a
   wide clear gap between them. */
body.has-bg-image::before {
  content: "";
  position: fixed;
  /* UNDERSIDE OF THE UI COLUMN, not the whole viewport: the photograph is
     contain-fitted into the right-hand region only, so the whole picture still
     shows AND none of the interface is drawn over it. */
  inset: 0 0 0 var(--photo-col, var(--ui-col));
  z-index: -2;
  background-image: var(--bg-image-wide);
  background-size: contain;
  background-position: right center;
  background-repeat: no-repeat;
  transform: scale(var(--bg-image-zoom));
  transform-origin: right center;
  filter: blur(var(--bg-image-blur)) brightness(var(--bg-photo-bright, 1));
}
/* HOW A CHOSEN BACKGROUND IS LAID OUT (Colin, 5 Oct 2026) - the standard photo is 'fit' (the whole picture, no crop). */
body.has-bg-image[data-bg-fit="fill"]::before { background-size: cover; background-position: center; }
body.has-bg-image[data-bg-fit="stretch"]::before { background-size: 100% 100%; background-position: center; }
body.has-bg-image[data-bg-fit="centre"]::before { background-size: auto; background-position: center; background-repeat: no-repeat; }
body.has-bg-image[data-bg-fit="tile"]::before { background-size: auto; background-position: top left; background-repeat: repeat; }
#photoImg { filter: brightness(var(--bg-photo-bright, 1)); }

/* On a phone the landscape crop would cut the picture in half, so swap in the
   portrait version, which is prepared separately. */
@media (max-width: 700px), (orientation: portrait) and (max-width: 900px) {
  body.has-bg-image::before {
    background-image: var(--bg-image-tall);
    background-position: center top;
  }
}

/* Dimming, but only where the interface is.
   The photo now lives on the right with nothing over it, so a flat full-screen
   wash would just dull the picture for no reason. Instead the darkening fades
   out towards the right: full over the cards on the left, clear by about 75%
   across, which is where the photograph begins. */
/* The ground the interface sits on.
   This used to be a wash that faded out to the right, because the interface was
   laid over the photo and the photo needed dimming underneath it. Now that the
   interface has its own column and the photo has the rest, there is nothing to
   fade: this is just the dark green of the app's column. */
body.has-bg-image::after {
  content: "";
  position: fixed;
  top: 0;
  bottom: 0;
  left: 0;
  width: var(--ui-col);
  z-index: -1;
  background: rgba(11, 31, 20, calc(var(--bg-image-dim) + 0.34));
}

/* Panels become translucent over a photo so the image still reads through,
   while keeping text contrast. */
body.has-bg-image .panel,
body.has-bg-image .card,
body.has-bg-image .mode {
  background: rgba(23, 28, 36, var(--bg-image-panel));
  backdrop-filter: blur(10px);
}
body.has-bg-image .card-guest {
  background: linear-gradient(
    180deg,
    rgba(53, 208, 127, .10),
    rgba(23, 28, 36, var(--bg-image-panel))
  );
}
body.has-bg-image .topbar { background: rgba(11, 31, 20, .68); }

/* THE READER'S OWN SIZE, AND THE HEIGHT THAT SURVIVES IT.
 *
 * `zoom` rather than a pile of font sizes: it scales the whole interface in one
 * move - the type, the clock dials, the fixture's miniature cards - which is what
 * "they might want it to be bigger" means. Colin's choice for when it no longer
 * fits: "if you put a scroll bar in then you can make things pretty big", so the
 * screen is ALLOWED to scroll now; the old "everything on exactly one screen"
 * rule holds at 100% and gives way above it.
 *
 * --card-h is the height a card gets, in the units CSS is working in AFTER the
 * zoom: the bar and the page padding scale with the zoom as well, so the window
 * is 100vh/zoom tall in those units. That keeps the cards exactly as tall as the
 * window at any zoom, and the bigger text simply fills a card sooner - the card
 * scrolls inside itself, which is the other half of what Colin chose.
 *
 * --strip-h and --bar-h are MEASURED by the client, not written down here, and that is
 * the point: this formula used to subtract a literal 77px - "59px top bar + 8px top and
 * 10px bottom padding", the one place the two numbers met BY HAND - and the bar is not
 * always 59px. It WRAPS: measured 103px at 1440 wide against 59px at 1600, so at 1440 the
 * cards reserved 44px more than the window had, the grid started at y=111 instead of 67,
 * and every card's bottom 34px sat below the fold - which is where the chat card's south
 * and south-east resize handles live (Colin: "the chat card needs a title bar, resize
 * ability"). --strip-h is the put-away strip (0 until something is put away) and --bar-h
 * starts at the bar's own widest-layout height so the first paint is sane before the
 * client has measured anything. The 8px and 10px are the game screen's own padding, below. */
:root {
  --ui-zoom: 1;
  --strip-h: 0px;
  --bar-h: 59px;
  --card-h: calc(100vh / var(--ui-zoom) - var(--bar-h) - 8px - 10px - var(--strip-h));
}
body { zoom: var(--ui-zoom); }

/* ---- the View panel ------------------------------------------------------ */
/*
 * THE PANEL HANGS UNDER THE BAR BUT IS NOT PART OF IT.
 *
 * It is `position: fixed` and it is a SIBLING of the COLFOOTY bar in the document, not a child
 * of it, and both of those are load-bearing once the cards have been arranged.
 *
 * A z-index is only ever compared with the elements INSIDE the same stacking context. The bar is
 * a window in the arrangement, so `applyWindows` gives it a z-index - which makes it a stacking
 * context of its own - and everything inside it, this panel included, is painted at the BAR's
 * place in the arrangement. On an arranged screen the bar and the cards are all handed the same
 * z-index, a tie is settled by document order, and the cards come after the bar: so the panel was
 * painted UNDER the cards however large its own z-index was. Measured on Colin's own arranged
 * screen, with the panel open: a hit test at the middle of the History tick returned the points
 * box. The panel was on the screen and could not be clicked where a card covered it.
 *
 * As a sibling of the bar it is compared with the CARDS instead, which is what z-index 90 was
 * always meant to say - and the same as the put-away strip, which is already a sibling of it.
 * `top` is set from the bar's own measured foot when the panel is opened (see `#openView` in
 * app.js); the value here is only the shape of it before that first measurement.
 */
.viewpanel {
  position: fixed;
  top: calc(var(--bar-h, 59px) - 4px);
  right: 1.25rem;
  z-index: 90;
  width: 22rem;
  max-width: calc(100vw - 2rem);
  padding: 1rem 1.1rem;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: 0 18px 40px rgba(0, 0, 0, .5);
}
.viewpanel h3 { margin: 0 0 .6rem; font-size: 1rem; }
.viewrow { display: flex; align-items: center; justify-content: space-between; gap: .75rem; margin-top: .5rem; }
.viewrow-end { justify-content: flex-end; margin-top: .9rem; }
.viewrow .label { font-size: .8125rem; color: var(--muted); text-transform: uppercase; letter-spacing: .6px; }
.zoomrow { display: flex; align-items: center; gap: .5rem; }
.zoomrow strong { min-width: 3.4rem; text-align: center; font-variant-numeric: tabular-nums; }
.zoomrow .backbtn { padding: 0 .6rem; }
.viewcheck { display: flex; align-items: center; gap: .5rem; margin-top: .35rem; font-size: .875rem; }
.viewpanel .error { margin: .5rem 0 0; }

/* A card that has been put away. Its strip button brings it back.
   ON THE BODY, not on #screen-game: the COLFOOTY bar is one of the five windows
   and it lives outside the game screen. All five can be put away - the round card
   and the bar were missing from this list, which is why putting the round card
   away left it on the screen while the account recorded it as gone, with nothing
   on the strip to bring it back. */
body.hide-bar .topbar { display: none; }
body.hide-fixture .fixture-panel { display: none; }
body.hide-round .panel.grow { display: none; }
body.hide-ladder .col-side > aside.panel { display: none; }
body.hide-points #controlPanel { display: none; }
body.hide-scores #scoreVarsCard { display: none; }
/* The shot timing diagnostic is put away the same way, and for the same reason: it is
   the owner's own card. `hide-shots` is added for every account that is not the owner,
   so the card is not merely empty for them - it is not on the screen at all. */
body.hide-shots #shotsCard { display: none; }
/* THE HISTORY CARD, put away the same way. `hide-history` is added for every guest as well as for an
   account that has put it away - the route behind it refuses a guest, so the card is not merely empty for
   one, it is not on the screen at all. */
body.hide-history #historyCard { display: none; }
body.hide-club #clubCard { display: none; }
/* THE CHAT CARD IS ONE ELEMENT ON TWO SCREENS - the game screen's column and the
   waiting room's host - so putting it away has to hide it wherever it currently is.
   See placeChatCard in app.js. */
body.hide-chat #chatCard { display: none; }
/* The fixture CARD is the container the round-card column count is measured
   against - see .season-fixture below. */
.fixture-panel {
  /* INLINE-SIZE, for the width queries that decide how many rounds fit across.
     The height container is the grid inside it - see .season-fixture. */
  container-type: inline-size;
  display: flex;
  flex-direction: column;
  /* The CARD does not scroll - the grid inside it does, so the title bar and the
     drag handles stay put. */
  overflow: hidden;
}
/* Putting the fixture away hands its width to the round, in the built-in layout. */
body.hide-fixture .grid { grid-template-columns: minmax(0, 1.72fr) minmax(0, 1fr); }

/* THE STRIP: what has been put away, and the way back. */
.minstrip {
  display: flex; align-items: center; gap: .5rem;
  margin-top: .5rem;
  min-height: 2.125rem;
}
.minstrip .minbtn {
  font: inherit; font-size: .8125rem; font-weight: 600;
  padding: 0 .875rem; height: 2.125rem;
  color: var(--text); background: var(--panel-2);
  border: 1px solid var(--line); border-radius: .5rem;
  cursor: pointer;
}
.minstrip .minbtn:hover { background: #26332d; border-color: #3f5148; }
.minstrip .label { font-size: .75rem; color: var(--muted); text-transform: uppercase; letter-spacing: .6px; }

/* ---- moving the cards about ------------------------------------------------
   With no arrangement saved, the game screen is the CSS grid it has always been.
   Once one IS saved, every window is fixed to this account's fractions of the
   window - percentages, so the arrangement scales in proportion when the window
   is resized. Reset takes the class off and the grid comes back. */
/* THE WINDOWS, NAMED ONE BY ONE - NOT `.grid > *`.
   That wildcard also caught `.col-side`, the wrapper around the ladder and the
   points box, and made it a fixed, z-indexed box of its own. That turned the
   wrapper into a stacking context at z-index 5, which TRAPPED its children: the
   ladder could be given z-index 11 and still never paint above the COLFOOTY bar at
   z-index 10, so it could not be brought to the front at all. Colin: "the ladder
   card still doesnt come to the top". Measured with elementFromPoint: the point at
   the ladder's title bar reported `topbar` even with the ladder at z-index 11.
   The wrappers must be left alone; only the cards named in WINDOWS get positioned. */
body.layout-custom .topbar,
body.layout-custom #screen-game .fixture-panel,
body.layout-custom #screen-game .panel.grow,
body.layout-custom #screen-game .col-side > aside.panel,
body.layout-custom #screen-game #controlPanel,
body.layout-custom #screen-game #scoreVarsCard,
body.layout-custom #screen-game #clubCard,
body.layout-custom #screen-game #shotsCard,
body.layout-custom #screen-game #chatCard,
body.layout-custom #screen-game #historyCard,
body.layout-custom #screen-waiting #chatCard,
/* THE HISTORY CARD IS ON THE WAITING ROOM TOO, for the reason it is there at all: a competition
   that has just been RESET sits in that room, and its finished season is exactly what the reader
   wants to look at. One card, moved between the two screens by one placement function (see
   SCREEN_CARDS in app.js), so it is named on both here - without this line the card would keep
   the grid it left behind and could not be dragged or resized in the waiting room, which is the
   fault `body.arranging #screen-waiting #chatCard` was added to fix for the chat. */
body.layout-custom #screen-waiting #historyCard,
/* ⚠⚠ AND THE CHAT CARD WHEREVER IT IS STANDING - WHICH IS THE LOBBY'S `#chatDock` AS WELL.
 *
 * Colin: *"the chat box is static in the lobby page, I told you I wanted it scaleable and moveable like all the
 * other cards."* He did, and it is written down in three places - `windows.js` ("movable, resizable"), the note
 * over `applyLayout` quoting him ("the chat card needs to be movable"), and `index.html` ("It is a WINDOW like the
 * others").
 *
 * The feature was never missing; THE SELECTOR LIST WAS. This rule is what turns a card into a positioned window,
 * and it named the chat card only on the game screen and in the waiting room. Everywhere else the card is MOVED
 * into `#chatDock` by `placeScreenCards` - and `#chatDock` sits OUTSIDE `<main>`, so none of the `#screen-*`
 * selectors reach it. Measured in the lobby: `position: static`, no drag strip, no resize handles, while
 * `applyWindows` was writing left/top/width/height onto it. A card dressed as a window and laid out as a docked
 * panel.
 *
 * ⚠ WRITTEN AS `#chatDock #chatCard` AND NOT A BARE `#chatCard`, so the list still reads as one line per card per
 * host - and so a future host that forgets to bring the card along is a visible omission here rather than a rule
 * that quietly applies everywhere.
 */
body.layout-custom #chatDock #chatCard {
  position: fixed;
  z-index: 5;
  margin: 0;
}
/* ==========================================================================================
 * ⚠⚠ AND THE SAME FOR AN ACCOUNT WITH NO ARRANGEMENT AT ALL - WHICH IS THE ONE THAT WAS BROKEN.
 *
 * The rule above needs `body.layout-custom`, and that class only appears once an account HAS an arrangement. For
 * everybody else the chat card had NO `position` from anywhere: `applyWindows` was writing a rectangle onto it
 * (left/top/width/height - measured on the element's own inline style) and every one of those properties was being
 * IGNORED, because they do nothing on a static element.
 *
 * What that looked like, in the lobby, on a fresh account:
 *
 *   `#chatCard` **749x105, position: static, no grip, no handles** - a full-width bar with the stream crushed into
 *   it, sitting over the photograph with nothing to take hold of. Colin: *"the chat window is broken. It doesn't
 *   move. It's stuck in the middle of the photo."*
 *
 * ⚠ IT IS SCOPED TO THE DOCK ON PURPOSE AND IS NOT A BARE `#chatCard`. The game screen places this card in its own
 * column and that layout works - a `position: fixed` card there would tear it out of the grid it belongs in. Only
 * where the card is DOCKED does it need to be a floating window, and the dock is exactly the case that had nothing.
 * ========================================================================================== */
body:has(#chatDock) #chatDock > #chatCard {
  position: fixed;
  z-index: 5;
  margin: 0;
}
/* THE BAR IS NOT LIFTED ABOVE THE CARDS ANY MORE.
   It used to be `z-index: 70` - "always on top" - and that is what trapped the ladder, the fixture and
   the points box behind it when Colin pushed them up: a card could be dragged under the bar, its title
   bar went with it, and the one control that could pull it back out was the thing that was hidden.
   Colin: "the colfooty bar it's always on top ... it shouldn't be like that, I'm always on top of
   everything scenario, can you fix that."
   So the bar takes a card's own z-index and is ordered among them like any other window (see BAR_LIFT
   in windows.js), which means a card brought forward goes OVER the bar. The View panel, being part of
   the bar, still passes over a card - it is the way back to a card that has been covered by another. */
body.layout-custom .topbar { z-index: 5; }
/* THE BAR IS A CARD IN ITS OWN RIGHT once it has been arranged, so it gets a
   card's solid background. It is semi-transparent in the built-in layout on
   purpose (the photograph shows through), but as a floating window that meant the
   fixture's round cards and the round card's clock dials showed straight through
   its buttons and its status line. */
body.layout-custom .topbar {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
body.layout-custom #screen-game .grid { display: block; }
/* The round card is still a column inside whatever box it has been given. NO
   overflow:hidden here: it would clip the card's own resize handles, which sit
   just outside its edges, and they would stop being grabbable. The inner lists
   look after their own scrolling. */
body.layout-custom #screen-game .panel.grow { display: flex; }
/* The strip becomes the taskbar, along the bottom, over everything. */
/* THE STRIP IS ONLY AS WIDE AS WHAT IS ON IT (Colin, 11 Oct 2026: "why is it the full length of the screen ... it should only be a few pixels wider than
   it needs to be"), so it no longer covers the bottom of the whole window - only the little corner it needs. */
body.layout-custom .minstrip {
  position: fixed; left: 0; right: auto; width: max-content; max-width: 100%; bottom: 0; z-index: 80; margin: 0;
  padding: .3rem .75rem;
  background: rgba(11, 31, 20, .94);
  border-top: 1px solid var(--line); border-right: 1px solid var(--line); border-radius: 0 .5rem 0 0;
}

/* THE LADDER'S TYPE IS SIZED TO ITS CARD, in windows.js - see fitLadder. A
   min-height here stopped the card shrinking at all; a container query could not
   do it either, because the card's zoom leaves its local height nearly unchanged,
   which is what a cqh is measured against. So it is measured and set from the
   client instead, and below 7px the table scrolls rather than being cut off. */

/* ---- the furniture: a grip and eight handles, in arrange mode -------------- */
/* ONLY when the built-in layout is still in use. Once an arrangement is saved,
   the windows are already position: fixed and the grips hang off those - a bare
   `body.arranging` rule here overrode `body.layout-custom` (same specificity,
   later in the file) and turned the cards back to `relative`, so a drag moved the
   saved numbers while the cards sat somewhere else entirely. */
body.arranging:not(.layout-custom) #screen-game .grid > *,
body.arranging:not(.layout-custom) #screen-game .col-side > aside.panel,
body.arranging:not(.layout-custom) #screen-game #controlPanel {
  position: relative;
}
/* THE TITLE BAR: right across the top of the card, the name in the middle, the
   put-away button hard right. Colin: "adjust the black title bar to be full width
   across the top and center the title name and minimize on the extreme right. the
   black title bar can be 60% thinner in its depth". It was a small pill in the
   corner about 24px deep; this is 10px - 60% thinner - and spans the card. */
.wingrip {
  display: none;
  position: absolute; top: 0; left: 0; right: 0; z-index: 44;
  align-items: center; justify-content: center;
  /* 15px, half again as deep as the 10px it was: Colin, "lets make them 50%
     deeper so the mouse can find it easier". The type comes up with it. */
  height: .9375rem;
  font-size: .8125rem;
  line-height: 1;
  color: var(--text);
  background: rgba(0, 0, 0, .72);
  border-bottom: 1px solid var(--line);
  cursor: move; user-select: none;
  /* HITTABLE ON A SMALL CARD WITHOUT LOOKING CHUNKY. It would otherwise be zoomed
     down with the card along with everything else, so it is zoomed back - but by
     at most a quarter, because the reason it looked far too deep was this
     counter-zoom being applied in full. */
  zoom: min(1.25, calc(1 / var(--win-scale, 1)));
}
body.arranging .wingrip { display: flex; }
.wingrip:focus-visible { outline: 2px solid var(--accent); }
/* THE ACTIVE CARD (Colin, 4 Oct 2026): the card whose title bar has the keyboard - the one the arrow keys move -
   gets a bright amber outline and a lit title bar. Amber because nothing else in the game is that colour (green is
   "yours", blue is the focus ring), so it cannot be mistaken for anything else. An outline, not a border, so no card
   changes size and the Owner page still fits one screen. */
:root { --active-card: #ffc928; }
body.arranging .card-active,
body.arranging :has(> .wingrip:focus-within) {
  outline: 3px solid var(--active-card);
  outline-offset: -3px;
}
body.arranging .card-active > .wingrip,
body.arranging .wingrip:focus-within {
  outline: none;
  background: var(--active-card);
  color: #1a1300;
}
body.arranging .card-active > .wingrip .gripdots,
body.arranging .wingrip:focus-within .gripdots { color: #1a1300; }
.wingrip .gripdots { position: absolute; left: .3rem; color: var(--muted); letter-spacing: 1px; }
.wingrip .gripname { text-transform: uppercase; letter-spacing: .6px; font-weight: 600; }
.wingrip .griprestore {
  position: absolute; right: 1.6rem; top: 50%; transform: translateY(-50%); height: 1rem; min-width: 1rem; padding: 0 .25rem;
  display: flex; align-items: center; justify-content: center; font: inherit; font-size: .75rem; line-height: 1; cursor: pointer; white-space: nowrap;
  color: var(--text); background: var(--panel-2); border: 1px solid var(--line); border-radius: .25rem;
}
.wingrip .griprestore:hover { border-color: var(--accent, #5be28a); }
.wingrip .gripmin {
  position: absolute; right: 0; top: 50%; transform: translateY(-50%);
  display: flex; align-items: center; justify-content: center;
  height: 1rem; min-width: 1rem; padding: 0 .25rem;
  font: inherit; font-size: .75rem; line-height: 1; cursor: pointer;
  color: var(--text); background: var(--panel-2);
  border: 1px solid var(--line); border-radius: .25rem;
}
.winhandle { display: none; position: absolute; z-index: 43; }
body.arranging .winhandle { display: block; }
/* TOUCH (Colin, 18 Oct 2026, on his phone: "it's really hard to move the cards around ... it moves every single bit of the screen"): a finger that starts on a grip or a handle is a DRAG, never a page scroll -
   without `touch-action: none` the phone scrolls the whole page under the finger and cancels the drag - and on a touch screen the grip and the handles are big enough to hit. */
.wingrip, .winhandle { touch-action: none; -webkit-user-select: none; user-select: none; -webkit-touch-callout: none; }
@media (pointer: coarse) {
  .wingrip { height: 2.25rem; font-size: 1rem; }
  .winhandle-n, .winhandle-s { height: 22px; }
  .winhandle-w, .winhandle-e { width: 22px; }
  .winhandle-nw, .winhandle-ne, .winhandle-sw, .winhandle-se { width: 30px; height: 30px; }
}
/* THE HANDLES SIT INSIDE THE CARD, not straddling its edge at -3px. Straddling
   them meant the card needed `overflow: visible` while arranging or it clipped its
   own handles - and with arrange mode on by DEFAULT that made every card spill its
   content instead of scrolling it, so a ladder or a fixture that did not fit hung
   outside its own background. Colin: "the fixture card background is to small for
   the contents". Inside the edge they are never clipped, so the cards keep their
   own scrolling all the time. */
.winhandle-n { top: 0; left: .5rem; right: .5rem; height: 6px; cursor: ns-resize; }
.winhandle-s { bottom: 0; left: .5rem; right: .5rem; height: 6px; cursor: ns-resize; }
.winhandle-w { left: 0; top: .5rem; bottom: .5rem; width: 6px; cursor: ew-resize; }
.winhandle-e { right: 0; top: .5rem; bottom: .5rem; width: 6px; cursor: ew-resize; }
.winhandle-nw, .winhandle-ne, .winhandle-sw, .winhandle-se { width: 12px; height: 12px; }
.winhandle-nw { top: 0; left: 0; cursor: nwse-resize; }
.winhandle-ne { top: 0; right: 0; cursor: nesw-resize; }
.winhandle-sw { bottom: 0; left: 0; cursor: nesw-resize; }
.winhandle-se { bottom: 0; right: 0; cursor: nwse-resize; }
/* THE HANDLES DO NOT SCALE WITH THE CARD. They live inside a window that is
   zoomed to match its size, so on a card dragged down to half size they would be
   half size too - and the corners are exactly what has to stay hittable. They are
   zoomed back by the window's own factor in full. The title bar is different: it
   is capped at a quarter, see .wingrip above. */
.winhandle { zoom: calc(1 / var(--win-scale, 1)); }
/* Arrange mode says so, so nobody wonders why clicking a card does odd things.
   ONE LINE PER CARD - the same windows the layout-custom rule above names, in
   the same words, so the two lists cannot drift apart. They are the windows in
   WINDOWS in web/windows.js: the bar, the fixture, the round, the ladder, the
   points box, the score variables card and the shot timing diagnostic.
   IT WAS `#screen-game .grid > *` AND THAT WAS THE BUG. The grid's children are
   WRAPPERS, not cards. The fixture and the round happen to be their own cards, so
   those two looked right - but the whole right-hand side is one child, `.col-side`,
   the flex column that CONTAINS the ladder, the score variables card and the points
   box. So the line was drawn round that column: one big rectangle, enclosing all
   three, which is exactly what Colin was looking at - "big light green dotted
   rectangles right around the outside ... those mysterious big rectangles actually
   belong to score variables, points box and ladder cards respectively" - while the
   ladder, the points box and the score variables card had no line of their own at
   all. (A separate `#arrangeFrame` drew a second, aggregate rectangle round the
   union of every card; it has been removed.) Same lesson as the layout-custom rule
   above, where the same wildcard put the ladder inside `.col-side`'s stacking
   context and it could not be raised. */
body.arranging .topbar,
body.arranging #screen-game .fixture-panel,
body.arranging #screen-game .panel.grow,
body.arranging #screen-game .col-side > aside.panel,
body.arranging #screen-game #controlPanel,
body.arranging #screen-game #scoreVarsCard,
body.arranging #screen-game #clubCard,
body.arranging #screen-game #shotsCard,
/* THE CHAT CARD IS ON TWO SCREENS - the game and the waiting room, because that
   is where the host explains the rules - so it is named on BOTH here. Written out
   rather than a bare `#chatCard`, so the list still reads as one line per card. */
body.arranging #screen-game #chatCard,
body.arranging #screen-game #historyCard,
/* THE HISTORY CARD IS ON THE WAITING ROOM AS WELL - see the layout-custom list above, and
   SCREEN_CARDS in app.js. Both cards are named one line each on both screens, so the list still
   reads as one line per card per screen. */
body.arranging #screen-waiting #chatCard,
body.arranging #screen-waiting #historyCard,
/* AND THE CHAT CARD IN THE LOBBY'S DOCK, for the reason given in the layout-custom list above: `#chatDock` is
   outside every `#screen-*`, so a rule that names only the screens leaves the card static wherever it is docked -
   which is the lobby, the records and the boards. */
body.arranging #chatDock #chatCard {
  /* A HAIRLINE ON EVERY CARD, however far it is zoomed. The outline is drawn in the
     card's own zoomed space, so a card at half size got a half-pixel line - measured
     0.84px on the fixture at zoom 1.197 and about 0.45px at the zoom floor, which is
     a line that can vanish on a cheap screen. Dividing by the card's own scale, the
     same trick .winhandle uses, keeps it one crisp pixel at every size. */
  outline: calc(1px / var(--win-scale, 1)) dashed rgba(53, 208, 127, .5);
  outline-offset: -1px;
}
/* NO overflow: visible ANY MORE. The handles are inside the cards now, so nothing
   needs to escape - and forcing it made every card spill its content while
   arranging, which is the default. */
/* (The View panel is anchored to the top bar, which is already position:sticky -
   a positioned element, so an absolute child hangs off it.) */

/* THE TOP BAR, IN rel UNITS.
   Colin: "you shouldnt be using pixels, its poor coding, you should use css so
   it works on every type of display". So every length in this block is in rem -
   it tracks the reader's own text size, not a number baked for one screen.
   1rem = 16px at the default, so at the default these are the same sizes the
   bar had before; they only diverge when a reader has asked for bigger text,
   which is exactly when they should. Measured before this change: at a root
   font size of 20px the wordmark, the button, its height and the bar height
   were all still identical, because every one of them was px.
   1px borders stay 1px on purpose: they are hairlines, and a hairline that
   scales reads as a mistake rather than as accommodation. */
.topbar {
  display: flex; align-items: center; justify-content: space-between;
  flex-wrap: wrap; row-gap: .5rem;
  /* A MINIMUM gap between the brand and the account cluster, which
     space-between alone does not give: when the two ends add up to the full
     width they meet, and the wordmark ran straight into the season title
     (measured at 900px: brand right edge 234, title left edge 234). The gap is
     a floor - at a wide window space-between still pushes them apart. */
  gap: 1rem;
  padding: .75rem 1.25rem; border-bottom: 1px solid var(--line);
  background: rgba(12, 18, 16, .94); backdrop-filter: blur(6px);
  position: sticky; top: 0; z-index: 10;
  /* The bar belongs to the app's column, like everything else. It used to span
     the viewport and push the account menu to the far right - which put it on
     the photograph. */
  width: var(--ui-col);
  max-width: 100%;
  /* ABOVE EVERY CARD, because the VIEW PANEL HANGS OFF THIS BAR.
   *
   * Colin: *"when you click the view button up on the COLFOOTY bar the drop down opens up but it
   * doesn't come to the front ... the points box and the round cards stay on top of the view drop
   * down."* The panel is a child of the bar, so it is trapped by the bar's own stacking context: its
   * `z-index: 90` only counts against the bar's other children, and the bar itself sat at 10 (or 20
   * in an arranged layout) while a card the reader has just clicked is raised to `Z_BASE + z` - up to
   * **55**. A raised card therefore painted over the bar and over everything hanging from it.
   *
   * The bar is chrome: it belongs above the cards, always. 70 clears the cards' ceiling of 55 while
   * staying under the minimise strip (80) and the panel's own 90, which are meant to be above it. */
  z-index: 70;
}
/* THE COLFOOTY BAR CARRIES A TITLE BAR TOO, and its padding was 12px against a
   title bar up to 18.75px deep - so the top of the Back button ran under it.
   Colin: "the colfooty bar ... the title bar is also blocking some of the buttons,
   all of the text in there has to be a little bit lower, part of it's missing."
   The padding is REBALANCED rather than increased: the top gains exactly what the
   title bar needs and the bottom gives the same amount up, so the bar keeps its
   height and nothing below it moves. That matters - the round card's height is a
   calc() off the viewport that assumes a 59px bar plus the game screen's padding,
   so a taller bar would push the card's last game under the bottom of the window. */
body.arranging .topbar:has(> .wingrip) {
  --topbar-clear: calc(.9375rem * min(1.25, calc(1 / var(--win-scale, 1))) + 2px);
  padding-top: var(--topbar-clear);
  padding-bottom: calc(24px - var(--topbar-clear));
}
.brand { display: flex; align-items: center; gap: .75rem; }
/* letter-spacing is in em so the tracking stays in proportion to the wordmark
   as the wordmark grows - 2px on a 17px word is .12em. */
.brand h1 { font-size: 1.0625rem; margin: 0; letter-spacing: .12em; }

/* ONE BACK BUTTON, the same everywhere.
   It used to be three different things: a padded button in the top bar with a
   1px vertical offset, and two bare underlined text links (History, Owner) with
   ZERO horizontal padding that did not line up with the button beside them.
   Now it is a real button with its arrow and its word centred in it by flex,
   which is what "centred" actually needs - a glyph and a word have different
   heights, and only flex evens them up. */
.backbtn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .4375rem;
  height: 2.125rem;
  padding: 0 .875rem;
  font: inherit;
  font-size: .8125rem;
  font-weight: 600;
  line-height: 1;
  color: var(--text);
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: .5rem;
  text-decoration: none;
  cursor: pointer;
}
.backbtn:hover { background: #26332d; border-color: #3f5148; }
.backbtn:active { transform: translateY(.0625rem); }
.backbtn .arrow { font-size: .9375rem; line-height: 1; }
/* THE DESTRUCTIVE CONTROL, MADE VISIBLE. Colin, on the waiting room where "Cancel this
   competition" sat as a muted underlined link beside "Leave this competition": he did not
   see it, having asked for exactly that button. So it keeps a secondary button's shape - it
   is not the primary action on the screen - but carries the danger colour, which is what
   makes it findable without making it inviting. It still asks before it does anything. */
.dangerbtn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: .6875rem 1rem;
  font: inherit;
  font-weight: 600;
  color: var(--danger);
  background: var(--panel-2);
  border: 1px solid var(--danger);
  border-radius: 8px;
  cursor: pointer;
}
.dangerbtn:hover { background: rgba(255, 92, 108, .12); }
.dangerbtn:active { transform: translateY(.0625rem); }
/* Beside a primary button it matches that button's height, so the pair sits on
   one line instead of one of them floating. */
.authrow .backbtn { height: auto; padding: .6875rem 1rem; }
.ball { color: var(--accent); font-size: 1rem; }
.whoami { display: flex; align-items: center; gap: .875rem; font-size: .8125rem; }
/* Owner / History / Sign out picked up a border from this rule AND an underline
   from `button.link`, which reads as neither a link nor a button. Pick one. */
.whoami .small-btn { padding: .25rem .625rem; border: 1px solid var(--line); border-radius: .375rem; text-decoration: none; }
.whoami .link { color: var(--text); }
.status { color: var(--muted); font-size: .8125rem; }
.whoami { display: flex; align-items: center; gap: .875rem; }
.signed-in { display: flex; align-items: center; gap: .5rem; font-size: .8125rem; }
/* THE BAR MUST NOT BREAK WHEN THE WINDOW IS NARROW.
   The season title in #status ("COLFOOTY — Colin's season · 0 coaches") is the
   one long thing up here, so it is the only thing allowed to give: it truncates
   with an ellipsis, on one line. Everything else keeps its natural width.
   Before this the cluster was squeezed instead, and at 900px the title wrapped
   to two lines, "signed in as" and the Home/Sign-out buttons each wrapped
   inside themselves, and the whole bar grew from 59px to 71px tall - so the
   wordmark and the Back button were left sitting against a two-line block with
   a 0px gap. Measured at 900px. */
.brand { flex: 0 0 auto; }
.whoami { flex-wrap: nowrap; min-width: 0; }
.status { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.status .st-season { background: var(--accent, #2f6fdb); color: #fff; font-weight: 700; padding: .05rem .5rem; border-radius: 999px; }
.status .st-host { color: var(--text, inherit); font-weight: 700; }
.signed-in,
.whoami .small-btn { flex: 0 0 auto; white-space: nowrap; }
.small-btn { padding: .125rem 0; font-size: .75rem; }
.panel.narrow { max-width: 420px; margin: 40px auto; }

/* ---------------------------------------------------------------------------
   THE HILLS, ON THE OWNER PAGE
   ---------------------------------------------------------------------------
   Four rows, one per scoring method, each a bag of marbles drawn as bars: taller
   where more marbles sit on that goal count, with the COUNT printed over each bar
   and the goal number under it, because that is the language the first hill was
   drawn in. The averages and the "1 in N" beside the name are the two things the
   choice turns on - what a point of pool is worth, and how rare the top is.

   THE COLUMNS SHARE THE WIDTH (`flex: 1 1 0`), so strength 12 with thirteen counts
   fits the panel exactly as strength 4 with five does. Nothing here scrolls
   sideways: the owner page is a narrow panel and a hill that has to be scrolled to
   be read is not a hill you can compare with the one above it.

   THE BAR HEIGHT IS 0.68 OF THE COLUMN, not all of it, because the column also
   carries the marble count above the bar and the goal number below it.
   --------------------------------------------------------------------------- */
.hills { display: flex; flex-direction: column; gap: 8px; margin: 10px 0 4px; }
.hill-row {
  border: 1px solid var(--line); border-radius: var(--radius);
  padding: 8px 10px 6px; cursor: pointer; background: rgba(12, 18, 16, .5);
}
.hill-row:hover { border-color: var(--muted); }
.hill-row.on { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }
.hill-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
.hill-name { font-weight: 700; font-size: 13px; }
.hill-row.on .hill-name { color: var(--accent); }
.hill-stats { display: flex; gap: 10px; font-size: 11px; color: var(--muted); white-space: nowrap; }
.hill-bars { display: flex; align-items: flex-end; gap: 2px; height: 76px; margin-top: 6px; }
.hill-col {
  flex: 1 1 0; min-width: 0; height: 100%;
  display: flex; flex-direction: column; justify-content: flex-end; align-items: center;
}
.hill-count { font-size: 10px; line-height: 1.1; color: var(--muted); }
.hill-bar {
  width: 100%; height: calc(var(--tall, 0%) * .68);
  background: var(--line); border-radius: 2px 2px 0 0;
}
.hill-col.peak .hill-bar { background: var(--accent); }
.hill-col.peak .hill-count { color: var(--text); }
.hill-goal { font-size: 10px; line-height: 1.2; color: var(--muted); }
.hill-note { margin: 6px 0 0; }

.authrow { display: flex; gap: 10px; margin-top: 16px; flex-wrap: wrap; }
button.wide { width: 100%; margin-top: 16px; }
.sectionhead {
  margin: 22px 0 8px; font-size: 12px; text-transform: uppercase;
  letter-spacing: .8px; color: var(--muted); font-weight: 600;
}
/* THE CODE BOX IS HALF THE WIDTH, AND THE BUTTON SITS WITH IT.
   Colin: *"the join a competition game code is too wide and if you made it half the width then you'd
   be able to fit in the Play with Friends portion of the card higher so that you don't have to
   scroll up and down."* A game code is four to seven digits; a field 780px wide for it was taking a
   whole row of the card from the things below - and a guest arrives here FIRST, so whatever this row
   costs is paid above everything else. Half the row for the input, the Join button straight after it,
   and the rest of the row left empty rather than filled. */
/* THE WAY BACK TO THE SEASON HAS TO SHOUT A BIT.
 *
 * Colin, on the first version, sitting quietly among the bar's other small grey links: *"let's make the
 * buttons stand out a bit ... it's hiding up there in the COLFOOTY bar not standing out very much."*
 * He is right, and it matters more than looks: the whole reason this button exists is that people get
 * lost and need ONE obvious thing to press. A control that blends into a row of grey words does not
 * do that job. So it is the only FILLED button on the bar - the game's own accent green, bold, with
 * room around it - and everything else up there stays a quiet link.
 */
.gameback {
  font-weight: 700;
  font-size: 13px;
  padding: .4rem .85rem;
  border-radius: 8px;
  box-shadow: 0 2px 10px rgba(53, 208, 127, .22);
}
.gameback:hover { box-shadow: 0 3px 14px rgba(53, 208, 127, .34); }

/* ---------------------------------------------------------------------------
   WHERE YOU CAN GO - Home, New season, Return to your game
   ---------------------------------------------------------------------------
   Colin: *"we want to be able to get to all of the locations that we need to get
   to, like right back to the original homepage, back to the lobby, back and
   forward to the game page, a method that is very easy for people to understand."*

   So the three places sit in ONE ROW next to the wordmark, in the order you would
   travel them - the front door, the lobby, the season you are in - each with a
   plain two-word label. A reader looking for "where do I go" finds one group
   instead of three controls scattered between the wordmark and the account
   cluster, which is where "Return to your game" used to hide.

   WHICH OF THEM IS UP IS NOT DECIDED HERE - `syncBarNav` in app.js owns that, so a
   button can never be up on one screen and dead on another. This is only how they
   look: quiet links for the two that move you between screens, and the accent
   frame for the one that takes you back to the season, sized down so it does not
   shout louder than the wordmark it sits beside.
   --------------------------------------------------------------------------- */
.navgroup { display: flex; align-items: center; gap: .5rem; margin-left: .25rem; }
.navgroup .small-btn {
  padding: .25rem .625rem;
  border: 1px solid var(--line);
  border-radius: .375rem;
  text-decoration: none;
  color: var(--text);
  white-space: nowrap;
}
.navgroup #navGame {
  font-size: .8125rem;
  padding: .3rem .7rem;
  border-radius: .375rem;
  box-shadow: none;
}
@media (max-width: 1100px) {
  /* THE BAR WRAPS BEFORE THE NAVIGATION DOES. At 1100 and below the three labels and the wordmark stop fitting on
     one line, and the client already measures the bar's height for the cards (measureBar) - so this only has to
     keep the group together rather than let it split mid-row. */
  .navgroup { flex-wrap: wrap; }
}


/* ==========================================================================================
 * ⚠ THE JOIN ROW COULD NOT FIT ITS OWN CARD, AND `min-width: 9rem` WAS THE REASON.
 *
 * Colin, looking at the lobby: *"the join button overflows it."* MEASURED rather than guessed -
 * `.joinrow input { flex: 0 1 50%; min-width: 9rem }` is a HARD FLOOR of 144px on the field, and the Join button
 * beside it is about 70px:
 *
 *      144 (the input's floor)  +  10 (the gap)  +  ~70 (the button)  =  ~224px
 *
 * and the card it lives in, in the two-column row at the bottom of the lobby, is about **182px** of content. Two
 * hundred and twenty-four does not go into one hundred and eighty-two, so the button was pushed out of the card.
 *
 * ⚠ AND `min-width` IS A DEMAND, NOT A LIMIT - which is the same lesson as the cards (§16) and the `--ui-col`
 * relationship: a floor beats a cap every time. So the floor goes, the field is allowed to shrink, and the row is
 * allowed to WRAP so that on a narrow card the button drops to its own line instead of overflowing.
 *
 * ⚠⚠ AND THE TWO OF THEM ARE ONE HEIGHT. Colin sent a picture of the row: the Join button was TALLER than the
 * field beside it and hung out of the card. `button` carries `padding: 11px 16px` from the base rule and the input
 * is a text control with its own metrics, so the pair never agreed - and `align-items: stretch` could not help
 * because the field sets its own box. Both are given the SAME explicit height, which is the only way to be sure,
 * and the row centres them so a wrap cannot reintroduce a mismatch.
 * ========================================================================================== */
.joinrow { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }
.joinrow input { flex: 1 1 9rem; min-width: 0; height: 38px; }
.joinrow button { flex: 0 0 auto; height: 38px; padding-top: 0; padding-bottom: 0; }

/* ==========================================================================================
 * ⚠ FOUR MORE FIELDS THAT WERE THE WIDTH OF THEIR CONTAINER. Same criterion as the front door:
 * a field is the longest thing typed into it, never the row it happens to sit in.
 *
 * These were found by `tools/_check-control-widths.mjs`, not by being told - Colin had named the lobby and the
 * home page, and running the criterion over every screen turned up these:
 *
 *      #joinCodeInput       455px drawn / 102px needed   a game code is six digits
 *      #lobbyTeam           744px drawn /  52px needed   one club name, and it was the full card
 *      #ownUnlockPassword   378px drawn /  60px needed   the owner password, typed once
 *
 * `max-width` RATHER THAN A FIXED `width`, deliberately: these are all inside rows that wrap on a narrow
 * window, and a hard width would overflow a phone. A cap says "never wider than its contents need" and lets the
 * row shrink when it has to, which is the same rule the buttons follow.
 *
 * EACH NUMBER IS THE LONGEST THING THAT GOES IN THE BOX, and the reason is written beside it so the next person
 * can re-derive it rather than guess:
 *
 *      #joinCodeInput       a game code is six digits; nine characters of field at 15px
 *      #lobbyTeam           one club name - the longest is NORTH MELBOURNE, fifteen characters
 *      #ownUnlockPassword   one password, twelve characters of it
 * ========================================================================================== */
#joinCodeInput { max-width: 140px; }
#lobbyTeam { max-width: 180px; }
#ownUnlockPassword { max-width: 150px; }
/* ⚠ THE CLUB PICKER IN THE LOBBY IS THE SAME CONTROL AS THE ONE IN THE WAITING ROOM, so it gets the same
   fifteen-characters-plus-arrow width. `.coachpick` is `flex: 0 1 260px` and the select fills it; this is the cap
   that stops it. It says "None" until a club is picked, and it must not resize when NORTH MELBOURNE arrives. */
#soloTeam, #soloTeamDD { max-width: 180px; }
/**
 * (THE GUEST'S PROMOTED JOIN BOX IS GONE, and this is where its rule was.)
 *
 * Colin asked for it once - *"when a guest goes to play there should be a join a competition that sort of sticks
 * out pretty clearly at the top"* - and then looked at the result: *"the lobby page it looks different with
 * Player 28 compared to the signed in player ... the join a competition is too wide but it's not wide on the
 * colin user lobby page."* Lifting it out of the grid and across the top cost a whole row of height, so a
 * guest's lobby no longer fitted the window either. Everybody gets the same grid now, with Join in the second
 * row beside Just watch, which is on screen without scrolling - the thing the promotion was really for.
 */
.gamelist { list-style: none; padding: 0; margin: 0; }
.gamelist li {
  display: flex; justify-content: space-between; align-items: center; gap: 12px;
  background: var(--panel-2); border: 1px solid var(--line);
  border-radius: 8px; padding: 12px 14px; margin-bottom: 8px;
}
.gamelist .meta { color: var(--muted); font-size: 12px; }

/* The app column. Roomy, but the welcome screen is allowed to run wider than
   the in-game panels, which read better at a narrower measure. */
/* The whole app lives in the left column, LEFT-ALIGNED. It used to be a centred
   block 1240px wide, which measured out as more than half of the photograph
   covered by panels. */
main { padding: var(--layout-pad); max-width: var(--ui-col); margin: 0; }
/* The welcome screen has its own two-column layout - cards on the left, the
   photograph filling everything to their right - so its main block runs the
   full width rather than sitting in the app's column. */
main:has(#screen-auth:not(.hidden)) { max-width: none; margin: 0; }
/**
 * THE FRONT DOOR GIVES UP ITS OWN PADDING SO THE BAR CAN BE THERE.
 *
 * Colin: *"yeah that sounds much better - put it back"* - the bar belongs on the welcome screen. The price is
 * 53px of the window, and there is nothing above the page to take it from: the stage's height is driven by the
 * three cards (889px), so the room has to come out of the front door's own furniture. Measured before this by
 * tools/_check-front-door-fit.mjs: **1004px in a 951px window** with the bar up.
 *
 * Everything below is scoped to this screen. The page's 22px padding top and bottom is the biggest single
 * saving and the least missed - it is air, not content - and the cards' own padding comes down with it. No
 * WORD is touched: he wrote this copy and it is not the thing to trim.
 */
main:has(#screen-auth:not(.hidden)) { padding: 10px 22px 12px; }
/* The game screen runs its panels right down to the fold, and the page's own
   bottom padding was putting it 2px over - which is enough to raise a
   scrollbar on a screen that is meant to fit in one window.
   The top and bottom padding is smaller than every other screen's on purpose:
   Colin wants the cards pushed up and given the height - "you've got room, you've
   got lots of room, you can push the cards up higher, extend them a bit". The
   top bar takes 59px and the sides stay at their usual 22px. */
main:has(#screen-game:not(.hidden)) { padding: 8px 22px 10px; }
.brandword { color: var(--accent); letter-spacing: 2px; }

.screen.hidden, .hidden { display: none !important; }

.panel {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 20px;
}
.panel + .panel { margin-top: 16px; }
/* Every card's name is centred in its card. Colin: "Put the word ladder in the
   center of the card too and the word fixture in the center of that card and the
   word round three of 24 in the center of that card". */
.panel h2 { margin: 0 0 10px; font-size: 17px; text-align: center; }
.panel h3 { margin: 18px 0 8px; font-size: 15px; }

/* The game screen: the whole draw, the round being played, and the ladder - three
   panels across the screen, the way the old program had it. The fixture is the
   narrow one now: rounds are drawn as a single column of games, so a card is tall
   and thin, and the width it gives up goes to the middle, which is where the
   games and their clocks live. */
.grid {
  display: grid;
  /* 0.58, not 0.55: at three cards across the fixture's games column came out at
     exactly the width of "GEEL v WBUL" - zero slack, so any font or padding
     change truncated 40 fixtures. The middle still has well over half the page. */
  grid-template-columns: minmax(0, 0.58fr) minmax(0, 1.72fr) minmax(0, 1fr);
  gap: 16px;
  /* ALL THREE CARDS THE SAME HEIGHT. `align-items: start` left the fixture and
     the ladder at their own content height, so with the middle card given the
     whole window the other two stopped 21px short of it. Colin: "you've got lots
     of room, you can push the cards up higher, extend them a bit". Stretching
     them also hands the ladder the extra height it wants (the rows take it). */
  align-items: stretch;
}
@media (max-width: 1000px) { .grid { grid-template-columns: 1fr; } }

/* THE CHAT CARD IS THE EXTREME RIGHT-MOST CARD ON THE GAME SCREEN.
   Colin, asked where it should sit so that it is always grabbable: "have it as extreme
   right most card". It used to be the last child of `.col-side` - a column whose height
   is fixed at var(--card-h) - so the card overflowed that column and was pushed past the
   bottom of the window. Measured before this: 409x120 at y=855 in a 900px window, 75px
   below the fold, with its lower half and every south/east resize handle outside the
   window. That is what he reported as "needs a title bar, resize ability".

   So it gets a column of its own, stretched to the full card height like the other
   three (placeChatCard moves the card into `.grid` - see app.js). ONLY WHILE IT IS ON
   SCREEN: putting it away (or a watch competition, which has no board) drops the
   fourth track rather than leaving an empty stripe of window, and `body.hide-fixture`
   keeps its own three-column arrangement with the chat column still on the end. */
@media (min-width: 1001px) {
  body:not(.hide-chat):not(.hide-chat-watch) .grid {
    grid-template-columns:
      minmax(0, 0.58fr) minmax(0, 1.72fr) minmax(0, 1fr) minmax(0, 0.92fr);
  }
  body.hide-fixture:not(.hide-chat):not(.hide-chat-watch) .grid {
    grid-template-columns: minmax(0, 1.72fr) minmax(0, 1fr) minmax(0, 0.92fr);
  }
}
/* A grid child, so it may be squeezed: the card's own min-width would otherwise hold
   the fourth column open wider than its share. The list inside it scrolls, which is
   what the card's `overflow: hidden` and #chatList's `overflow-y: auto` are for. */
#screen-game .grid > #chatCard { min-width: 0; }

/* The round being played, in the biggest type on the screen. One game is two
   rows - home then away - with the club on the left, the score after EACH
   quarter, then the total. Fixed columns so the quarters line up right down the
   round, which is what makes the progression readable. */
.scoreboard { margin-top: 6px; }
/* One game is a BLOCK: its own ground clock on the LEFT, then its two sides.
   The clock is on the left because that is what ties it to the game - on the
   right, past the total, there was nothing saying which game it belonged to.
   The nine games SHARE the panel height between them (flex: 1 1 0 each) and the
   dial takes its size from the row, so all nine always fit whatever the window
   is doing - the panel uses the full height instead of leaving a gap at the
   bottom and scrolling the last game off. */
/* The gap between one game and the next. Colin: "I want a bigger gap between the
   games they're too close together", and with the play buttons gone from this
   card there is more height to spend on it. The games themselves give up the rest
   (they are flex: 1 1 0). */
/* The gap between games: it used to be 1.25rem, which is 20px x 8 gaps = 160px of
   the round card spent on AIR - and at a smaller window that air was the difference
   between all nine games fitting and the card growing a scrollbar that cut the
   ninth one off. Colin: "It doesn't come back at the rounds end because card is big
   enough to hold everything." The dials get the height instead. */
#liveScoresList { display: flex; flex-direction: column; gap: .75rem; }
/* A GAME NEVER SHRINKS BELOW ITS OWN CONTENTS.
/* A GAME NEVER SHRINKS BELOW ITS TWO ROWS OF TEXT.
   The nine games divide the card's height between them, which is fine until the
   card is small or the text is made big - then they crushed and the rows collided.
   Colin, at 175%: the club names sat on top of each other.
   Three tries, in order. `min-height: 0` (the original) let them crush to nothing.
   `min-height: auto` handed the floor to whatever was intrinsically biggest - the
   clock dial at 76px - and made every game 76px on the normal screen, so the round
   card gained a scrollbar it should not have. A floor divided by the card's zoom
   was wrong too: the card's zoom multiplies the content and the floor together
   anyway.
   The answer is a plain 3.75rem, because LOCAL sizes do not change with the View
   panel's text size - the zoom scales what is painted, not the CSS px the layout
   works in - so 60px holds two 19px rows at every text setting, and it scales with
   a resized card on its own. */
.sc-game {
  flex: 1 1 0;
  min-height: 3.75rem;
  display: flex; align-items: center; gap: 16px;
  /* A game's own content never reaches the LIST's scrollbar. Its two rows measure
     57px against a 60px floor, so anything left over is a fraction of a pixel -
     and a fraction of a pixel is all it takes to put a scrollbar on the round card
     mid-round, when the scores appear. Colin: "when the round completes the
     scrollbar disapears properly, but why is it show itself during game play".
     With this, the scrollbar answers only to the games' own boxes and the gaps,
     which is what the floor is there to control. */
  overflow: hidden;
}
/* A SHORT WINDOW: the nine games, the gaps and the head row all have to fit a
   card whose height is a fixed calc() off the viewport, so a shorter window
   cannot give them more room - something has to give, and it must not be type.
   Measured at 1440x800 before this rule: the list's box was 637px and its
   content 684px (head row 36 + list margin 6 + nine games at the 60px floor =
   540 + eight gaps at 12 = 96). 47px of the ninth game sat behind a scrollbar;
   at 1366x768 it was 79px; at Colin's own 1887x951 there is no scrollbar at all,
   which is exactly why he never saw it.
   The three cuts are all air or slack: the gap halves, the game floor drops to
   56px - still above the 57px the two rows of text actually measure, so no glyph
   is clipped - and the 6px of air above the head row goes. Measured after: 0px of
   overflow at 1440x800, 1366x768, 1280x800 and 1280x720, and 0 at every larger
   size. Nothing inside a game moves, so the card reads the same; it just fits.
   This sits AFTER the base rules on purpose: same specificity, so the later rule
   is the one that wins inside the query. */
@media (max-height: 860px) {
  #liveScoresList { gap: .375rem; }
  .sc-game { min-height: 3.5rem; }
  .scoreboard { margin-top: 0; }
}
/* A FIXED slot, so the header row can line its columns up with the games. The
   dial itself fills the slot's height and stays round.
   STRETCHED, not centred, and that is what stops the round card showing a
   scrollbar it does not need. A centred flex item has an auto height, so the
   dial's `height: 100%` had nothing to resolve against and the SVG fell back to
   its intrinsic 76px - taller than the 63.6px a game gets on the built-in screen
   - and the last game's overflow was enough to call up a scrollbar for good.
   Colin: "the round scroll bar never disapears, even when there is no scrolling
   needed". Stretched, the slot is the game's height and the dial follows it. */
.sc-clockslot, .sc-clock {
  flex: 0 0 76px; align-self: stretch;
  display: flex; align-items: center; justify-content: center;
}
.sc-clock svg { height: 100%; width: auto; aspect-ratio: 1 / 1; max-height: 76px; display: block; }
/* ONE LINE ACROSS THE WHOLE WIDTH, instead of a little underline under each
   heading. Colin: "you underlined quarter one quarter two quarter 3 quarter 4 -
   we have two character underlines. Maybe you should underline team as well,
   just put a line right across the whole width of the card". The line is on the
   heading ROW, so it runs the full width of the card including under TEAM. */
.sc-headrow {
  display: flex; align-items: center; gap: 16px;
  border-bottom: 1px solid var(--line);
  padding-bottom: 6px;
}
.sc-teams { flex: 1 1 auto; min-width: 0; }
.sc-row {
  display: grid;
  /* CENTRED, AND HUGGING ITS CONTENT.
     Every column is centred - the heading over the score beneath it - which is
     what Colin asked for: "the 4th quarter heading has to come across a little
     bit to be in the center of the column so that everything is centred. also
     quarter 2 can come across a tiny bit and same for quarter 3. quarter 4's got
     to come across a lot more because we're involving the totals as part of the
     quarter 4 score."
     The widths are what make that read the way he wants. Because the columns hug
     their content, centring moves the second and third headings only a little -
     their columns are barely wider than their scores - while the fourth moves a
     long way, because its column has to hold the quarter's score AND the match
     total side by side. `space-between` spends whatever is left on the gaps, so
     the scores still reach the right-hand edge.
     minmax(0, ...) so the columns can still give way on a narrow window rather
     than overflowing the panel. */
  grid-template-columns:
    minmax(0, 12rem) minmax(0, 4.5rem) minmax(0, 4.5rem) minmax(0, 4.5rem) minmax(0, 6.5rem);
  column-gap: .875rem;
  justify-content: space-between;
  align-items: baseline;
  padding: 0;
}
.sc-head { margin-bottom: 5px; }
.sc-team {
  font-size: 19px;
  letter-spacing: .3px;
  text-transform: uppercase;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.sc-q {
  font-size: 17px;
  text-align: center;
  /* THE ROW'S COLOUR, which is the club's own colour - so every score in a row
     matches the club name at the start of it. It used to be a flat muted grey,
     with the club name the only thing in colour. */
  color: inherit;
  font-variant-numeric: tabular-nums;
}
/* The quarter the clocks are running: the one number on the panel that moves.
   IN THE CLUB'S OWN COLOUR, like every other score. It used to be forced to the
   bright text colour, and because the running quarter is the ONLY score on screen
   while it is being played, that made every score on the card white until the siren
   went. Colin: "during game play the scores are all white then after the qtr
   finishes they change to their proper club colors, can we fix that please".
   Heavier and a touch brighter is enough to read as the live one. */
.sc-running { font-weight: 700; filter: brightness(1.2); }
.sc-total {
  font-size: 20px;
  font-weight: 700;
  text-align: right;
  font-variant-numeric: tabular-nums;
  /* THE ROW'S LINE BOX, NOT THE TYPE'S. At 20px bold the default leading made this
     cell 30px tall while the rest of the row was 25.5px - so the row grew the
     moment a score appeared in it and the round card's scrollbar came back.
     Colin: "when i click pay round button the scrollbar in the round card returns".
     20 x 1.275 = 25.5px, so the row is the same height before and after a round. */
  line-height: 1.275;
}
/* THE FOURTH QUARTER CARRIES THE MATCH TOTAL TOO - there is no separate Total
   column any more, and the total COUNTS WITH the quarter rather than appearing
   when it ends. Colin: "we don't need the word total so 4th quarter actually
   will be the goals the points and the total ... and that's all in one
   particular column", and "the total needs to be involved in the counting of the
   4th quarter". The quarter's score and the total sit side by side, centred in
   the column like everything else. */
.sc-q4 {
  display: inline-flex;
  justify-content: center;
  align-items: baseline;
  gap: .5rem;
}
/* THE HEADINGS ARE HEADINGS. Colin: "underline team quarter one quarter two
   quarter 3 quarter 4 and make them much much bigger because they're the
   headings". At 11px they were smaller than the scores they label (17px); at 1rem
   they are bigger than them. The underline is NOT here - it is one line across
   the whole heading row now, see .sc-headrow above. */
.sc-head .sc-team,
.sc-head .sc-q,
.sc-head .sc-total {
  font-size: 1rem;
  font-weight: 600;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: .8px;
}
/* THE COACH'S OWN ROW IS MARKED BY ITS WEIGHT, NOT BY SOMEBODY ELSE'S COLOUR.
 *
 * There used to be a rule here: `.sc-me .sc-team, .sc-me .sc-total { color: var(--accent); }` - his own
 * row's name and match total forced to the app's green. It is older than the per-club colours and it now
 * fights them. Colin, sending two screenshots of it:
 *
 *   *"look into the total score 232 on the end of geelong, why is it green? It's not green on the host's
 *   card, it's only green on the Player 28 card ... incidentally Geelong is the coached team of Player 28
 *   ... and also Carlton on the host has got a green total as well, why is that?"* and *"the number 96 on
 *   the end in a green font. not good."*
 *
 * Measured on his live game, row by row: every club's total is that club's own colour - Hawthorn
 * rgb(230,184,74), Geelong rgb(168,200,255), Collingwood rgb(242,244,243), Brisbane rgb(212,136,160) - and
 * his own row's total was rgb(53,208,127), the accent green. That is the whole of it: **the green followed
 * the COACH, not the club**, which is why Geelong's 232 was green on one card and Carlton's 96 on the
 * other. It was the only number on the card that did not belong to its club, and it contradicted the rule
 * the whole scoreboard is built on (see .sc-q above: "every score in a row matches the club name at the
 * start of it").
 *
 * The NAME escaped it only by accident: `clubName()` writes the club colour as an INLINE style, and an
 * inline style beats a class rule - so only the total ever showed the green.
 *
 * The row is still unmistakably his: the name is BOLD (see .club-me). A weight that marks him without
 * contradicting his club is a better marker than a colour that does - and it is what he asked for
 * ("the team being coached its font is bold which I like").
 */
/* THE COACH'S OWN ROW IS ONE POINT BIGGER THAN THE OPPONENT'S.
   Colin: *"the team being coached its font is bold which I like but I want you to increase the font
   size by one and we'll take a look at what that looks like ... and I assume the score is also in the
   bold as well don't they, and up the scores by one as well."* So his club's name goes 19 -> 20px and
   his own row's numbers go 17 -> 18px, with the match total 20 -> 21px. ONLY HIS ROW: he asked for the
   bold one, and the opponent's name and numbers stay exactly as they were.
   THE LINE BOX IS PINNED TO WHAT IT ALREADY WAS, and that is the whole trick. Everything here inherits
   `line-height: 1.5` from body, so the 19px name already owns a 28.5px line box and the row is exactly
   that tall. One more pixel of type with the default leading would make EVERY row of a score card that
   holds nineteen of them 1.5px taller, which pushes the last rows out of the card's background - the
   fault this file has had to fix more than once (see the note on .sc-total above). 19 x 1.5 = 28.5,
   17 x 1.5 = 25.5 and the total's own 20 x 1.275 = 25.5, so the three values below are the OLD line
   boxes: the type grows, the row does not. */
.sc-me .sc-team { font-size: 20px; line-height: 28.5px; }
.sc-me .sc-q { font-size: 18px; line-height: 25.5px; }
.sc-me .sc-total { font-size: 21px; line-height: 25.5px; }

.muted { color: var(--muted); }
.small { font-size: 13px; }
.good { color: var(--accent); }
.error { color: var(--danger); min-height: 20px; margin: 10px 0 0; font-size: 13px; }

label { display: block; margin: 14px 0; font-size: 13px; color: var(--muted); }
/* ==========================================================================================
 * ⚠ EVERY FORM CONTROL WEARS THE GAME'S OWN FONT. A BARE `<input>` DOES NOT INHERIT IT.
 *
 * Found by measuring rather than by looking: `input#gameName` in the customise panel resolved to
 * `font-family: Arial` while its own label, the rounds dropdown beside it and the club names under it were all
 * on the game's `system-ui` stack - so a season-name box was the one piece of Arial on the screen. The same was
 * true of every text and password field on the home page.
 *
 * `input` and `textarea` are the elements that do not inherit `font` by default (the browser gives them their
 * own, and it is a serif or Arial depending on the platform). `select` and `button` are set per-control
 * elsewhere; this rule is the floor for all of them, so a control added later is in the right typeface unless
 * something deliberately says otherwise.
 * ========================================================================================== */
input, select, textarea, button { font-family: inherit; }
/* ==========================================================================================
 * ⚠⚠ EVERY BOX YOU TYPE INTO IS ONE BOX. THIS WAS `input[type=text]`, AND THAT IS THE WHOLE BUG.
 *
 * Colin, on the front door: *"you should know that a password field should be the same shape as your name field,
 * I should have the same background colour of the fields - you got a coach's name field a totally different
 * background colour, you gotta get 'em all synchronized and unified."*
 *
 * He is right, and the cause is one selector. Every rule in this file that styled a text box said
 * `input[type=text]`, so a `<input type="password">` matched NONE of them and fell through to the browser's own
 * default: a WHITE, SQUARE box on a dark page. MEASURED on the live front door before this line changed:
 *
 *      loginName      left 236  width 128  radius 8px   background rgb(30,39,35)   <- styled
 *      loginPassword  left 293  width 128  radius 0px   background rgb(255,255,255) <- the browser's default
 *      regName        left 236  width 128  radius 8px   background rgb(30,39,35)
 *      regPassword    left 351  width 159  radius 0px   background rgb(255,255,255)
 *
 * Two different shapes, two different colours, and no shared left edge - all three of the things he listed. The
 * fields were not "unlined up" by a layout fault; they were two different KINDS of box, because only one kind had
 * ever been styled.
 *
 * SO THE SELECTOR NAMES THE THING, NOT ONE INSTANCE OF IT: every input that takes text, whatever it holds. `search`
 * and `url` and `tel` were equally unstyled and would have gone the same way the moment anything used one.
 * ========================================================================================== */
input[type=text], input[type=password], input[type=search], input[type=email],
input[type=tel], input[type=url], input[type=number] {
  display: block; width: 100%; margin-top: 6px; padding: 11px 12px;
  background: var(--panel-2); border: 1px solid var(--line); border-radius: 8px;
  color: var(--text); font-size: 15px;
}
input[type=text]:focus, input[type=password]:focus, input[type=search]:focus, input[type=email]:focus,
input[type=tel]:focus, input[type=url]:focus, input[type=number]:focus {
  outline: 2px solid var(--accent-2); outline-offset: 1px;
}
/* The placeholder is legible against the field it is in, not against the page. */
input::placeholder { color: var(--muted); opacity: 1; }

button {
  font: inherit; padding: 11px 16px; border-radius: 8px; cursor: pointer;
  background: var(--panel-2); color: var(--text);
  border: 1px solid var(--line);
  transition: background .12s, transform .06s;
}
button:hover { background: #26303d; }
button:active { transform: translateY(1px); }
button.primary { background: var(--accent); color: #04150c; border-color: transparent; font-weight: 600; }
button.primary:hover { background: #45e08f; }
button:disabled { opacity: .45; cursor: not-allowed; }
button.link { background: none; border: none; color: var(--muted); text-decoration: underline; padding: 6px 0; }

.divider { display: flex; align-items: center; gap: 10px; margin: 18px 0; color: var(--muted); font-size: 12px; }
.divider::before, .divider::after { content: ""; flex: 1; height: 1px; background: var(--line); }

/* Mode picker on the lobby */
/**
 * TWO COLUMNS THAT FLOW INDEPENDENTLY - WHICH IS NOT WHAT A GRID ROW DOES, AND THAT IS THE WHOLE POINT.
 *
 * Colin, looking at the page: *"so the just watch card didn't move up like you said it did, I controlled F5 but
 * the just watch card stays in the same spot."* He was right and my note was wrong: reordering the cards moved
 * JOIN up and dropped YOUR GAMES down, while Just watch never left the spot it was in - because in a normal
 * two-column grid every row is as tall as its TALLEST card. "Coach with friends" is the tall one (name, code,
 * Create, and the Customise section), so the short "Coach against the computer" beside it left a 215px hole
 * underneath itself, and Just watch sat below the hole rather than below the card.
 *
 * So each card is PLACED, and the two columns run down independently:
 *
 *     Coach against the computer  |  Coach with friends   (spans the next row as well)
 *     Just watch                  |
 *     Join a competition          |  Your games
 *
 * Which is exactly what he asked for twice over - *"let's move the just watch card up, and the join a
 * competition card can move up with it"* - and it reads in his order down each column (solo, then watch, then
 * join; friends, then your games). It also SHORTENS the page by about 170px, because the hole is gone.
 *
 * The DOM order is kept as the reading order (see the `wanted` list in renderLobby), so a screen reader and the
 * tab key follow solo -> friends -> watch -> join -> your games, the same sequence the eye takes.
 */
.modes {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
  margin-top: 14px;
  align-items: start;
}
/**
 * ⚠ FIVE CARDS BECAME THREE, AND THE ONE THAT STARTS ANYTHING TAKES THE WHOLE FIRST ROW.
 *
 * Colin, folding the three starting cards into one: *"so those three cards, we can get rid of the just watch card.
 * Gone, gone ... we're going to put the word NONE in the coach against the computer."* And then, of the two left:
 * *"we can put these things side by side so that everything's fitting on the one page instead of having to scroll
 * down."*
 *
 * So the grid is TWO rows now:
 *
 *     the coach card   - full width, because it is the only way to start anything and it grows when a club is
 *                        picked (the code and the second button appear)
 *     Join a season  |  Your games
 *
 * THE FIXED ROWS THE OLD FIVE-CARD LAYOUT NEEDED ARE GONE WITH THE CARDS. They were `grid-row` numbers chosen to
 * interleave a 2x2 of modes with a full-width create card; with one card above and two below, the flow order is
 * the order and nothing has to be pinned. Leaving them would have placed the join box in a row that no longer
 * exists and left a hole the height of a card.
 */
/**
 * ⚠ FOUR CARDS, AND THE ROWS ARE PINNED - SO ADDING ONE MEANS MOVING THE OTHERS.
 *
 * The Night Series card was added to the markup and to `renderLobby`'s order list, and it still came out in the
 * WRONG PLACE: `#joinBox` and `#myGamesBox` are pinned to `grid-row: 2`, so the new card landed beside them in row
 * two's first column - measured, `nightBox x45..267 top 655` sitting under a `joinBox` at `top 497` while
 * `myGamesBox` sat beside it. It was in the DOM order the client asked for and the GRID put it somewhere else,
 * which is what a pinned row does to a card nobody pinned.
 *
 * So every card is placed, including the new one:
 *
 *     row 1   the coach card          - the one thing that starts a league, full width
 *     row 2   the Night Series        - a different COMPETITION, full width so it reads as its own thing
 *     row 3   Join a season | Your games  - the two you come BACK for, still side by side
 */
#coachCard { grid-column: 1 / -1; grid-row: 1; }
/* The Night Series takes a row of its own. It is not a way to start a league, it is another competition, and
   full width is what says so - squeezed into a half column beside Join a season it reads as a third option in a
   list of ways to start the same thing. Hidden for anybody who may not start one (see renderLobby). */
#nightBox { grid-column: 1 / -1; grid-row: 2; }
#joinBox { grid-column: 1; grid-row: 3; }
#myGamesBox { grid-column: 2; grid-row: 3; }

/*
 * ===========================================================================
 * THE COACH CARD'S THREE ROWS - Colin's arrangement, 1 Nov.
 *
 *   Coach your team                                    <- the heading
 *   Select your team [None ▾]        Game code [↻][ ]  <- the picker, and the code the instant a club is picked
 *   And/or — customise the season                      <- optional, and shut until it is asked for
 *   [ the panel: Rounds, then the clubs with ticks, then the name, then save ]
 *
 * His words: *"we're gonna have a 'coach your team' heading and then 'select your team' with an arrow pointing to
 * a drop down list which defaults to none. Immediately on selecting a team a game code will pop up straight away
 * instantaneously to the right of it. Below the 'select your team' you're going to have an 'and/or' (customise
 * season) button."*
 *
 * ONE LINE FOR THE PICKER AND THE CODE, which is what "to the right of it" means - and it is why `align-items`
 * is `flex-end`: the picker is a labelled control, taller than the bare code box beside it, and lining their
 * BOTTOMS up is what keeps the two inputs on one visual line rather than the label level with the box.
 * ======================================================================== */
.coachrow {
  display: flex; flex-wrap: wrap; gap: 14px; align-items: flex-end;
  margin: 10px 0 0;
}
.coachpick { flex: 0 1 260px; min-width: 180px; }
/* The code takes the rest of the line, and it appears - it is never a placeholder - so it carries no reserved
   width of its own when it is hidden. */
#coachCode { flex: 0 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 4px; }
#coachCode .label { font-size: 12px; color: var(--muted); }
/* A GAME CODE IS SIX OR SEVEN DIGITS, AND THE BOX HAS TO SHOW ALL OF THEM.
 *
 * ⚠ 220px WAS TOO WIDE, 150px WAS TOO NARROW, AND BOTH WERE GUESSES. Colin caught the second one on a
 * screenshot: *"the game code doesn't fit inside the field, you can see from the screenshot that you've got
 * 32222 but there's numbers missing, because you gotta make it a bit wider so you can see the game code."*
 *
 * MEASURED in the browser rather than guessed at: a 7-digit code at the box's own type - 21px, weight 700, with
 * 3px of letter-spacing - needs **103px**, and the field only had **68px** of content box (150 minus the 48px
 * re-roll button and the 8px gap, minus padding). `scrollWidth 130 v clientWidth 92`: clipped, exactly as he
 * said. The arithmetic for the width it needs instead:
 *
 *      103px  the seven digits
 *    +  24px  padding (12 either side)
 *    +  48px  the re-roll square
 *    +   8px  the gap between them
 *    = 183px, so the column is 190px and there is 7px of slack for a wider code or a different font.
 */
#coachCode .codeedit { max-width: 190px; }
/* THE "AND/OR" ROW: the button, then the one-line read-out of what the current size means. */
.coachor { align-items: center; gap: 12px; margin-top: 12px; }
.coachor #sizeFacts { margin: 0; }
/* ==========================================================================================
 * PLAY AND CREATE, SIDE BY SIDE, EACH THE WIDTH OF ITS OWN WORDS.
 *
 * Colin: *"look how wide the play button is, there's no gap between play and create the season."* Both halves of
 * that were one cause: `.mode button { width: 100% }` drew each button across the whole card, so they could only
 * stack, each with 700-800px of nothing inside it. He wants *"Create a season"* and *"Watch a season"* not wide
 * either, so they are NOT stretched - the row is a flex row and each button takes its own width.
 *
 * It wraps, because the card goes narrow on a small window and "Create the season" beside "Play" is 230px of
 * button: below that they stack rather than overflow, which is the only case where stacking is right.
 * ========================================================================================== */
/* THE NIGHT SERIES CLUB LIST: one long list, like the "Select your team" list above it; left-out clubs are dimmed and say how to bring them back (Colin, 18 Oct 2026) */
#nightTicks.nightlist { display: flex; flex-direction: column; gap: 4px; max-height: 340px; overflow-y: auto; }
#nightTicks.nightlist .clubtick { display: flex; align-items: center; gap: 8px; padding: 6px 8px; border-radius: 6px; border: 1px solid transparent; }
#nightTicks.nightlist .clubtick.leftout { opacity: .6; border-color: var(--warn, #e0b030); border-style: dashed; }
#nightTicks.nightlist .clubtick .leftouttag { font-style: normal; font-size: 12px; color: var(--warn, #e0b030); margin-left: auto; }
#nightTicks.nightlist .clubtick .clubrename { margin-left: 8px; }
/* the Night Series page on its own: nothing but the Night Series */
#screen-lobby.nightfocus .modes > :not(#nightBox), #screen-lobby.nightfocus .lobbyintro, #screen-lobby.nightfocus #joinBox, #screen-lobby.nightfocus #myGamesBox { display: none !important; }
.nightnotice { margin: 8px 0; padding: 8px 12px; border-radius: 6px; font-size: 14px; line-height: 1.4; background: rgba(224, 176, 48, .12); border: 1px solid rgba(224, 176, 48, .5); }
.nightnotice.ready { background: rgba(53, 208, 127, .10); border-color: rgba(53, 208, 127, .5); }
.homeactions { display: flex; flex-direction: column; gap: 10px; margin: 10px 0 4px; }
.homeactions .homegroup { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.homeactions .homehead { width: 100%; margin: 0; font-size: 12px; opacity: .75; text-transform: uppercase; letter-spacing: .04em; }
.homeactions button { width: auto; }
.homeactions .homedanger { background: rgba(220, 70, 70, .18); border: 1px solid rgba(220, 70, 70, .6); color: inherit; }
.homeactions .homebig { display: flex; flex-direction: column; align-items: flex-start; gap: 2px; width: 100%; max-width: 440px; padding: 10px 14px; text-align: left; font-size: 16px; }
.homeactions .homebig strong { font-size: 17px; }
.homeactions .homebighint { font-size: 13px; font-weight: 400; opacity: .82; line-height: 1.3; margin: 0; }
.homeactions .homejoin { display: flex; gap: 8px; width: 100%; max-width: 440px; }
.homeactions .homejoin input { flex: 1; min-width: 0; font-size: 18px; padding: 10px 12px; letter-spacing: .08em; }
.homeactions .homejoin .homejoinbtn { width: auto; align-items: center; justify-content: center; padding: 10px 22px; }
.homeactions .homedanger { width: 100%; max-width: 440px; text-align: left; }
.homeactions .homenote { font-size: 12px; opacity: .8; }
.cardactions { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; margin-top: 4px; }
/* THE PANEL. Shut by default and opened by the button; it holds a lot, so it scrolls rather than growing the
   lobby past the window - the card is full width and the page must still fit one screen. */
#sizeBox { margin-top: 12px; }
#sizeBox .clubticks { max-height: 260px; overflow: auto; }
/* The Night Series' own list, capped lower than the customise panel's. Both hold the same nineteen clubs, but this
   panel is inside the lobby's narrow column rather than a full-width form, so nineteen rows at two to a line is a
   tall block - and the whole point of this panel is that a reader can see the ticks, the Rounds 4 and the Start
   button without scrolling the card. 200px shows about six rows and scrolls for the rest. */
#nightSetup .clubticks { max-height: 200px; overflow: auto; }
/* THE SEASON'S NAME, LAST BEFORE THE SAVE, and it is compulsory - so it is set like a field that matters
   rather than as another optional line.
 *
 * ⚠ AND IT IS A NAME, NOT A SENTENCE. `max-width: 340px` was still 340px wide for a word, and Colin had it in
 * his list of things that were too wide. 260px is comfortably more than the longest season name anyone types and
 * less than a third of the card.
 *
 * `font: inherit` IS A FIX, NOT TIDYING. This box was resolving to `font-family: Arial` while everything around
 * it - the labels, the rounds dropdown, the club names - was on the game's own `system-ui` stack, because a bare
 * `<input>` does not inherit the font the way every other element does. Measured in the browser: `input#gameName
 * ... font: Arial`. It is the only control in the panel that was a different typeface from its own label. */
.sizename { display: block; margin-top: 12px; }
.sizename input { max-width: 260px; font: inherit; font-size: 15px; }
/* ==========================================================================================
   ⚠⚠ A CLUB AND ITS RENAME BUTTON ARE ONE THING, AND THEY HAVE TO LOOK LIKE ONE THING.
   Colin, reading the Night Series panel: *"the club names with the ticks and then the rename options - they look
   like they're closer to the wrong team in the middle section. Let's move the renames across closer to the team
   that they will be renaming ... and let's put a little background around each of the teams together with the
   rename to bubble them together, so that everyone knows that that rename belongs to that club."*

   THE FAULT WAS `margin-left: auto` ON THE BUTTON, which pushed "Rename" to the far right of its grid column. With
   two columns and a 12px gutter, a club in the LEFT column had its button sitting right beside the club in the
   RIGHT column - so "NORTH MELBOURNE   Rename" read as though the Rename belonged to whatever name came next. He
   is describing a real ambiguity, and it gets worse the wider the column is.

   SO THE ROW IS A CHIP NOW, and that is three decisions:
     * THE BUTTON SITS BESIDE THE NAME, not at the far edge - `margin-left: auto` is gone. The gap after the name
       is a fixed 10px, so the button's distance from its club never depends on how long the name is or how wide
       the column happens to be;
     * A SUBTLE BACKGROUND AND OUTLINE WRAP THE PAIR, which is what "bubble them together" asks for. The eye reads
       a bordered chip as one object, so the association does not depend on proximity alone - and proximity alone
       was exactly what failed;
     * PADDING GIVES IT ROOM TO READ AS A CHIP rather than as text with a box drawn round it.

   ⚠ THE FORMER 10.5rem COLUMN FLOOR WAS MEASURED FOR THE OLD SHAPE and is left alone deliberately: the chip adds
   padding rather than width, and the name is still the widest thing in it, so a narrower floor is not needed and
   a wider one would drop the grid to fewer clubs per row for no gain.

   ⚠ AND THIS IS THE ONLY `.clubtick` RULE. There was a second one further down carrying `padding: 3px 0`, which
   quietly undid the chip's padding - the same declaration split across two blocks, which is how a change comes out
   half-applied and looks like the browser ignoring you. The label is the hit target, so the whole chip is
   clickable, and `margin: 0` undoes the block-label spacing every other label in the file carries. */
.clubtick {
  display: flex;
  align-items: center;
  gap: 7px;
  margin: 0;
  font-size: 13px;
  color: var(--text);
  cursor: pointer;
  /* A CHIP: the club and its Rename, bound together by a box. */
  padding: 3px 8px;
  border: 1px solid rgba(255, 255, 255, .10);
  border-radius: 6px;
  background: rgba(255, 255, 255, .04);
  /**
   * ⚠⚠ AND THE CHIP FITS ITS CONTENTS, SO THE BUBBLE ACTUALLY CLOSES ROUND THE PAIR.
   *
   * `justify-self: start` is what makes it hug: a grid item stretches to its column by default, so the box ran the
   * full 222px while the club and its button occupied only the left 147px - a border around empty space on the
   * right, which is not "a bubble around the team and its rename", it is a box with a name in the corner. Measured
   * before this line: the chip extended 75px past the button.
   *
   * ⚠ THE TRADE IS REAL AND IT IS HIS CALL: content-sized chips mean the names down the list are NOT in a straight
   * column - "ADELAIDE" is narrower than "COLLINGWOOD", so each chip ends where its own name does. What that buys
   * is the thing he actually asked for - the box closes round the club and its button, so the button can never be
   * read as belonging to anything else. If he would rather have the straight edge, this line is the one to remove
   * and the gap below still holds the association on its own.
   */
  justify-self: start;
}
/* AND THE BUTTON NEVER DRIFTS AWAY FROM ITS CLUB. `margin-left: auto` lived here and is what put "Rename" beside
   the NEXT club along; the fixed gap is the whole fix. */
.clubtick .clubrename { margin-left: 10px; }
/*
 * "YOUR GAMES" AND "PLAY WITH FRIENDS" SHARE THE SECOND ROW, EACH HALF THE WIDTH.
 *
 * The create card used to take the whole column, and the reason was measured: in a 362px half-column the
 * game code and the line under it wrapped to three lines. Colin has overruled that, and he is the one
 * looking at it - *"we're going to get the card that says play with friends and we're going to squash it up
 * to be half of the width that it is because it's out of control too wide, and then we're going to get the
 * your games up the top card which is much too wide and we're going to make that in half, and then we can
 * put these things side by side so that everything's fitting on the one page instead of having to scroll
 * down."*
 *
 * SO THE GRID IS THREE ROWS OF TWO, and the two one-line cards - "Competition size" and "Join a
 * competition" - are the first of them, rather than a full-width strip above and a full-width block
 * hanging off the bottom. `renderLobby` does the ordering (a member only; a guest keeps the join box full
 * width at the top, which is the one reader who needs it there).
 *
 * `align-self: start` IS DELIBERATE: a grid cell stretches to its row by default, and "Your games" holds one
 * line per competition - stretching it to the height of the create card beside it would draw a half-empty
 * box the height of a form.
 */
.modes > #myGamesBox,
.modes > #sizeBox,
.modes > #joinBox { align-self: start; min-width: 0; }
.modes > .mode { min-width: 0; }
/*
 * EVERY CELL OF THIS GRID IS THE SAME CARD, WITH THE SAME TITLE.
 *
 * Colin, on the first attempt: *"look at how horrible that's set up. That is really horrible and ugly and
 * hard to work out what's going on. Just a dog's breakfast."* He was right and the fault was consistency,
 * not spacing: "Competition size" was a full-width strip, "Join a competition" was bare text on the page,
 * "Your games" was a floating uppercase label above a bolted-on box - and beside them sat two proper mode
 * cards and a create card. Six blocks, four different treatments.
 *
 * So the frame is stated ONCE, for every child of the grid, and so is the title: the mode cards' own accent
 * heading rather than the muted uppercase section label that belongs between blocks of prose, not on top of
 * a card. Every card in this grid is now framed from this one rule - the last two that framed themselves
 * (`.sizebox` as a card of its own, and the guest's promoted join box) were both folded into the cards they
 * belong to, so there is nothing left to make an exception of.
 */
.modes > * {
  align-self: start;
  /**
   * ⚠⚠ `width: 100%` AND `justify-self: stretch` ARE LOAD-BEARING. WITHOUT THEM THE CARD COLLAPSED TO 119px.
   *
   * Colin's screenshot: the coach card's text wrapping ONE WORD PER LINE - "ADELAIDE is / yours. The / computer
   * runs / every club ..." - because the card was **119px wide inside a 404px grid**, i.e. it had shrunk to its own
   * content instead of filling its column.
   *
   * A grid item stretches to its track by default, so something had taken that away, and the fix must not depend on
   * knowing which: naming the width makes the card fill its column WHATEVER the cascade is doing underneath. That is
   * the same principle as every other width repair in this file - state the relationship rather than rely on a
   * default that something else can switch off.
   *
   * ⚠ AND IT WAS MEASURED BOTH WAYS, because "the column rule is the fault" was the wrong diagnosis twice:
   *   * forcing `grid-template-columns: minmax(0, 1fr) !important` on the live element did NOT widen the card
   *     (119px before and after) - so the column layout was never the constraint;
   *   * the card's own computed style was `width: 118.609px, max-width: none, justify-self: auto` - it was sizing
   *     to the first track's content, not to the grid.
   */
  width: 100%;
  justify-self: stretch;
  min-width: 0;
  margin: 0;
  padding: 12px 15px;
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
.modes > * > .sectionhead,
.modes > * > .sizerow > .sectionhead,
.modes > * > h3 {
  margin: 0 0 6px;
  font-size: 15px;
  font-weight: 600;
  text-transform: none;
  letter-spacing: 0;
  color: var(--accent);
}
/* The size row keeps its heading INLINE with the facts and the Change button, so it carries no gap of its
   own - and the box itself loses the padding and margin it was given as a full-width strip. */
.modes > * > .sizerow > .sectionhead,
.mode .sizebox > .sizerow > .sectionhead { margin: 0; }
/**
 * "CUSTOMISE THE COMPETITION" IS A SECTION OF THE CARD THAT CREATES ONE, NOT A CARD OF ITS OWN.
 *
 * Colin folded the two together: *"I put the competition size card in with the Play with Friends as all one
 * card ... so I would have it as a modify or customise competition - modified size, rounds."* So inside the
 * card it drops its own frame and its own padding, and takes a divider instead - the reader should see ONE
 * card with a setting in it, which is the whole point of the move. Its sub-heading keeps the accent colour at
 * the card's own weight so it still reads as a heading rather than as body text.
 */
.mode .sizebox {
  background: none;
  border: 0;
  border-top: 1px solid var(--line);
  border-radius: 0;
  margin-top: 10px;
  padding: 8px 0 0;
}
/**
 * THE SUB-HEADING AND THE CHANGE BUTTON SHARE A LINE, THE FACTS SIT UNDER THEM - and that is a fix for a
 * MEASURED fault, not a preference.
 *
 * As one wrapped FLEX row inside a half-width card - heading, facts sentence and the button - the three came to
 * more than the 411px the card gives, so the row folded onto three lines: 120px of card that had just been
 * given the extra height, on a lobby whose whole point is that it fits one screen. Measured with
 * tools/_check-lobby-fit.mjs at Colin's own window: the page came back 999px in 951px.
 *
 * A two-column GRID says it once: the heading and the button on the first line, the facts across the full width
 * on the second. The DOM order is unchanged, so no markup and no copy has to move for this - the facts are
 * still the whole sentence, just not fighting the button for the same line.
 */
.mode .sizebox > .sizerow {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: 2px 10px;
}
.mode .sizebox > .sizerow > .sectionhead {
  grid-column: 1;
  grid-row: 1;
  margin: 0;
  font-size: 15px;
  font-weight: 600;
  color: var(--accent);
}
.mode .sizebox > .sizerow > #sizeFacts { grid-column: 1 / -1; grid-row: 2; margin: 0; }
.mode .sizebox > .sizerow > #sizeChange { grid-column: 2; grid-row: 1; margin-left: 0; }
/**
 * (A note for whoever reads the height next: the GAME CODE's hint was also suspected of costing a line, and a
 * rule was written to sit it beside the code box. Measured afterwards, it changed nothing - `.codeedit` is a
 * flex row and takes the full width, so the hint is on its own line either way - so the rule is not here. The
 * height came from the size row folding onto three lines, above.)
 */
/* Inside a card, a list row is a row - not a second card with its own frame. */
.modes > #myGamesBox .gamelist li {
  background: none;
  border: 0;
  border-radius: 0;
  padding: 8px 0 0;
  margin: 0;
}
/* ==========================================================================================
   ⚠⚠ THE ROW'S CONTROLS IN ONE COLUMN - WHICH IS WHAT PUTS DELETE *UNDER* OPEN.
   Colin: *"underneath where it's got open ... a little button half the depth ... just under the open."*

   The row is `display: flex; justify-content: space-between`, so a button appended as a SIBLING of Open comes out
   BESIDE it, on the far right - which is where the first attempt put it. Measured then: the delete button and the
   Open button above it OVERLAPPED by 28px and the delete button started 37px above the bottom of the one it was
   supposed to sit beneath. A flex row lays its children out ACROSS it, and "under" is the other axis; the pair has
   to be a column before anything can be under anything. So the row has two children again - the text and this - and
   `space-between` still puts them left and right.

   IT IS SIZED TO ITS OWN CONTENTS, per the width rule in §11: the Open button is its own width and the delete
   button is its own, and neither is stretched to the column. */
.rowctl { display: flex; flex-direction: column; align-items: flex-end; gap: 3px; flex: 0 0 auto; }
/* AND THE LITTLE DELETE BUTTON ITSELF, which is four decisions read off that one sentence:
     * SHALLOW - the app's buttons carry `padding: 11px 16px`; this one carries 4px, which comes out at about half
       a primary button's height beside it (measured 25px against 47px) rather than a second full-size control;
     * PALE RED - a tint of the app's own danger colour rather than the solid fill "Cancel this competition" wears.
       That button throws away a season somebody is playing; this one clears a line off a list, and the two should
       not look equally alarming;
     * NOT UNDER View - the client only draws it for a competition that is not complete (see renderLobby), so a
       finished season has no delete control to press at all. "Locked in" is exactly right: that is the record;
     * AND IT NAMES ITS OWN SECOND PRESS, so nobody has to read a hint to know the first one did not do it. */
.delwrap { display: flex; flex-direction: column; align-items: flex-end; gap: 2px; flex: 0 0 auto; }
.delbtn {
  padding: 4px 10px;
  font-size: 12px;
  line-height: 1.25;
  color: #ff9a9a;
  background: rgba(220, 80, 80, .14);
  border: 1px solid rgba(220, 80, 80, .45);
  border-radius: 6px;
}
.delbtn:hover:not(:disabled) { background: rgba(220, 80, 80, .26); }
.delbtn:disabled { opacity: .6; }
/* The "press again" line, in the row rather than in a bubble: there is one of them and it is one short phrase. */
.delnope { color: var(--muted); font-size: 10px; white-space: nowrap; }
/* ONE COLUMN ON A NARROW SCREEN, and the placement above is released with it - a single column cannot have a
   card spanning two rows of it, and the grid's own order is the order he wants anyway. */
@media (max-width: 700px) {
  .modes { grid-template-columns: 1fr; }
  #soloCard, #watchCard, #joinBox, #friendsCard, #myGamesBox {
    grid-column: 1;
    grid-row: auto;
  }
}
.mode {
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 13px 16px;
}
.mode h3 { margin: 0 0 4px; font-size: 15px; color: var(--accent); }
.mode label { margin-top: 10px; }
/* ==========================================================================================
 * ⚠ A BUTTON IS AS WIDE AS ITS WORD. THIS WAS `width: 100%`, AND IT MADE A 62px WORD 868px.
 *
 * Colin, looking at the lobby he had just been given: *"if a button doesn't need to be as wide as a fucking
 * house don't make it that wide. All of your buttons are too wide on everything except for the occasional one
 * like view in your games ... play against the computer too wide ... create a season too wide, watch a season
 * too wide."* Measured on his own screen before this line changed:
 *
 *      #startSolo      "Watch a season"      876px drawn, 139px of content   -> 737px of nothing
 *      #startSolo      "Play"                868px drawn,  62px of content   -> 806px of nothing
 *      #letOthersJoin  "Create the season"   868px drawn, 155px of content   -> 713px of nothing
 *
 * Two buttons at 100% are also what put Play above Create instead of beside it, which is the other half of what
 * he said about that row - *"there's no gap between play and create the season"*. A button sized to its word in
 * a flex row has a gap for free.
 *
 * SO THE DEFAULT IS THE WORD, and `button.wide` is how a control asks for the whole row instead. It is used for
 * the one place that genuinely wants it: the long single action at the foot of a form, which reads as the point
 * of the card rather than as one of a row of choices. (`button.wide` is defined near the top of the file, at the
 * auth row, and is not repeated here.)
 *
 * THIS IS A RULE FOR THE WHOLE FILE, not a patch on this card: anything added later inherits "as wide as your
 * word" and has to opt IN to being wide. That is the way round he asked for it.
 * ========================================================================================== */
.mode button { width: auto; max-width: 100%; }

/* AN ERROR LINE WITH NOTHING IN IT TAKES NO ROOM.
 *
 * Every one of these cards carries a `<p class="error">` that is filled only when something goes wrong -
 * `#lobbyError`, `#soloError`, `#watchError`, `#joinError` - and an empty one is still a LINE of the page.
 * Four of them is four blank lines, and this card is trying to fit in one window. `:empty` matches the
 * paragraph exactly while it holds nothing, so the line appears the moment there is something to say and
 * costs nothing until then. */
.mode p.error:empty, #joinBox p.error:empty, .sizebox p.error:empty, #myGamesBox p.error:empty { display: none; }

/* ---- THE SIZE OF THE COMPETITION ------------------------------------------
   Colin: "we can have less teams if we want in the competition so we can and we can
   select the amount of rounds that we want to have as well ... that can only be done
   by the host". One block on the lobby - before a competition exists - and one in the
   waiting room, for one that does. Same shape in both: the clubs to tick, the rounds
   that can be dealt, and the facts in small muted text underneath.

   THE TICKS ARE A GRID, not a column: nineteen clubs listed one to a line pushed the
   three ways of starting a season most of a screen down the page. `auto-fit` at a
   9rem minimum gives three or four columns on a wide card and one on a phone, with no
   breakpoint to keep in step by hand. */
.sizebox {
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 4px 14px 10px;
  margin-top: 16px;
}
/* ONE COMPACT LINE UNTIL SOMEBODY ASKS FOR MORE. Collapsed, the block is the heading,
   the facts and a Change button on one row - nineteen ticks above every way of starting a
   season was 357px tall and what Colin called "so wide and it's dominating the screen". */
.sizerow { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.sizerow .sectionhead { margin: 0; }
.sizerow #sizeFacts { margin: 0; }
/* The control goes to the far end: the heading and the facts belong together as a
   sentence, and Change is the thing you press rather than read.
   IT IS A BUTTON, NOT A LINK. Colin, told the option existed but could not find it: a
   plain underlined word next to the facts reads as part of the sentence, not as something
   to press. Same secondary-button frame as the rest of the game (`backbtn`), sized down so
   the row stays one line. */
.sizerow #sizeChange, .sizerow #lobbySizeChange { margin-left: auto; }
.sizebtn { padding: .2rem .6rem; font-size: .8125rem; }
#sizeControls { padding-top: 4px; }
/* The heading is the first thing in the box, so it does not need the gap the section
   heads carry when they follow a block of text. */
.sizebox .sectionhead { margin-top: 14px; }
/* The heading says what the ticks are, and the button beside it ticks them all; neither wants half the row.
   `flex-start` rather than `space-between` for the same reason the buttons changed: this button was being pushed
   out to 757px by a heading four words long. */
.sizehead { display: flex; align-items: baseline; justify-content: flex-start; gap: 10px; }
.sizehead .sectionhead { margin-bottom: 0; }
.clubticks {
  display: grid;
  /* ⚠ 10.5rem, NOT 9rem, BECAUSE "NORTH MELBOURNE" IS THE LONGEST CLUB IN THE GAME. At 9rem the column was
     144px, which is narrower than the name plus its tick, its Rename button and the gaps - so NORTH MELBOURNE
     wrapped onto two lines and made its whole row taller than the four beside it. The column is measured from the
     content rather than guessed: the tick (13px + 7px gap) + the name at 13px + the gap + "Rename" (56px) + the
     12px column gap. Nineteen clubs still lay out five to a row in the card's 868px, exactly as they did before. */
  grid-template-columns: repeat(auto-fit, minmax(10.5rem, 1fr));
  gap: 0 12px;
  margin: 8px 0 2px;
}
/* ⚠ THE `.clubtick` RULE THAT USED TO BE HERE IS GONE - it is one rule now, above the `.clubticks` grid, so the
   chip's padding cannot be undone by a later declaration. See the note there. */
.clubtick input { width: auto; margin: 0; accent-color: var(--accent); }
.sizebox label { margin: 10px 0; }
/* A size the generator will not deal, or a finals field that is not built yet. Read it
   rather than a failure: the amber the game already uses for "not yet". */
.sizewarn { color: var(--warn); }

.code { color: var(--accent); letter-spacing: 3px; font-size: 17px; }

/* The join code, made big because it gets read aloud over the phone */
.codebox {
  margin: 18px 0;
  padding: 18px 20px;
  background: var(--panel-2);
  border: 1px solid var(--accent);
  border-radius: var(--radius);
  text-align: center;
}
.codebox .label {
  display: block;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 1px;
  color: var(--muted);
  margin-bottom: 6px;
}
.bigcode {
  display: block;
  font-size: 42px;
  font-weight: 800;
  letter-spacing: 1px;
  color: var(--accent);
  text-transform: lowercase;
  line-height: 1.1;
  word-break: break-all;
}
.codebox .copycode {
  margin-top: 10px;
  padding: 6px 16px;
  border: 1px solid var(--accent);
  border-radius: var(--radius);
  color: var(--accent);
  cursor: pointer;
}
.codebox .sharehint { margin: 10px auto 0; max-width: 46ch; }
.codebox .sharehint:empty { display: none; }

.coach-list { list-style: none; padding: 0; margin: 14px 0; }
.coach-list li {
  padding: 10px 12px; background: var(--panel-2); border: 1px solid var(--line);
  border-radius: 8px; margin-bottom: 8px; display: flex; justify-content: space-between;
}
.tag { font-size: 11px; color: var(--accent); border: 1px solid var(--accent); padding: 1px 7px; border-radius: 99px; }

/* THE ROUND'S NAME, CENTRED, and the quarter pill beside it. The play button is
   NOT here - it is up on the top bar with the speeds, so this card is nothing but
   the round: the name and the games. Colin: "the round three of 24 in the center
   of that card", and then, of the play button, "put the play round button up on
   the same bar". */
.roundbar {
  display: flex; justify-content: center; align-items: center;
  gap: .75rem; margin-bottom: 12px;
  /* The title bar must not SHRINK. The card is a column flex container, and when
     the results hold pushes a tall list into it the bar was the thing that gave
     way - measured collapsing to 0px, so "Round 1 of 24" and the pill vanished
     off the top of the card. It is the round's identity; it keeps its height and
     the scroller below it keeps its own. */
  flex: 0 0 auto;
}
.roundbar h2 { margin: 0; text-align: center; }
.pill { font-size: 12px; text-transform: uppercase; letter-spacing: .8px; color: var(--muted);
  border: 1px solid var(--line); padding: 3px 10px; border-radius: 99px; }
/* NEXT ROUND sits on the round's own title bar, sized to the heading beside it:
   the nine games need every pixel the bar does not take. */
/* Bigger and bolder (Colin, 5 Oct 2026: "it's not a really jump out at me type of button, it's quite small"). */
#roundNext { padding: 6px 20px; font-size: 15px; font-weight: 700; line-height: 1.3; }
/* THE DUPLICATE ON THE COLFOOTY BAR. Colin: "just put it up pretty much in the middle
   under where it says Colfooty bar, there's a big space there for it." The bar is a
   flex row - brand, controls, this, account cluster - so `margin-inline: auto` takes
   the free space on BOTH sides and parks it in the middle of that gap, whatever the
   window width and whatever the layout has done to the cards. Sized to match the
   card's copy so the two read as the same button. */
#roundNextTop {
  padding: 6px 20px;
  font-size: 15px;
  font-weight: 700;
  line-height: 1.3;
  margin-inline: auto;
}
/* While a finished round is being held, the pill says what it did for you, so it
   reads as a result rather than a status. */
#phasePill.final { color: var(--accent); border-color: var(--accent); }

/* HOW FAST, AND PAUSE - ON THE TOP BAR, between the wordmark and the account
   cluster. Colin: "the slow medium fast button can we put that right up the top
   on the colfooty bar up the top". The client shows this group only in a game. */
.topcontrols { display: flex; align-items: center; gap: 10px; }
/* The BAR's size, not a panel's. These share the bar with the 34px Back button,
   and a full-size primary button made the bar 13px taller - which is 13px the
   cards below lose, and enough to raise a scrollbar on a screen that is meant to
   fit in one window. Measured: bar 59px -> 72px, page 3px over. */
.topcontrols .primary {
  height: 2.125rem;
  white-space: nowrap;
  padding: 0 .875rem;
  font-size: .8125rem;
}
.speeds { display: flex; align-items: center; gap: 0; }
.speedbtn {
  font: inherit;
  font-size: 12px;
  font-weight: 600;
  padding: 0 10px;
  height: 34px;
  color: var(--muted);
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 0;
  cursor: pointer;
}
.speedbtn + .speedbtn { border-left: 0; }
.speedbtn:first-child { border-radius: 8px 0 0 8px; }
.speedbtn:last-child { border-radius: 0 8px 8px 0; }
.speedbtn:hover { background: #26332d; color: var(--text); }
.speedbtn.chosen { background: var(--accent); border-color: var(--accent); color: #04150c; }

/* ---- the ground clock ----------------------------------------------------
   Colin's photograph: black-rimmed enamel face, sixty spots on the dial, and the
   red band from 25 to 35 that means time on. One sits beside every game's total,
   because every game runs its own quarter and its own siren. */
.clock-face { display: block; }
.clock-rim { fill: #14181c; }
.clock-white { fill: #f4f1e8; }
.clock-on { fill: #b3141c; }
.clock-num {
  font-size: 11px; font-weight: 700; text-anchor: middle;
  fill: #1b2126; font-family: inherit;
}
.clock-num.on { fill: #ffffff; }
.clock-num .lit { fill: #ffffff; }
.clock-num .dark { fill: #1b2126; }
/* The hand carries a light halo so it reads on the white AND on the red - a red
   hand would have been red on red through the whole of time on. */
.clock-hand-halo { stroke: #ffffff; stroke-width: 7.5; stroke-linecap: round; }
.clock-hand { stroke: #14181c; stroke-width: 4; stroke-linecap: round; }
.clock-hub { fill: #14181c; }
/* Time on: the hub goes white. The hand is already sitting in the red, which is
   the real signal - this is just the same thing said once more. */
.clock-warn .clock-hub { fill: #ffffff; }
/* Siren: this game's quarter is over and its face says so. */
.clock-siren .clock-white { animation: siren 620ms steps(1, end) infinite; }
@keyframes siren { 50% { fill: #b3141c; } }

/* The points box. Kept to one line of numbers: the same figures the original's
   blue panel showed, without the padding that was eating the ladder's height.
   The ladder has to fit whole, so every pixel here is one the ladder does not
   get. */
.poolbox {
  display: flex;
  align-items: flex-end;
  /* POINTS ON THE LEFT, YOUR SELECTION ON THE RIGHT, and nothing in between. The four statistics
     that used to take the middle ("Average used / game", "Average left / game", "Rounds left",
     "Cap this round") were removed on Colin's instruction - "we've got too much information in
     there ... we're going to get rid of that so that we can squash it up a bit" - so this row is
     now two figures instead of six, and the room goes to the number board underneath. */
  justify-content: space-between;
  flex-wrap: nowrap;
  gap: 2px 10px;
  background: var(--panel-2); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 4px 9px; margin-bottom: 5px;
}
.pool-main, .pool-selection { display: flex; align-items: baseline; gap: 6px; }
.pool-main .label, .pool-selection .label {
  font-size: 11px; color: var(--muted);
  text-transform: uppercase; letter-spacing: .8px;
}
.pool-main .value, .pool-selection .value {
  font-size: 22px; font-weight: 700; color: var(--accent); line-height: 1;
}
/* THE DELETE BESIDE YOUR SELECTION, immediately left of the number - Colin: "a little delete
   button in the points box right next to your selection just on the left hand side of your
   selection." It stays on the card whether or not it can be used, so the row never reflows and
   the number never moves under the mouse; when there is nothing waiting it is disabled rather
   than hidden. Sized off `.qchip`'s ladder so it reads as part of the points box rather than as
   a stray control, and aligned on the BASELINE of the 22px figure it sits beside. */
.pool-selection .selectiondel {
  align-self: center;
  font: inherit; font-size: 11px; line-height: 1;
  min-width: 1.35rem; padding: 2px 5px;
  color: var(--text); background: #3f5148;
  border: 1px solid #5b7267; border-radius: 6px;
  cursor: pointer;
}
.pool-selection .selectiondel:hover:not(:disabled) { background: #4a5f55; border-color: #6b8377; }
.pool-selection .selectiondel:active:not(:disabled) { transform: translateY(1px); }
.pool-selection .selectiondel:focus-visible { outline: 2px solid var(--accent-2); outline-offset: 2px; }
/* Nothing waiting to be taken back: dimmed, and it cannot be pressed. */
.pool-selection .selectiondel:disabled { opacity: .35; cursor: default; }
/* THE QUESTION IS SMALL TYPE. Colin: "if we can shrink down the text size of how many points this
   quarter". It is a label for the board below it, not a heading. */
#nominateBox > label { font-size: 12px; color: var(--muted); }
#nominateBox > label .hint { font-size: 11px; margin-top: 1px; }

.hint { display: block; color: var(--muted); font-size: 12px; margin-top: 3px; }

/* THE FINISHED ROUND IS READ BEFORE ANYTHING ELSE. It says so in the points box, in
   the same green as the rest of the "you" prompts, because the box is inert until
   Next round is pressed. */
#roundHeldNote {
  color: var(--accent);
  font-weight: 600;
  margin: 2px 0 4px;
}

/* THE CLUB QUESTION, in the waiting room after a reset. Colin: "it needs to produce
   a message saying your previous team was blah, do you still wanna coach that team,
   and you say yes." It sits above the club picker, tinted like the other "you" rows
   so it reads as a question rather than as more instructions. */
.keepbox {
  background: rgba(53, 208, 127, .09);
  border: 1px solid rgba(53, 208, 127, .45);
  border-radius: var(--radius);
  padding: .6rem .75rem;
  margin: .5rem 0;
}
.keepbox p { margin: 0 0 .5rem; font-weight: 600; }
.keepbox .authrow { margin: 0; }

/* ---- THE QUEUE -----------------------------------------------------------
   The numbers picked ahead, in the order they will be spent. Compact: the points
   box shares its height with the ladder. */
#bufferBox {
  border-top: 1px solid var(--line);
  margin-top: 6px;
  padding-top: 5px;
}
.bufferhead { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.bufferhead .label {
  font-size: 11px; color: var(--muted);
  text-transform: uppercase; letter-spacing: .8px; white-space: nowrap;
}
.bufferhead .hint { display: inline; margin: 0; font-size: 11px; text-align: right; }
/* ONE ROW OF CHIPS, AND IT STAYS ONE ROW.
 *
 * Colin: *"we want a little row where the forwarding points already exists so that when we click 3
 * or 4 times a new row doesn't get created so that the numbers 2 through to 12 stay in the same
 * place so a mouse can go click click click."* The queue used to be a COLUMN of game bars
 * (`.qgame`, one per game the queue covered), so every few presses added a row, the block grew
 * downwards, and the numbered board above it slid with it - which is the same fault the buttons
 * themselves were moved for (see the note in index.html).
 *
 * Now the game bars sit SIDE BY SIDE in one row of fixed height. Nothing about the queue can change
 * the card's height again: a long queue runs off to the right (and scrolls sideways if it has to)
 * rather than downwards, so the numbers stay exactly where the mouse left them. */
.queuechips {
  display: flex; flex-direction: row; flex-wrap: nowrap; align-items: center; gap: 6px;
  margin: 4px 0 2px; min-height: 1.6rem;
  overflow-x: auto; overflow-y: hidden;
  scrollbar-width: thin;
}
/* ONE BAR PER GAME, laid out in that row. */
.qgame {
  display: flex; flex-wrap: nowrap; align-items: center; gap: 4px;
  border-left: 2px solid var(--line);
  padding-left: 6px;
  flex: 0 0 auto;
}
/* The chips that have no quarter left this season - the queue ran past the end of
   the fixture. They are still shown and still deletable; the muted rule says they
   are not going to be played, not that they are broken. */
.qgame.stranded { border-left-color: var(--muted); }
.qgamelabel {
  flex: 0 0 auto;
  font-size: 10px; text-transform: uppercase; letter-spacing: .6px;
  color: var(--muted); white-space: nowrap;
}
.qchip {
  min-width: 1.6rem; text-align: center;
  font-size: 12px; font-weight: 650; font-variant-numeric: tabular-nums;
  padding: 1px 5px;
  font-family: inherit;
  /* Same fix as .numbtn - a waiting number was --panel-2 on a --panel card, which is
     1.12:1, so the chips vanished into the points box when the window was small. */
  color: var(--text); background: #3f5148;
  border: 1px solid #5b7267; border-radius: 6px;
  cursor: pointer;
}
.qchip:hover { background: #4a5f55; border-color: #6b8377; }
/* The one that goes on the quarter in front of you. */
.qchip.next { color: #072015; background: var(--accent); border-color: var(--accent); }
/* HIGHLIGHTED. Colin: "I want to be able to highlight a number in the queue." A
   blue ring rather than a fill, so it stays legible on top of the green "next" chip
   - the chip that is both next in line and selected is the common case, because the
   first chip is the one people reach for. */
.qchip.picked {
  outline: 2px solid var(--accent-2); outline-offset: 1px;
  border-color: var(--accent-2);
}
.qchip:focus-visible { outline: 2px solid var(--accent-2); outline-offset: 2px; }
/* Three controls in the points box's tightest row: let them share it. */
#bufferActions { flex-wrap: wrap; justify-content: flex-start; gap: 6px; }
#bufferActions .backbtn { height: 1.75rem; padding: 0 .5rem; font-size: 11px; }
#bufferActions .backbtn:disabled { opacity: .45; cursor: default; }
#bufferActions .backbtn:disabled:hover { background: var(--panel-2); border-color: var(--line); }
#bufferNote { margin-top: 3px; }

/* ONE LINE EACH, AND THE FULL SENTENCE ON HOVER.
   Every one of these is a sentence about the state of the game - "Quarter 1 is running…", "Round 1
   is finished. Press Next round…", a bye, a sacked club, what the queue refused. They used to wrap
   onto two or three rows each, and between them they were most of the text Colin asked to be rid of:
   "we have to clean up a problem with the points box ... that's why these buttons are struggling to
   get in the view". Clipped to one row with the whole sentence on the element's own `title` (set in
   app.js's setText), so nothing is hidden - it is one hover away instead of three lines of card. */
#quarterRunning,
#roundHeldNote,
#byeNote,
#sackedNote,
#riskLine,
#bufferNote {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* While a quarter runs: the pool and its figures stay where they are. */
#quarterRunning { margin: 2px 0 0; }
/* The numbered board, once a queue is building: the buttons are still the way to
   add to it, so they stay put - this only marks that something is waiting. */
.numbtns.queued { outline: 1px dashed var(--line); outline-offset: 3px; border-radius: 10px; }
/* In the points box the hint sits on the same line as the question - the box is
   squashed as far as it goes, because every pixel here is one the ladder wants. */
#nominateBox .hint { display: inline; margin: 0; font-size: 12px; }
#nominateBox label { display: block; margin-bottom: 0; }

/* ---- the game code -------------------------------------------------------
   The code is generated for you before you arrive, so this row is: a small
   "give me another" button, then the code itself, big and centred, reading as a
   thing you can say out loud. The button used to sit AFTER the field, which put
   the action at the end of the sentence and made the whole row look like one
   long input with an arrow stuck in it. */
.codeedit { display: flex; gap: 8px; margin-top: 6px; align-items: stretch; }
.codeedit input {
  flex: 1 1 auto;
  min-width: 0;
  height: 48px;
  padding: 0 12px;
  text-align: center;
  text-transform: lowercase;
  letter-spacing: 3px;
  font-weight: 700;
  font-size: 21px;
}
/* The re-roll button is a small square, not a word: it holds one glyph, so it is a fixed 48x48 and the code
   field beside it keeps every other pixel of the row.
    It is still stated explicitly even though `.mode button` is now `width: auto` - this button is `flex: 0 0 48px`
   as well, and a future change to the row should not be able to squash the glyph. It is also no longer
   out-specifying anything: that fight was with the old `width: 100%`, which is gone. */
.codeedit .reroll {
  flex: 0 0 48px;
  width: 48px;
  height: 48px;
  padding: 0;
  font-size: 21px;
  line-height: 1;
  color: var(--muted);
  background: var(--panel);
}
.codeedit .reroll:hover { background: #26303d; color: var(--text); }
input[type=range] { flex: 1; accent-color: var(--accent); height: 26px; }

/* Which club do you want to coach. A plain select, listing all nineteen. */
select.clubpick {
  display: block;
  width: 100%;
  margin-top: 6px;
  padding: 9px 10px;
  font: inherit;
  font-size: 15px;
  color: var(--text);
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: 8px;
}
select.clubpick:focus { outline: 2px solid var(--accent-2); outline-offset: 1px; }

/* ==========================================================================================
 * ⚠ THE ROUNDS BOX IS NOT A CLUB PICKER, AND IT WAS WEARING THE CLUB PICKER'S WIDTH.
 *
 * Colin: *"the rounds drop down is ridiculous, it's as wide as ... you got a number there and you use the full
 * width."* He is describing a real cause and not a taste: the rounds control borrows `select.clubpick` for its
 * frame (`<select id="roundPick" class="clubpick roundpick">`), and `.clubpick` is `width: 100%` because a club
 * name wants the width - "North Melbourne" plus the arrow. So a control holding the characters "24" was drawn
 * 868px wide. Measured in the browser before this rule: `#roundPick 868px drawn, 59px wanted, 802px empty`.
 *
 * THE OPTIONS ARE ONE OR TWO DIGITS AND NOTHING ELSE - `paintRoundOptions` fills this from `sizeRulesFor(...)
 * .options`, which are round counts - so the box is sized for two digits and the arrow. It cannot need more.
 * ========================================================================================== */
select.roundpick { width: 96px; }

/* ==========================================================================================
 * "SELECT YOUR TEAM" - A CUSTOM DROP-DOWN, SO EACH CLUB CAN CARRY A "rename" LINK (Colin, 18 Oct 2026).
 *
 * A native <select> cannot hold a link, and people were not discovering that clubs can be renamed. The button looks
 * like the select it replaces (same frame as `select.clubpick`); the list opens under it and each row is the club's
 * name with a small "rename" beside it in the blue accent (`--accent-2`, the same blue as the focus ring) at 11px against
 * the name's 15px - quieter than the name on both counts, which is the point: the name is the choice, rename is the aside.
 *
 * ⚠ THE NATIVE SELECT STAYS IN THE DOM (`.srhide`): it is what the rest of the page reads. It is hidden by clipping, not
 * `display: none`, so it is still a real form control with a real value and still takes the `change` event.
 * ========================================================================================== */
.srhide {
  position: absolute !important; width: 1px !important; height: 1px !important; margin: -1px !important; padding: 0 !important;
  overflow: hidden !important; clip: rect(0 0 0 0) !important; clip-path: inset(50%) !important; border: 0 !important; white-space: nowrap;
}
.clubdd { position: relative; }
.clubdd > label { margin-bottom: 0; }
.clubddbtn {
  display: block; width: 100%; margin-top: 6px; padding: 9px 30px 9px 10px; position: relative;
  text-align: left; font-size: 15px; border-radius: 8px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.clubddbtn::after {
  content: ''; position: absolute; right: 11px; top: 50%; width: 6px; height: 6px; margin-top: -5px;
  border-right: 2px solid var(--muted); border-bottom: 2px solid var(--muted); transform: rotate(45deg);
}
.clubddbtn[aria-expanded="true"]::after { margin-top: -2px; transform: rotate(225deg); }
.clubddbtn:focus-visible, .clubddlist:focus-visible { outline: 2px solid var(--accent-2); outline-offset: 1px; }
.clubddlist {
  position: absolute; z-index: 30; left: 0; top: 100%; margin: 2px 0 0; padding: 4px; list-style: none;
  min-width: 200px; width: max-content; max-width: min(300px, calc(100vw - 32px));   /* narrow: a club name and a small link, not the width of the card */
  max-height: min(340px, 55vh); overflow-y: auto; overscroll-behavior: contain;
  background: var(--panel-2); border: 1px solid var(--line); border-radius: 8px; box-shadow: 0 8px 24px rgba(0, 0, 0, .45);
}
.clubddlist .clubddrow {
  display: flex; align-items: center; justify-content: space-between; gap: 14px;
  padding: 7px 8px; border-radius: 6px; cursor: pointer; color: var(--text); font-size: 15px;
}
.clubddlist .clubddrow[aria-selected="true"] { background: rgba(53, 208, 127, .14); }
.clubddlist .clubddrow.active, .clubddlist .clubddrow:hover { background: rgba(255, 255, 255, .09); }
.clubddlist .clubddname { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The rename link: smaller, quieter, a different colour - and a button, so it can be focused, but dressed as a link. */
.clubddlist .clubddrename {
  flex: 0 0 auto; width: auto; margin: 0; padding: 4px 2px; background: none; border: 0; border-radius: 4px;
  font-size: 11px; line-height: 1.2; color: var(--accent-2); text-decoration: underline; cursor: pointer;
}
.clubddlist .clubddrename:hover { background: none; color: var(--text); }
.clubddlist .clubddrename:focus-visible { outline: 2px solid var(--accent-2); outline-offset: 1px; }
/* The Night Series picker's button says "Pick or create your club" (Colin: make it wide enough for that on one line) - wider than the
   260px a club name needs; on a phone it is the row's full width and the text wraps rather than clips. */
.coachpick.nightdd { flex: 0 1 320px; min-width: min(100%, 300px); }
.nightdd .clubddbtn { white-space: normal; }
@media (min-width: 560px) { .nightdd .clubddbtn { white-space: nowrap; } }

/* THE COLOUR PICKER IS A CONTROL, SO IT HAS TO LOOK LIKE ONE.
   `input[type=color]` renders as a bare swatch with no border and no height of its
   own, so beside the styled name box in the club-rename block it read as a stray
   patch of colour rather than something to press. Sized and framed to match the
   text input it sits next to, on the same row. */
input[type=color] {
  width: 3rem;
  height: 2.25rem;
  padding: 2px;
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: 8px;
  cursor: pointer;
}
input[type=color]:focus { outline: 2px solid var(--accent-2); outline-offset: 1px; }

/* The nomination, as a block of numbered buttons rather than a slider. Every
   option is on screen at once and takes one click, instead of being hidden
   behind a drag handle you have to aim.
   Colin cut the board to 2..12, so all eleven now fit on ONE row - which is
   height the ladder gets back. auto-fit still wraps them if the column is
   narrow, and a short range (finals, capped at 5) just gets bigger buttons. */
.numbtns {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(38px, 1fr));
  gap: 5px;
  margin: 3px 0 3px;
}
.numbtn {
  font: inherit;
  font-size: 15px;
  font-weight: 650;
  padding: 6px 0;
  /* BLACK NUMBERS ON A LIGHT KEY.
     Colin, twice: "the selection buttons have got poor contrast to the background,
     especially when resized down - I'm talking about in the points box", and then,
     when a mid-grey fill was still not enough: "let's make them lighter with black
     numbers."
     He is right that the fill was the problem, so the fill is now LIGHT and the
     numbers are BLACK - the biggest contrast a button can have, and it does not care
     about scale: the windows are drawn with a CSS zoom, so a hairline border thins as
     the window shrinks while a light key on a dark card does not.
     Measured with tools/_check-contrast.mjs (computed colours, real browser, 1600x950
     and 1100x700): fill #cfe0d6 is 12.4:1 against the card behind it and the black
     #08150f numerals are 13.6:1 on the fill - where the original was 1.12:1 and 1.5:1.
     The CHOICE (`.chosen`, the number this quarter actually got) stays the saturated
     green so it is still a different thing from an unlit key. */
  color: #08150f;
  background: #cfe0d6;
  border: 1px solid #eaf3ee;
  border-radius: 8px;
  cursor: pointer;
  font-variant-numeric: tabular-nums;
}
.numbtn:hover { background: #e6f2ec; border-color: #ffffff; }
.numbtn:active { transform: translateY(1px); }
.numbtn.chosen {
  background: var(--accent);
  border-color: var(--accent);
  color: #06170e;
}
.numbtn:focus-visible { outline: 2px solid var(--accent-2); outline-offset: 2px; }
/* A FINISHED ROUND IS NOT IN PLAY YET, and the buttons SAY SO rather than vanishing.
   Colin, looking at the held round: "buttons in the points box disappeared again."
   They are refused while the round is held - see the green note and pressNumber in
   app.js - but taking them away made the box look broken and made the whole block
   jump every time a round ended. Dimmed and unclickable says the same thing without
   either problem, and the note right above them says what to press instead. */
.numbtn:disabled {
  opacity: .35;
  cursor: not-allowed;
}
.numbtn:disabled:hover { background: #cfe0d6; border-color: #eaf3ee; }

/* Nineteen clubs have to fit above the points box, so the rows are tight. At
   6px of padding the ladder wanted 766px and got squeezed into 240 - four rows
   visible and the rest hidden behind the points box. */
/* The score-variables card's table: small, and the coached clubs picked out so a
   coach can find their own row at a glance. */
/* The score variables card scrolls inside itself (Colin, 4 Oct 2026), like the shot timing card. */
#scoreVarsCard { display: flex; flex-direction: column; overflow: hidden; }
#scoreVarsBody { flex: 1 1 auto; min-height: 0; overflow-y: auto; overflow-x: auto; }
table.vars { width: 100%; border-collapse: collapse; font-size: 12px; }
table.vars th { text-align: left; color: var(--muted); font-weight: 500; padding: 2px 4px; border-bottom: 1px solid var(--line); font-size: 11px; text-transform: uppercase; }
table.vars td { padding: 1px 4px; border-bottom: 1px solid #212936; white-space: nowrap; }
table.vars td.num, table.vars th.num { text-align: center; font-variant-numeric: tabular-nums; }
table.vars tr.is-coached td { color: var(--accent); }

/* ---- THE SHOT TIMING DIAGNOSTIC CARD --------------------------------------
   Colin asked for the card himself: "I need a new feature inside the owner button
   ... it's a diagnostic feature ... I want some variable outputs displayed to me
   ... the time intervals when the scores for the quarters are laid out ... I want
   those time periods displayed through all the teams for the upcoming quarter or
   the previous quarter ... because I want to take a look at them because I'm not
   convinced that they're getting laid out properly."

   EVERYTHING HERE IS SCOPED TO #shotsCard AND ITS OWN CLASSES, deliberately. A shared
   rule changed to suit one card is how a card nobody was looking at breaks - so the
   summary table is `.shots-sum` and the raw list is `.shots-raw`, rather than either
   of them borrowing `table.vars` or `table.ladder`.

   THE CARD IS NOT THE SCROLLER. The title bar and the eight resize handles are
   children of the card, so anything the CARD scrolls takes them with it and the card
   can no longer be moved or resized - which is what Colin hit on the fixture card:
   "when the scroll bar is in play on the fixture card the title bar scroll too". The
   content that cannot fit scrolls in `.shots-scroll` inside the card instead. */
#shotsCard {
  display: flex;
  flex-direction: column;
  min-height: 160px;
  overflow: hidden;
  /* More room at the top than a plain panel: in arrange mode the card's own title bar
     is an absolute overlay across its top edge, and the heading must start below it. */
  padding: 20px 10px 10px;
}
#screen-game .col-side > #shotsCard { flex: 0 1 auto; }
/* THE WRAPPER IS THE SCROLLER. `min-height: 0` is what lets it shrink inside the flex
   column at all; without it the card refuses to go below its content and the overflow
   comes back to the card, which is the fault this exists to prevent. */
.shots-scroll {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
}
/* WHICH ROUND AND QUARTER, first on the card: a coach has to know what he is looking
   at before any number on it means anything. */
.shots-head {
  margin: 0 0 4px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: .3px;
}
/* THE RULES, ON THE CARD, because Colin is checking a suspicion and has to be able to
   judge the numbers against what "correct" is. The figures come from `view.shotRules`,
   so this line can never quote a rule the engine is not using. */
.shots-rules {
  margin: 0 0 8px;
  font-size: 11px;
  line-height: 1.35;
  color: var(--muted);
}
.shots-rule-num { color: var(--text); font-variant-numeric: tabular-nums; }
/* One block per club that played: the profile line, the summary row, then its shots. */
.shots-club { margin: 0 0 10px; }
.shots-clubname { margin: 0 0 2px; font-size: 12px; font-weight: 600; }
.shots-sum { width: 100%; border-collapse: collapse; font-size: 11px; }
.shots-sum th {
  text-align: left; color: var(--muted); font-weight: 500;
  padding: 1px 4px; border-bottom: 1px solid var(--line);
  font-size: 10px; text-transform: uppercase;
}
.shots-sum td { padding: 1px 4px; border-bottom: 1px solid #212936; white-space: nowrap; }
.shots-sum td.num, .shots-sum th.num { text-align: right; font-variant-numeric: tabular-nums; }
/* THE MATCH'S OWN NUMBERS, set apart from the club's: they answer a different
   question - the floors and the cap govern the MERGED sequence, not one club's. */
.shots-matchline {
  margin: 2px 0 3px;
  font-size: 11px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
/* THE RELAXED FLAG IS LOUD ON PURPOSE. Colin's whole suspicion is that the layout is
   not what it should be, and a quarter where the 90-second goal floor had to give way
   is exactly the state he is hunting: hiding it in a column of grey figures would
   defeat the card. Colour is never the only signal - the words say it too. */
.shots-relaxed {
  margin: 2px 0 3px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .4px;
  color: var(--warn);
}
/* THE RAW LIST: every shot, in clock order, with both gaps. Denser than the summary
   because it is the evidence rather than the verdict. */
.shots-raw { width: 100%; border-collapse: collapse; font-size: 11px; }
.shots-raw th {
  text-align: left; color: var(--muted); font-weight: 500;
  padding: 1px 4px; border-bottom: 1px solid var(--line);
  font-size: 10px; text-transform: uppercase;
}
.shots-raw td { padding: 0 4px; border-bottom: 1px solid #212936; white-space: nowrap; }
.shots-raw td.num, .shots-raw th.num { text-align: right; font-variant-numeric: tabular-nums; }
/* A goal and a behind are not the same event and the list has to say which is which at
   a glance, without a wall of colour: the G is the goal. */
.shots-raw td.shots-type { font-weight: 700; text-align: center; }
.shots-raw tr.shots-goal td.shots-type { color: var(--accent); }
/* NOTHING TO SHOW YET - nothing has been played, or this club kicked nothing. A plain
   sentence, because a blank card reads as a broken one. */
.shots-none { margin: 0 0 8px; font-size: 12px; color: var(--muted); }
.shots-error { margin: 0 0 8px; font-size: 12px; color: var(--warn); }

table.ladder {
  width: 100%; border-collapse: collapse; font-size: 12px;
  /* Grow to fill the panel rather than run past it: the rows take the spare
     height, so the ladder is as big as the space allows AND never scrolls.
     Colin: "the ladder wants to be bigger... I don't want people having to
     scroll to see the ladder".
     The ROW PADDING is what sets the minimum height, so it is zero: the text
     stays at 13px (bigger than it was) and the rows still stretch to fill
     whatever the panel has, on any window height. */
  flex: 1 1 auto;
}
table.ladder th { text-align: left; color: var(--muted); font-weight: 500; padding: 2px 4px;
  border-bottom: 1px solid var(--line); font-size: 11px; text-transform: uppercase; }
table.ladder td { padding: 0 4px; border-bottom: 1px solid #212936; }
/* No wrapping: on a narrower window a club name folding onto two lines made the
   ladder taller than its panel, which is the scroll Colin does not want. The
   numeric columns give up the width instead - they are 1-3 digits. */
table.ladder td, table.ladder th { white-space: nowrap; }
table.ladder td.formcol, table.ladder th.formcol { text-align: left; letter-spacing: 0; font-weight: 700; }
.form-W { color: #35d07f; }
.form-L { color: #ff5d5d; }
.form-D { color: #ffd23f; }
table.ladder tr.me td { background: rgba(53,208,127,.08); }
table.ladder tr.out td { opacity: .45; text-decoration: line-through; }
/* SACKED IS NOT ELIMINATED. Colin: "maybe a flag like a sacked coach flag ...
   where it's got coach on the ladder and it's got the person's name, maybe you
   could put down ... sacked."
   So the row is NOT struck through and NOT faded the way an eliminated coach's is:
   the club is still playing, still earning ladder points and can still make the
   finals - it is the PERSON who is gone. The flag is an amber marker down the
   left-hand edge, and the coach column itself reads "sacked" (see renderLadder in
   app.js). The row's TEXT keeps its own colour: a club name is drawn in the club's
   colour, and tinting the whole row would either fight that or make the numbers
   harder to read than the ladder's own type. */
table.ladder tr.sacked td:first-child {
  box-shadow: inset 3px 0 0 var(--warn); color: var(--warn);
}
/* THE FINALS LINES, AND A WASH SO FAINT IT IS BARELY THERE.
   Colin: "lets wash out those coloured backgrounds ... Keep them of course just tone
   it back a little more. atm too contrasting". The lines mark the zones - a line
   under the sixth (the week off) and under the tenth (the last finals place) - and
   the washes are now 5%, down from 17% and then 8%.
   At this strength a wash cannot interfere with the writing: GOLD COAST is orange
   (#FF9500) and sits in the amber band, and the measured contrast of every club
   colour and of the white text is reported by tools/_probe-scroll.mjs so this can
   be checked rather than argued about. */
/* THE TOP-SIX LINE AND THE WILD CARD BAND EXIST ONLY IN A FINAL TEN (Colin, 15 Oct 2026: a final four, five or eight has no wild card round, so no line under 6th) - renderLadder adds these classes only when the finals field is 10 */
table.ladder tbody tr.top6 { background: rgba(53, 208, 127, .05); }
table.ladder tbody tr.wcband { background: rgba(255, 196, 87, .05); }
table.ladder tbody tr.top6-cut td { border-bottom: 3px solid rgba(53, 208, 127, .9); }
/* the line under the last club that makes the finals - set by renderLadder from the competition's own finals field (4, 5, 8 or 10), not fixed at 10 */
table.ladder tbody tr.finals-cut td { border-bottom: 3px solid rgba(255, 196, 87, .85); }
table.ladder tbody tr.finalist td { background: rgba(255, 196, 87, .07); }
table.ladder td.num, table.ladder th.num { text-align: right; }

.fixtures { margin-top: 16px; }
.fixtures h3 { font-size: 12px; text-transform: uppercase; color: var(--muted); letter-spacing: .8px; }
.fixtures .fx { display: flex; justify-content: space-between; padding: 6px 0; border-bottom: 1px solid #212936; font-size: 13px; }
.fixtures .fx .vs { color: var(--muted); }

/* Previous winners: one row per finished season. */
table.history { width: 100%; border-collapse: collapse; font-size: 13px; margin-top: 12px; }
table.history th {
  text-align: left; color: var(--muted); font-weight: 500; padding: 5px 8px;
  border-bottom: 1px solid var(--line); font-size: 11px;
  text-transform: uppercase; letter-spacing: .8px;
}
table.history td { padding: 8px; border-bottom: 1px solid #212936; vertical-align: top; }
/**
 * `.hist-win` IS GONE. It painted a club name the accent green and nothing on this card has carried the class
 * since the result line was rebuilt as winner-and-loser sides - a rule for markup that does not exist is how a
 * colour comes back without anyone deciding it should.
 */

/**
 * ⚠ THE GREEN IS OFF THE GRAND FINAL RESULT, AT COLIN'S ASKING.
 *
 * Colin, reading this row: *"tell me precisely why you wanted to go with green text for Fremantle's score and
 * white text for Carlton's score."* He asked twice, and the honest answer is that nobody chose it here - the
 * accent green was already on this cell when the table was restyled, and it was carried forward unexamined. Seen
 * again it reads as a colour coding nobody asked for: the whole winning BLOCK - club name AND its score - was
 * green, and the loser's score was greyed, so the row said "won" three times over.
 *
 * WHAT IT IS NOW:
 *
 *   each club's NAME is in its own club colour, which is what the rest of the board does (`clubName`);
 *   BOTH SCORES are the card's own plain colour, the same treatment the best-attack and best-defence figures get;
 *   the WINNER is still marked - bold, and its "d" sits between them - so the result is as readable as it was.
 *
 * The rule is kept in one place: `--gf-winner` is what a winner's name is drawn with when the marking has to
 * shout, and it is the CLUB's own colour, not the app's accent.
 */
table.history .hist-gf { white-space: nowrap; }
table.history .hist-gf-team { display: inline; white-space: nowrap; }
table.history .hist-score { margin-left: 7px; color: var(--text); font-variant-numeric: tabular-nums; }
table.history .hist-gf-d { color: var(--muted); margin: 0 10px; }
/* THE WINNER IS BOLD AND NOT RECOLOURED. `font-weight` alone marks it, and the club colour underneath is left
   exactly as `clubName` set it - so Fremantle reads purple and Carlton reads blue, as they do everywhere else. */
table.history .hist-gf-win { font-weight: 700; }
/**
 * ⚠ AND THE SCORE IS PUSHED AWAY FROM THE CLUB NAME, WHICH IS THE "SQUASHED" HE COULD SEE.
 *
 * Colin, reading the row: *"you could spread out the grand final teams their scores a little bit more - they're a
 * bit squashed."* He was right, and here is exactly why it happened: the score is appended to the club with no
 * space of its own, and the four things in the line had only 5px at the "d" to separate any of them -
 *
 *     FREMANTLE 30.18.198 d CARLTON 20.27.147
 *      └────────┬───────┘   └───────┬──────┘
 *          no gap at all          5px each side
 *
 * so "FREMANTLE30.18.198" read as one word until the eye separated them itself, and the loser's name crowded the
 * same 5px. The gaps are now on the SCORE (7px from its club) and around the "d" (10px each side).
 *
 * ⚠ THE TABLE IS EIGHT COLUMNS WIDE NOW, AND THE ROOM CAME FROM WHERE HE SAID IT WAS. Third and Fourth are BACK -
 * he never asked for them to go; dropping them was my reading of "the final four can now be best attack and best
 * defence" and it was wrong. His instruction: *"you've got to put 3rd and 4th back, and the best attack the team
 * name and the amount, and the best defence the team name and the amount ... you've got coach that can go further
 * to the left, you don't need 2026 in the date, you just can use 26, so that can squash up a bit so you've got
 * room."*
 *
 * So the room is taken in that order: the date is two digits (see app.js), the 260px floor under this column is
 * gone, and the fixed widths give way to `nowrap` where they can.
 *
 * ⚠ WHAT IS NOT ALLOWED IS A CELL WRAPPING. This is a board to be read DOWN, and a wrapped club name turns one row
 * into two and breaks the column it is in. So every cell that holds a result is `nowrap`, and the two that can
 * give way - the season name and the Third/Fourth clubs - ellipsise at a set width instead. That is measured by
 * `_check-history-card.mjs` rather than trusted to the eye.
 */
/* The season's own name gives way first: a long competition title ellipsises rather than pushing the finals
   across the card. The date under it is two digits now, so this column is narrower than it was. */
table.history .hist-season { max-width: 180px; }
table.history .hist-coach { white-space: nowrap; }
table.history .hist-points { white-space: nowrap; }
/* THIRD AND FOURTH ARE CLUB NAMES ALONE - no score, no coach - so they need the width of a club and no more.
   They are the second thing to give way, after the season name. */
table.history .hist-place { white-space: nowrap; max-width: 148px; overflow: hidden; text-overflow: ellipsis; }
/* Previous winners: one row per finished season, ON ONE LINE. */
table.history td .muted.small { margin-left: 6px; }
/**
 * ⚠ AND NEITHER SCORE IS RECOLOURED ANY MORE - not the winner's and not the loser's.
 *
 * These two rules are what Colin was looking at: `hist-score-win` painted the winning side's score the accent
 * green, and the `:not(...)` rule greyed the loser's. Both are gone. A score is a score and reads in the card's
 * own colour; WHO won is said by the bold club name and the "d" between them, which is the football-program
 * convention this row was built on and does not need a colour to carry it.
 *
 * The rules are kept, saying what they now do, rather than deleted - because the class names are still on the
 * markup, and a future reader finding them unstyled deserves to know they were deliberately emptied.
 */
table.history .hist-score-win { color: inherit; }
/*
 * THE BEST ATTACK AND THE BEST DEFENCE - the two columns that replaced "Third" and "Fourth".
 *
 * Colin: *"let's go with the best attack and the best defence categories inside the history - what used to be one
 * line of top 4 teams, with their scores and coach and remaining points, can now be (best attack and best
 * defence)."*
 *
 * ONE LINE, CLUB THEN NUMBER, never wrapped: these are two single facts per season and a column that wraps turns
 * a scannable board into a paragraph. The number takes the same treatment the Grand Final's scores take
 * (`hist-score`, tabular) so the two figures in these columns line up down the card and can be compared at a
 * glance - which is the whole reason for showing them.
 */
table.history .hist-extreme { white-space: nowrap; }
table.history .hist-extreme .hist-score { color: var(--text); font-variant-numeric: tabular-nums; }
/*
 * An OLD season has no score to show - the finals were not played when it finished -
 * so its club line simply has no score in it. Nothing stands in for the number: an
 * empty space reads better than "undefined" or a zero that was never kicked.
 */

ul.results { list-style: none; padding: 0; margin: 0; }
ul.results li {
  background: var(--panel-2); border: 1px solid var(--line); border-radius: 8px;
  padding: 12px 14px; margin-bottom: 8px;
}
.resline { display: flex; justify-content: space-between; gap: 12px; align-items: baseline; }
.score { font-variant-numeric: tabular-nums; font-weight: 700; font-size: 17px; }
.win { color: var(--accent); }
.loss { color: var(--danger); }
.draw { color: var(--warn); }
.strength { font-size: 12px; color: var(--muted); }

/* On the GAME screen the photograph becomes a faded wallpaper filling the whole
   page, so the fixture, the round being played and the ladder can all use the
   full width - the layout the old program had, on one screen. It is dimmed
   harder than elsewhere because it is atmosphere here, not something to read. */
main:has(#screen-game:not(.hidden)) { max-width: none; margin: 0; }
body:has(#screen-game:not(.hidden)) .topbar { width: 100%; }
/* THE LOBBY'S PHOTOGRAPH FILLS THE WINDOW, AND THE CARDS KEEP OUT OF IT.
 *
 * Two complaints, three years apart in the same file, and the fix for the second must not
 * undo the first:
 *
 *   1. "the lobby page has the background photo with the same issue as the other pages in
 *      that its not full screen top/bottom" - the photo is a ::before, so the screen that is
 *      up has to inset it. `inset: 0` is what makes it reach the top and the bottom of the
 *      window, and that is KEPT here.
 *   2. Colin, on the same screen: "All these cards are all too big they could all be pushed
 *      over to the left so you could see picture on the right hand side ... you just taken up
 *      space for nothing". The `max-width: none` that came with fix 1 let the cards run the
 *      full width of the window, straight across the picture. MEASURED at 1887x951: main was
 *      **1872px wide (99.2% of the window)**, the two mode cards were 886px each with the
 *      right-hand one ending at **x=1829**, the picture was painted at **x=595..1887** (full
 *      height) - so the cards sat over 96% of it - and the wash over it was `left 0, width
 *      1872`, dimming the whole window.
 *
 * SO THE CARDS GO BACK INTO THE APP'S OWN COLUMN (`--ui-col`, the same left-hand column every
 * other screen's interface lives in) while the photo keeps `inset: 0`. The photo is
 * contain-fitted to the RIGHT of the window, so nothing is cropped and nothing is stretched:
 * at 1887x951 it paints 1292x951 at x=595..1887, and with the cards ending at x=852 that
 * leaves **1035px of clear picture** down the right-hand side.
 *
 * And the wash follows the cards rather than the window. It is there to give the translucent
 * cards contrast; over the photograph it was only ever dulling it. */
body.has-bg-image:has(#screen-lobby:not(.hidden))::before { inset: 0; }
body.has-bg-image:has(#screen-lobby:not(.hidden))::after {
  left: 0;
  right: auto;
  /* THE SAME WIDTH AS THE DIVISION, from the one expression - see the note on `main:has(#screen-lobby...)`. The
     wash exists to give the translucent cards contrast; it has no business lying over the picture, and a width
     that disagreed with the column would leave either a strip of unpainted page or a strip of dulled photograph. */
  width: max(560px, min(1000px, calc(100vw - min(100vw, 100vh * var(--photo-aspect, 1.359)))));
}
/**
 * ==========================================================================================
 * ⚠⚠ ONE DIVISION, FROM THE LEFT EDGE UP TO THE PHOTOGRAPH - AND EVERYTHING LIVES INSIDE IT.
 *
 * Colin: *"just keep it inside the photo. Why don't you make a division up until the edge of the photo and put
 * everything in the division."*
 *
 * That is the whole design, and it is simpler than what was here. There is ONE column (`main#app`), it ends where
 * the photograph begins, and every card is a block inside it. Nothing else states a width - which is what went
 * wrong twice before:
 *
 *   1. the panel carried `max-width: var(--ui-col)`, and `--ui-col` is a PERCENTAGE (`clamp(420px, 53%, 1000px)`).
 *      A percentage in `max-width` resolves against the element's OWN containing block, so it gave `main` 53% of
 *      the body (992px) and the panel 53% of the 948px section (503px) - the panel was half the width of the column
 *      it sat in, and 445px of the lobby was empty.
 *   2. removing that cap instead of fixing it let the panel run to its parent's full width, 956px, straight over
 *      the picture.
 *
 * SO THE COLUMN STATES ITS EDGE, ONCE, AND THE CARDS STATE NOTHING. The picture is `contain`-fitted and
 * right-aligned (`background-position: right center`), so its left edge is arithmetic rather than a guess:
 *
 *      painted width  = min(viewport width, viewport height x aspect)
 *      its left edge  = viewport width - painted width
 *
 * `--photo-aspect` is published by web/background.js from the loaded image's own naturalWidth/naturalHeight, so a
 * different photograph needs no change here. The 1.359 fallback is the current picture (2000x1472) and is only in
 * play for the instant before it loads. The 1000px cap is the width the lobby always had, so a window where the
 * picture is squeezed - or absent - cannot make the column wider than it has ever been.
 *
 * ⚠ AND THE EDGE IS THE BOX EDGE, PADDING INCLUDED (`box-sizing: border-box` is set globally - see the rule near
 * the top of this file). That is what makes "up to the photo" exact: `main` occupies 0..595 and its padding is
 * INSIDE that, so the cards end short of the picture by the padding alone, which is the gap he asked for. The first
 * attempt subtracted the padding a second time and stopped 44px short of the picture for no reason.
 * ========================================================================================== */
main:has(#screen-lobby:not(.hidden)) {
  /* A MINIMUM, because on a window whose shape is close to the photo's (1280x900, 1440x1024...) the space beside the picture is nearly nothing and the whole lobby was squeezed to a 13px sliver - the column may overlap the photo rather than vanish (found 18 Oct 2026) */
  max-width: max(560px, min(1000px, calc(100vw - min(100vw, 100vh * var(--photo-aspect, 1.359)))));
  margin: 0;
}
/*
 * THE LOBBY'S COLUMN IS WIDER THAN EVERY OTHER SCREEN'S, SO THE CARDS CAN BE NARROWER.
 *
 * Colin, on this card: *"we're going to get the card that says play with friends and we're going to squash
 * it up to be half of the width that it is because it's out of control too wide ... and then we can put
 * these things side by side so that everything's fitting on the one page instead of having to scroll down."*
 *
 * MEASURED at 1887x951, in the app's usual 830px column (780 of card): the halves are 362px, and at 362 the
 * game code and the line under it wrap (`#gameCode` + its hint) - which is exactly what the old comment
 * above `.modes > .mode:last-child` recorded, and why the create card used to take the whole width. Halving
 * the cards therefore makes that one card TALLER, and the page came out at **1092px in a 951px window**:
 * the two changes he asked for were both right and still left 141px to find.
 *
 * THE COLUMN IS THE THING WITH SLACK IN IT. The cards only have to be half of it, so giving the column
 * 170px more width takes the halves to ~482 - wide enough for the code row to sit on one line again - and
 * the whole card comes down with it, in his words, without touching a word of his copy.
 *
 * IT IS A LOBBY-ONLY OVERRIDE because the picture is only on these screens, and it is deliberately modest:
 * the photograph paints from x=595 to the right-hand edge (1292px wide, contain-fitted, at 1887x951), so a
 * card column of 1000 leaves **595px of clear picture** where 830 left 1035. He asked for the cards to be
 * *"pushed over to the left so you could see picture on the right hand side"* - this keeps two thirds of the
 * picture clear and buys a screen with no scrolling. See the note above for the measurement that set 830.
 */
body:has(#screen-lobby:not(.hidden)) { --ui-col: clamp(420px, 53%, 1000px); }
/*
 * AND THE LAST FEW PIXELS COME OUT OF THE AIR, NOT OUT OF HIS WORDS.
 *
 * MEASURED after the width change: the page was **1024px in a 951px window**, with the air between the
 * blocks costing 66px, the controls 41-47px each and the card's own padding the rest. Every rule below is
 * scoped to `#screen-lobby`, so no other screen is tightened by it, and none of it removes a single word:
 * it is padding, margins and control height. The one thing it must not do is crowd the reader - the gaps
 * here are still 8-12px, which reads as a tighter card rather than as a cramped one.
 */
/* ==========================================================================================
 * ⚠⚠ THE LOBBY'S PANEL IS AS WIDE AS THE DARK GREEN PANEL. THIS IS THE LINE THAT WAS MISSING.
 *
 * Colin, four times, with screenshots: *"about a third of the grey background card needs to disappear, it's about a
 * third too wide"*, then *"have a look at the width of the dark green left panel and put the card inside that"*,
 * then *"fix the **** card"*, and finally *"how many times do you have to be told."*
 *
 * He was right every time and I kept changing the mechanism instead of the thing he could see. MEASURED on his own
 * window (924x691), lobby on screen:
 *
 *      main#app         0 .. 924      <- full width
 *      .panel           no max-width at all
 *      #coachCard       45 .. 879      = 834px, i.e. nearly the whole window
 *
 * WHY IT WAS FULL WIDTH. `.panel` carries NO width of its own (styles.css:826 sets padding, background, radius and
 * nothing else). Normally `main` constrains it - `main { max-width: var(--ui-col) }` - but BELOW 1279px the media
 * query sets `main { max-width: 100% }`, so the constraint disappears and the panel inherits the viewport. The
 * `--ui-col` relationship fixed in §16 was therefore never reaching the card at the sizes he was looking at: it
 * capped the ELEMENT, and the element had already given up.
 *
 * SO THE CONSTRAINT GOES WHERE THE CARD ACTUALLY LIVES. `--ui-col` is already defined as "never narrower than the
 * card column it has to hold" (§16), so capping the panel at that width puts the card inside the green panel at
 * EVERY size, above and below the breakpoint, without a second number to keep in step.
 *
 * IT IS SCOPED TO THE LOBBY on purpose: the waiting room and the game screen position their own cards, and a shared
 * rule changed to suit one screen is how a screen nobody is looking at breaks.
 * ========================================================================================== */
/**
 * ==========================================================================================
 * THE CARDS FILL THE DIVISION. NOTHING HERE STATES A WIDTH.
 *
 * This is Colin's own answer - *"make a division up until the edge of the photo and put everything in the
 * division"* - and it is the end of a fault that had two goes at going wrong. `main` is the division: it ends at
 * the photograph's edge (see the note above it). `#screen-lobby` is its child, the panel is a block inside that,
 * and `.modes` is a grid inside the panel - so EVERY ONE OF THEM IS ALREADY the right width, and any `max-width`
 * written here can only make something narrower than the space it was given.
 *
 * That is exactly what happened: the panel used to carry `max-width: var(--ui-col)`, and because `--ui-col` is a
 * PERCENTAGE it resolved against the panel's own parent rather than the window - leaving the panel at 503px inside
 * a 948px section. Four hundred and forty-five pixels of the lobby were empty and every card in it was squeezed
 * into half the room, which is why the Night Series club list could not show "Rename" on a line.
 *
 * So the only width statement left is the division's. If a card ever needs to be narrower than the division, it
 * says so and says why - it does not inherit a cap from three levels up.
 * ========================================================================================== */
#screen-lobby .panel { padding: 14px var(--layout-pad) 12px; }
#screen-lobby .panel .roundbar { margin-bottom: 8px; }
/**
 * ⚠⚠ AND IT IS ONE COLUMN ON A NARROW WINDOW, WHICH IS WHAT CLOSES THE DEAD SPACE AND THE OVERFLOW TOGETHER.
 *
 * Colin, twice in one message: *"there's too much padding on the right hand side, you could have made it wider"*
 * and *"the join button overflows it."* MEASURED at his 909x691, the lobby:
 *
 *      main#app        0 .. 894
 *      .modes          x45 .. x449   = 404px wide, in TWO columns of 196px
 *      #coachCard      x45 .. x449   = 404px  (it spans both columns)
 *      the dark panel  starts at x0 and the page is 894 wide
 *
 * So the cards were 404px wide inside a panel that had ~850px to give, and the right-hand ~440px of the panel was
 * empty. The two-column split is what did both faults at once: each column is only 196px, which is not enough for
 * the join row (a field plus a button) AND it leaves half the panel doing nothing.
 *
 * `--cards-w` IS THE ONE NUMBER THAT DECIDES IT. It is the width a card is designed for (380px), so a container
 * that can give each card that much may sit side by side, and one that cannot stacks them. That is a comparison of
 * two lengths the stylesheet already has - no new pixel figure, and nothing to keep in step:
 *
 *      the two-column layout needs  --cards-w * 2 + gap + padding
 *
 * and below that the grid is ONE column and every card takes the full width of the panel. Above it, the lobby keeps
 * the two-column arrangement Colin asked for (*"we can put these things side by side so that everything's fitting
 * on the one page instead of having to scroll down"*).
 */
@media (max-width: 1279px) {
  /**
   * ⚠⚠ ONE COLUMN, AND EVERY CARD SPANS IT EXPLICITLY.
   *
   * `#coachCard`, `#joinBox` and `#myGamesBox` carry `grid-column` / `grid-row` placements from the TWO-column
   * arrangement (see `#coachCard { grid-column: 1 / -1; grid-row: 1 }` and the two below it), and `#joinBox` and
   * `#myGamesBox` are pinned to columns 1 and 2. Setting the grid to one column does NOT move them: an item placed
   * at LINE 2 creates an implicit second track, so the grid kept two columns of 118px and 274px and the coach card -
   * spanning `1 / -1`, which in a one-column grid is just column 1 - was **119px wide with its text wrapping one
   * word per line**.
   *
   * Forcing `grid-template-columns: minmax(0, 1fr) !important` on the live element did NOT widen it, which was the
   * measurement that proved the columns were not the constraint: the PLACEMENTS were. So the placements are cleared
   * here, and every card is told to span the lot.
   *
   * This is also what Colin asked for in the end - *"it doesn't even have to overlap just squash it back into the
   * dark green left panel"* - and it settles the 119px card, the Join button's overflow and the dead space on the
   * right together, because a card that fills its column has room for all three.
   */
  #screen-lobby .modes { grid-template-columns: minmax(0, 1fr); }
  #screen-lobby .modes > * {
    grid-column: 1 / -1;
    grid-row: auto;
  }}
#screen-lobby .panel > p.muted { margin: 0 0 10px; }
#screen-lobby .modes { margin-top: 10px; gap: 12px; }
#screen-lobby .mode label { margin-top: 8px; }
/* The guest's own copy of the join box is a direct child of the card, not a cell of the grid. */
#screen-lobby .panel > #joinBox { margin-top: 10px; }
#screen-lobby .mode button, #screen-lobby .mode select, #screen-lobby .mode input,
#screen-lobby .joinrow button, #screen-lobby .joinrow input { padding-top: 7px; padding-bottom: 7px; }

/* THE BAR OFF THE FLOWING SCREENS.
 *
 * This was written for the history PAGE, which no longer exists - the history is a card on the game screen
 * now (see below) - and the rule itself outlived it because it was never really about history:
 *
 *   THE BAR WAS SITTING ON THE PANEL. Once an account has arranged its screen the bar is a WINDOW -
 *   `position: fixed` at its saved rectangle - so a page that flows begins at the top of the viewport: the
 *   bar occupies 0-65, `main` begins at 0, and the bar covers the first 43px of whatever is under it.
 *   Colin: *"Coldfooty is covering a lot of the history card stuff."* The GAME screen needs none of this
 *   because windows.js positions its cards; every other screen flows underneath the bar.
 *
 * `--bar-h` is already measured and published by `measureBar()` in app.js on every layout pass, and it is
 * the height DIVIDED BY the zoom - so adding it to padding inside the zoomed body comes out right at any
 * text size, and there is no second number to keep in step.
 *
 * THE SECOND RULE HERE IS GONE WITH THE PAGE IT WAS FOR: `body:has(#screen-history:not(.hidden))` widened
 * the column to 64% so six columns of season record fitted on one line. Nothing can match it any more - the
 * element was deleted - and the card has its own width in `#historyCard` below.
 */
body.layout-custom main:not(:has(#screen-game:not(.hidden))) {
  padding-top: calc(var(--bar-h, 59px) + 22px);
}

/* ---- the history card ------------------------------------------------------
 * WHAT USED TO BE A WHOLE SCREEN. Colin: *"we probably don't even need to have a whole history window if
 * we've got a card that we can bring into the game page ... we can just bring it in with a tick in the View
 * window. Then we don't need to have any special history page."* So it is a card like the fixture and the
 * ladder - movable, resizable, put away - filled from `#historyBody` exactly as the screen was.
 *
 * EVERYTHING HERE IS SCOPED TO #historyCard AND ITS OWN CLASSES, deliberately, the same rule the shot
 * timing card follows above: a shared rule changed to suit one card is how a card nobody was looking at
 * breaks. It borrows `table.history` on purpose - that IS the table's own style, and the card is now the
 * only place it is drawn.
 *
 * THE CARD IS NOT THE SCROLLER. The title bar and the eight resize handles are children of the card, so
 * anything the CARD scrolls takes them with it. Colin: *"this history card needs to have a scroll bar
 * obviously, because these seasons are going to get deeper and deeper and deeper."* The seasons scroll in
 * `.history-scroll` inside the card instead. */
#historyCard {
  display: flex;
  flex-direction: column;
  min-height: 120px;
  overflow: hidden;
  /* More room at the top than a plain panel: in arrange mode the card's own title bar is an absolute
     overlay across its top edge, and the heading must start below it. */
  padding: 20px 0 0;
}
#screen-game .col-side > #historyCard { flex: 0 1 auto; }
.historyhead {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 0 12px 8px;
  flex: 0 0 auto;
}
/* THE WRAPPER IS THE SCROLLER. `min-height: 0` is what lets it shrink inside the flex column at all;
   without it the card refuses to go below its content and the overflow comes back to the card, which is
   the fault this exists to prevent. */
.history-scroll {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
  padding: 0 12px 10px;
}
/* The table fills the card, so it does not need the margin it carried on a page. */
#historyCard table.history { margin-top: 0; }
body.has-bg-image:has(#screen-game:not(.hidden))::before { inset: 0; }
body.has-bg-image:has(#screen-game:not(.hidden))::after {
  left: 0;
  right: 0;
  width: auto;
  background: rgba(11, 31, 20, calc(var(--bg-image-dim) + 0.44));
}

/* Both side panels fit the window and scroll INSIDE themselves, so the page
   itself never scrolls - everything on one screen, which is the whole point of
   this layout. Without this the ladder (19 rows) pushed the page down and a
   scrollbar appeared even though the controls were visible.
   Kept in step with the game screen's own padding (59px top bar + 8 + 10) and
   with .panel.grow, so all three cards come out the same height. */
/* A DEFINITE HEIGHT, NOT A MAXIMUM. The fixture divides this height between its
   rows so the whole draw fits, and `1fr` rows can only divide a height that is
   actually definite: with max-height the panel's height came from its contents,
   the rows fell back to their natural size, and at 200% text the fixture showed
   six rounds and scrolled for the rest. `height` also keeps all three cards the
   same height, which is what the max-height was for. */
.grid > aside.panel {
  height: var(--card-h);
  overflow: hidden;
}
/* The generic "one panel under another gets a gap" rule was pushing the middle
   panel 16px down the page and off the bottom of the window. In the grid the
   panels are side by side, so there is no gap to add. */
.grid > .panel + .panel, .grid > .panel + .col-side { margin-top: 0; }

/* A DEFINITE height, not a maximum: the nine games split whatever height the
   window has between them and the dials take their size from the row, so the
   panel is always full and the last game is never scrolled off. */
.panel.grow {
  display: flex;
  flex-direction: column;
  /* 59px top bar + the game screen's own 8px top and 10px bottom padding. It has
     to be kept in step with that padding by hand - it is the one place the two
     numbers meet. */
  height: var(--card-h);
}
/* THE FINALS SCROLL INSIDE THE CARD, exactly as the round's games do. Without this
   the finalists and the whole bracket ran on past the card's own border - Colin:
   "the data the text is on the outside of the card". */
.panel.grow > #finalsBox:not(.hidden) {
  flex: 1 1 auto;
  min-height: 0;
  overflow: auto;
}
/* A FINALS WEEK IS PLAYED ON THE ROUND CARD, so during the finals this card holds
   TWO things at once: the week being played (the grid, its dials and its reveal) and
   the bracket below it. They used to take turns - the finals box REPLACED the grid -
   which is why an even split of the card is wrong for both: a finals week is one to
   four games, and the bracket is the long one.
   So the grid takes the height it actually needs (measured: 108px for a two-game
   week, against 871px of card) and the bracket keeps every pixel of the rest. The
   45% ceiling is the guard for a week of four games at 200% text: past that the grid
   scrolls its own list instead of pushing the bracket out of the card. */
.panel.grow:has(> #finalsBox:not(.hidden)) > #liveScores:not(.hidden) {
  /* NO SHRINK EITHER: the bracket is the one that gives way. Without `0 0` the two
     children shared the shortfall out in proportion to their contents and the grid
     was squeezed to 60px of its 108 (measured), clipping the second game. */
  flex: 0 0 auto;
  max-height: 45%;
}
.panel.grow > #liveScores:not(.hidden) {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
}
.panel.grow > #liveScores #liveScoresList {
  flex: 1 1 auto;
  /* auto, not hidden: when the games cannot fit - a small card, or text at 200% -
     the round scrolls rather than losing the last few games off the bottom. */
  overflow-y: auto;
  min-height: 0;
}
/* The "quarter in progress" line sits right above nine dials; the panel h3's own
   margin was costing the games 20px they need. */
#liveScoresTitle { margin: 2px 0 6px; font-size: 14px; }

/* The right column: ladder on top, points box underneath, both inside the same
   one-screen limit. The ladder gives up height first and scrolls; the points box
   is `flex: none`, so the numbers and the buttons are ALWAYS on screen - having
   to scroll to reach the buttons that play the game was, fairly, called out. */
.col-side {
  display: flex;
  flex-direction: column;
  gap: 12px;
  /* A definite height, not a maximum. With max-height the column stopped at its
     content, so the ladder sat at its natural size and left the rest of the
     column empty - the spare height Colin wants the ladder to have was doing
     nothing at all. Kept in step with .panel.grow and the game screen's padding
     (59px top bar + 8 + 10) so the ladder card ends level with the round card. */
  height: var(--card-h);
}
.col-side > aside.panel {
  flex: 1 1 auto;
  min-height: 140px;
  /* The CARD does not scroll; the table inside it does. See .ladder-scroll. */
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
/* THE CONTENT SCROLLS, NOT THE CARD. Two reasons, both Colin's:
   "when the scroll bar is in play on the fixture card the title bar scroll too" -
   the title bar and the drag handles are children of the card, so anything the
   CARD scrolls takes them with it; and "there is no right boundary to grab in the
   fixture window when the scroll bar is there" - the scrollbar sat exactly on top
   of the east resize handle. Scrolling inside means the scrollbar is inset by the
   card's own padding, clear of the handles and under the title bar. */
/* A flex COLUMN as well as a scroller: table.ladder carries `flex: 1 1 auto` so it
   fills the card and its rows spread into the spare height - Colin's "the ladder
   wants to be bigger" - and that rule needs a flex parent. Adding this wrapper for
   the scrolling took the table out of the panel's flex flow, and the rows stopped
   spreading and sat at the top of a stretched card: "when you stretch down on the
   ladder window the text stay at the top". */
.ladder-scroll {
  flex: 1 1 auto;
  min-height: 0;
  overflow: auto;
  display: flex;
  flex-direction: column;
}
.col-side > #controlPanel { flex: 0 0 auto; padding: 6px 8px; }
/* THE POINTS BOX'S OWN TITLE BAR SITS OVER ITS TOP EDGE, so its content has to
   start below it. The bar is an absolute overlay pinned to the card's top (full
   width, deliberately - see .wingrip) and can be up to 18.75px deep once it is
   counter-zoomed, so the standard 20px of card padding already clears it. The
   POINTS BOX is the one deliberately tight card (6px, because every pixel it takes
   is one the ladder does not get), and its "Points" label and figure were starting
   INSIDE the bar. Colin: "the number of points needs to be lowered down because
   part of it is being blocked by the title bar, the points box title bar."
   Arrange mode is on by default, so this is the card's normal state; with arrange
   off there is no bar and the tight padding comes back. */
.col-side > #controlPanel:has(> .wingrip) { padding-top: 20px; }
/* The prompt and the button on one row: they say the same thing, and a row here
   is a row the ladder does not get. */
.lockrow { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.lockrow #riskLine { margin: 0; }
/* An empty line still costs height, and this one is usually empty. */
#riskLine:empty { display: none; }
/* Nothing to spend - waiting on other coaches, knocked out, or spectating - so
   the box goes away rather than sitting there empty. */
.col-side > #controlPanel:not(:has(#allocation:not(.hidden))) { display: none; }
/* THE POINTS BOX CANNOT SHIFT ITS OWN BUTTONS.
   Measured by tools/_probe-points-static.mjs, in Colin's own layout: pressing the
   same number four times slid every button 56.8px down the card - 23px when the
   "quarter is running" line appeared above them, then 33.8px when the first chip
   landed in "Picked ahead" - and with the pointer left exactly where he clicked,
   elementFromPoint came back with the label instead of the button. Colin: "the
   selection numbers drop down and I have to move my mouse again, they don't stay
   static ... you can click the same number four times without having to worry about
   it shifting a little bit."
   The ORDER of the box is the first half of the fix - the buttons come first and
   everything that grows sits below them (see the markup in index.html). This is the
   second half: the card is a fixed share of the window from the owner's own layout
   (0.335 of it here), so its column has to be told what keeps its height and what
   gives way, or content simply slides down the card and out of the bottom. */
.col-side > #controlPanel { display: flex; flex-direction: column; }
/* THE CONTENT SCROLLS, THE CARD DOES NOT - the points box's `.ladder-scroll`.
   Colin, with the Force button on the card: *"you've got text at the bottom information that should
   be inside the background of the card, because it doesn't fit in the background that's been selected
   the scroll bar should come into play and it's not doing it."* And on the fix: *"if you're going to
   have a button and it's going to push it past the existing background, well then the sidebar has to
   come into play."*
   The card itself must never be the scroller: the title bar and the eight resize handles are its own
   children, so anything the CARD scrolls takes them with it - the trap Colin already hit on the
   fixture card ("when the scroll bar is in play on the fixture card the title bar scroll too"), which
   is why the ladder scrolls a wrapper and not itself. This is that same wrapper for the points box.
   `min-height: 0` is what lets it actually shrink inside the flex column; without it the box refuses
   to go below its content and the overflow comes straight back. */
.col-side > #controlPanel > .controls-scroll {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
}
.col-side > #controlPanel > #allocation {
  display: flex; flex-direction: column; min-height: 0;
}
/* The figures, the buttons, the running/held line and the "waiting on others" row
   keep their height no matter how many numbers are queued. */
.col-side > #controlPanel .poolbox,
.col-side > #controlPanel #nominateBox,
.col-side > #controlPanel #quarterRunning,
.col-side > #controlPanel #roundHeldNote,
.col-side > #controlPanel #byeNote,
.col-side > #controlPanel #sackedNote,
.col-side > #controlPanel #waitingForOthers { flex: 0 0 auto; }
/* THE FORCE BUTTON IS SMALLER THAN A PRIMARY BUTTON.
   Colin: *"that big gigantic green button ... I don't think it needs to be that big."* Every button
   in the app carries `padding: 11px 16px` and inherits the page's 15px type, which is right for a
   button on a form and far too big for one sitting inside the points box - the card is a narrow
   column, so the sentence wrapped to three lines and took about 90px of a card that has none to
   spare, which is what pushed the rest of the box out of the bottom in the first place.
   Scoped to THIS button by id, so no other button anywhere changes size. */
#resolveRound {
  padding: 7px 10px;
  font-size: 12px;
  line-height: 1.25;
  margin-top: 4px;
}

/* THE SACK BOX IS A BUBBLE NOW, so it takes no height on the card at all.
   It used to be a scrolling list - one button per coach the host can take a club off, up to 18 in a
   full competition - and that scroll was one of the two Colin could see inside the points box. Its
   sentence and its buttons both live in the bubble under the "Sack a coach" button (see
   .bubblepanel), which is what he asked for: "can we get all that information to be in a bubble
   ... so we don't have to have text everywhere". */
.col-side > #controlPanel #sackBox {
  flex: 0 0 auto;
}
/* The sack buttons WRAP: a row per coach, and each says who and which club. */
#sackButtons { display: flex; flex-wrap: wrap; gap: 6px 10px; }
#sackButtons:empty { display: none; }
/* A SACK CONTROL IS DELIBERATELY QUIET AND DELIBERATELY NOT A PRIMARY BUTTON.
   It is irreversible for the season, so it must not look like the Play button:
   a plain outline, the warning colour on hover only, and no filled state. There
   is also a confirmation in front of it (see sackCoach in app.js) - the colour
   is the second line of defence, not the first. */
.sackbtn {
  background: transparent; color: var(--muted);
  border: 1px solid var(--line); border-radius: 6px;
  font: inherit; font-size: 11px; padding: 2px 6px; cursor: pointer;
}
.sackbtn:hover, .sackbtn:focus-visible { color: var(--warn); border-color: var(--warn); }
#sackNote { margin: 2px 0 0; }

/* ---- THE HOVER BUBBLE -----------------------------------------------------
   Colin: "can we get all that information to be in a bubble - there might be a button that says
   sack a coach and then you can hover over the button and it gives you the bubble as to what
   happens - so we don't have to have text everywhere."

   One button, the words on it, and the explanation underneath the pointer. THE BUBBLE OPENS
   UPWARDS (`bottom: 100%`): the sack row sits near the bottom of the points box, so a bubble below
   it would be clipped by the card's own edge. `:focus-within` as well as `:hover`, so the keyboard
   can reach the buttons inside it - and there is no gap between the button and the panel, or the
   pointer would leave the wrapper on the way in and the bubble would shut. */
.bubblewrap { position: relative; display: inline-block; }
.bubblepanel {
  display: none;
  position: absolute;
  left: 0;
  bottom: 100%;
  z-index: 40;
  width: min(24rem, 78vw);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 8px 10px;
  box-shadow: 0 10px 26px rgba(0, 0, 0, .55);
}
.bubblepanel p { margin: 0 0 4px; }
.bubblewrap:hover .bubblepanel,
.bubblewrap:focus-within .bubblepanel { display: block; }

/* What is left is the queue, and IT DOES NOT SCROLL VERTICALLY ANY MORE - it is one row of fixed
   height (see .queuechips), so there is nothing for a vertical scroll to hold. `overflow-x` on the
   chips row looks after a queue that runs past the width; the box itself never shows a scrollbar,
   which is what Colin asked for: "we don't want scroll bars inside the points box ... so that they
   don't flip up and down with all this extra content". */
.col-side > #controlPanel #bufferBox {
  flex: 0 0 auto; min-height: 0; overflow: hidden;
}
/* IN EVERY OTHER LAYOUT TOO, the row that holds the way on keeps its height. It
   sits above the queue in the markup, so with the queue giving way instead of the
   row, the button that settles a quarter is on the card however many numbers are
   queued - and on a bye it is the only control there is. */
#waitingForOthers { flex: 0 0 auto; }
/* While the points box is up, the ladder's footnote steps aside: the ladder is
   what matters, and the footnote was costing it two rows. */
.col-side:has(#allocation:not(.hidden)) > aside.panel > p.muted.small { display: none; }

/* ---- the whole season's draw -------------------------------------------
   One CARD per round, three across - the boxes Colin drew. A round reads as a
   unit, and each card lists its nine games DOWN the card, one per row: that
   makes a card tall and thin, which is what lets the fixture be the narrow
   column and give its width to the games. */
.season-fixture {
  margin-top: 7px;
  /* The HEIGHT container, for the round cards' type: their size is measured
     against the height of the grid they are laid out in. */
  container-type: size;
  display: grid;
  grid-template-columns: repeat(var(--fx-cols, 3), minmax(0, 1fr));
  /* THE ROWS SHARE THE CARD'S HEIGHT, so all 24 rounds are in the card instead of
     the last six being below the fold. Colin: "the fixture card doesnt hold all the
     rounds in it, see picture". Four rows of three in the built-in layout, eight
     rows of three farther down - whatever the column count, the rows divide up
     whatever height there is. */
  grid-auto-rows: minmax(min-content, 1fr);
  /* The GRID scrolls, not the card, so the title bar and the drag handles stay
     where they are while the draw scrolls under them. */
  overflow: auto;
  gap: 5px;
  align-content: stretch;
  flex: 1 1 auto;
  min-height: 0;
}
/* The thresholds are below the fixture's own width in the built-in layout (318px
   at 1887), so nothing about the normal screen changes: three rounds across is
   the design there, and this only takes over once the card is genuinely narrow. */
/* HOW MANY ROUNDS FIT ACROSS IS SET BY THE CLIENT, in --fx-cols, because a
   container query could not be trusted here. Custom properties do not evaluate
   inside a @container condition, so the zoom-compensated thresholds silently never
   applied; and without that compensation the container's own size and the query's
   rem are measured in different spaces, so at 175% text the fixture declared
   itself 181px wide on a 318px card, dropped to one column and squashed every
   round card to 10px. The client measures the card in its own pixels - which no
   zoom touches - and sets the count. See renderFixtureColumns in app.js. */
/* Three cards across only works while the window is wide. Below this the fixture
   goes two across and the panel scrolls instead - a full name you have to scroll
   for beats an ellipsis you cannot read.
   The fallback is TWO, but the client's own count wins when it has run: it measures
   the real card width (including both zooms) and it is the only place that knows a
   round card also has to carry the clubs with the bye - see renderFixtureColumns.
   Hardcoding 2 here overrode that choice at every viewport under 1800px. */
@media (max-width: 1799px) {
  .season-fixture { grid-template-columns: repeat(var(--fx-cols, 2), minmax(0, 1fr)); }
}
.sf-round {
  min-width: 0;
  background: rgba(255, 255, 255, .06);
  border: 1px solid rgba(255, 255, 255, .07);
  border-radius: 6px;
  /* No vertical padding and no header margin: fixed costs inside a card that is
     being asked to scale, and they came out of the games' type. */
  padding: 0 6px;
}
.sf-round.current {
  background: rgba(53, 208, 127, .16);
  border-color: rgba(53, 208, 127, .45);
}
/* A PLAYED ROUND CAN BE CLICKED to look back at it on the round card (lookBackFor in app.js); the one on the card
   now is outlined in gold, so it never reads as the green "current" round. */
.sf-round.sf-click { cursor: pointer; }
.sf-round.sf-click:hover { border-color: rgba(255, 255, 255, .35); }
.sf-round.sf-looking {
  background: rgba(240, 190, 60, .16);
  border-color: rgba(240, 190, 60, .7);
}
/* ⚠ THE ONE LINE AT THE TOP OF A NIGHT SERIES DRAW, and it REPLACES fifteen repetitions of the same fourteen
   club names (see nightDrawNote in app.js). A knockout round holds one game and leaves fourteen clubs out, so
   drawing the byes put 92px of non-players above a 12px game on every row - the match the card exists to show
   was the smallest thing on it. The rule is stated once instead.
   Muted and small on purpose: it is a caption for the draw, not part of it, so it must not compete with the
   first round heading underneath. It wraps freely, because it is a sentence. */
.sf-note {
  margin: 0 0 6px;
  font-size: clamp(calc(9px / var(--win-scale, 1)), .95cqh, 14px);
  line-height: 1.35;
  color: var(--muted);
}
.sf-r {
  display: block;
  margin-bottom: 0;
  /* THE ROUND'S OWN TITLE, and the floor is 11px to the games' 9.
     Colin, asked to choose between all 24 rounds in view and readable type: "raise
     the fixture's minimum type to 9px - the fixture scrolls for the later rounds".
     So the type has a readable floor at every text setting and the DRAW scrolls
     instead of shrinking to fit. At the built-in card that shows about six rows of
     the eight; the rest is a short scroll away. */
  font-size: clamp(calc(11px / var(--win-scale, 1)), 1.11cqh, 20px);
  font-weight: 700;
  letter-spacing: .4px;
  color: #d3e2d9;
  /* Just "Round 12" now - the clubs with the bye are their own block below it (see
     .sf-bye). A round number always fits, so the ellipsis here is only a guard
     against a card dragged very narrow. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* ⚠ A NIGHT SERIES ROUND IS NAMED, NOT NUMBERED, AND ITS NAMES ARE LONGER THAN "Round 12".
   "Preliminary Final round" is twenty-three characters against a round number's eight, and the nowrap-and-ellipsis
   above - which is right for a number, because a truncated "Round 1..." is unreadable nonsense - would cut it to
   "Preliminary F...". So a named round is allowed to wrap onto a second line and keeps all of its words. The
   heading is the one thing on the card that must never be abbreviated: "Grand Final" and "Preliminary F" are not
   the same announcement. */
.sf-r.night {
  white-space: normal;
  overflow: visible;
  text-overflow: clip;
}
/* THE CLUBS WITH THE BYE, TWO TO A ROW.
   Colin, on the loose rows and the missing break: "you've got bigger spaces between
   the bye teams and little spaces between the games, so that's confusing. What you
   want to do is have little spaces between the bye teams so from row to row little
   spaces, and then your large space goes between the first game and the last of where
   the word bye is written, to distinguish between what's a bye and what's a game."
   And then, on how many names a row: "let's make the bye only have a maximum width of
   two team names and then you drop down a row, because on some occasions you've got
   team names that go four wide and that sort of stuffs up the resizing of the windows
   a bit."
   So this is a TWO-COLUMN grid, set in the same type as the games - same size, same
   1.3 line-height - which is what makes a row of bye names sit as tightly as a row of
   games. Two columns rather than a wrapped run of text means the block's height is a
   COUNT of the names (ceil((names + 1) / 2) rows) instead of a function of how many
   happened to fit across, so resizing a window no longer reflows the bye list into a
   different number of rows. `minmax(0, max-content)` keeps each column only as wide
   as its widest short name without ever overflowing a narrow card. */
.sf-bye {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, max-content));
  justify-content: start;
  column-gap: 8px;
  font-size: clamp(calc(9px / var(--win-scale, 1)), .87cqh, 17px);
  line-height: 1.3;
  font-weight: 500;
  letter-spacing: .2px;
  color: var(--muted);
}
.sf-bye .sf-v { font-size: .9em; }
.sf-games {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 0;
  /* THE LARGE SPACE, between the last bye row and the first game: it is what tells
     the eye where the byes stop and the round's games start. The rows INSIDE each
     block are one line-height apart with no gap at all, so this is the only break on
     the card - measured: bye rows 11.7px apart, and the first game 6px clear of the
     bye block, i.e. a 17.7px step where every other step is 11.7px. Colin: "your
     large space goes between the first game and the last of where the word bye is
     written, to distinguish between what's a bye and what's a game." */
  margin-top: 6px;
  /* Nine line boxes have to fit the card's height - the header plus eight games,\n     each 1.3 font-sizes tall, so 11.7 font-heights in all - AND the card's fixed\n     costs (6px of padding and border) have to come out of the same height. That is\n     an affine relationship, not a proportional one, which is why a plain cqh\n     multiplier could not fit both a tall card and a short one: 0.94cqh minus the\n     1px of fixed cost is the tight fit at every card height. */
  font-size: clamp(calc(9px / var(--win-scale, 1)), .87cqh, 17px);
  line-height: 1.3;
}
.sf-g { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sf-v { color: var(--muted); }
/*
 * THE RESULT OF A PLAYED GAME, BESIDE THE CLUBS.
 *
 * Colin: *"we're going to put the results on the fixture as it gets played ... we'll keep the fixture with the
 * little totals numbers right next to the name and we'll underline the winner"*, written out as
 * `GC 123 v SYD 75 with the winner bold and underlined in the same font color`.
 *
 * THE NUMBERS ARE TABULAR, so a column of totals lines up on the units - which is what makes a fixture of
 * results scannable rather than a jumble. And NOTHING IS RECOLOURED: *"the same font color"* is explicit, so
 * the winner is marked with weight and a rule and keeps the club colour it already had.
 *
 * ⚠ `text-underline-offset` IS `auto` AND THAT IS THE WHOLE FIX FOR A REAL FAULT. It used to be `2px`, and at
 * this card's 9px type the whole reason the rule exists is the fixture's own `overflow: hidden`: a decoration
 * pushed 2px below the baseline is drawn outside the line box, and an inline box that ends where its glyphs end
 * CLIPS IT. Measured on his own finished season, cell by cell, from the screenshot's pixels: with `2px`,
 * **33 of 207 winning club cells painted any underline at all** - the rest were clipped away, which is exactly
 * what he reported: *"you have only underlined two sometimes 3 instead of all nine of the winners."* With
 * `auto`, **204 of 207**. The three that still would not read are the three `GC` cells, whose club box is
 * **11-12px wide**: a 1px rule across an 11px box is what the threshold could not resolve, not a rule that is
 * missing.
 *
 * THE LESSON, because it cost an hour and it is not obvious: `text-decoration-line: underline` was COMPUTED AND
 * CORRECT on every one of those cells the whole time, and so was `text-decoration-color`. Only the pixels knew.
 * A probe that asks the DOM whether text is underlined cannot see this fault - see `_check-fixture-underline.mjs`,
 * which asks the screenshot instead.
 *
 * THE MARK GOES ON THE CLUB AND ITS TOTAL TOGETHER, because the total is what won it - underlining the name
 * alone reads as "this club is notable" rather than "this club won 123 to 75".
 *
 * `.sf-g` above still refuses to wrap and ellipsises instead; a game line that cannot fit its own column is
 * caught by the probe rather than left to look fine on one machine and truncated on another.
 */
.sf-t { font-variant-numeric: tabular-nums; }
.sf-won { font-weight: 700; text-decoration: underline; text-underline-offset: auto; }
/* Club names carry their club's colour inline (see CLUB_COLOUR in app.js). The
   player's own club is bold as well - knowing which one is you matters more than
   the colour does. */
.club-me { font-weight: 700; }
/* The round grid - every game, each side's score after each quarter, then the
   total: the shape the 2003 program drew. Column widths are fixed so the four
   quarters line up down the whole round, header included. */
ul.results li.qz-head-li {
  background: none; border: 0; border-radius: 0; padding: 0 14px 4px; margin: 0;
}
.qz-head, .qz-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) repeat(4, 52px) 58px;
  gap: 6px;
  align-items: baseline;
}
.qz-head {
  font-size: 11px; text-transform: uppercase; letter-spacing: .8px; color: var(--muted);
}
.qz-row { font-size: 13px; padding: 2px 0; }
.qz-game { padding: 10px 14px; }
.qz-team { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.qz-q { text-align: right; color: var(--muted); font-variant-numeric: tabular-nums; }
.qz-total { text-align: right; font-weight: 700; font-variant-numeric: tabular-nums; }
.qz-row.win .qz-team { color: var(--accent); }
.qz-row.loss .qz-team { color: var(--danger); }
.qz-row.draw .qz-team { color: var(--warn); }
/* THE SAME ONE-POINT BUMP ON THE ROUND GRID, for the same reason and with the same pinned line box.
   The coach's own club's name and his row's TOTAL go 13 -> 14px; the total is the bold number here
   (the four quarter cells beside it are muted, not bold), which is what Colin was describing when he
   said *"I assume the score is also in the bold as well"*. 13 x 1.5 = 19.5px is the line box every row
   already has, so pinning it to that keeps all nine lines - the heading and the eight games - exactly
   as tall as they were and the round card cannot grow its scrollbar back.
   `:has()` picks his row wherever this grid is drawn (the round card, the results, the finals), because
   the coach's marker is the `.club-me` span INSIDE the team cell rather than a class on the row. */
.qz-team .club-me { font-size: 14px; line-height: 19.5px; }
.qz-row:has(.club-me) .qz-total { font-size: 14px; line-height: 19.5px; }
.tiny { font-size: 13px; color: var(--muted); list-style: none; padding: 0; }

.premier { color: var(--accent); font-size: 22px; }

/*
 * A ROW OF THE BRACKET LIST, SIZED AND PLACED TO HIS HAND.
 *
 * Colin: *"I want them to be a bigger size font and I want them centered inside that dark green background thing
 * that you've got them sitting in - not left justified, put him in the center. You know a slightly larger font -
 * now with a slightly larger font I don't mean let now it won't fit on the screen, you know just lose some of
 * the padding or margin in the top and bottom of those green backgrounds on each game."*
 *
 * THREE THINGS, AND THE ARITHMETIC THAT KEEPS THE CARD FROM GROWING:
 *
 *   1. BIGGER TYPE - the clubs go to 16px against the app's 15px body, where they were inheriting 13px, so a
 *      result line reads as the main thing on the card rather than as small print;
 *   2. LESS PADDING, deliberately, to pay for it: 10px top and bottom becomes 5px. **MEASURED ON HIS OWN CARD:
 *      a row is 32px where the same row at the old padding and the old 13px type is 40px** - the bigger type
 *      costs 4.5px of line box and the padding gives back 10px, so the row comes out SHORTER than it was and
 *      the eleven-match bracket has room to spare rather than less. That is the whole of *"I don't mean let now
 *      it won't fit on the screen"*. (16 x 1.5 = 24px of line and 5 + 5 of padding is 32, which is the sum
 *      done the other way round; `_check-finals-card.mjs` prints both numbers and fails if the new row is the
 *      taller one.)
 *   3. CENTRED - the CLUB LINE sits in the middle of its own green row. The row is a three-column flex with
 *      the flexible middle (`.fs-teams`), so the line that carries the game is centred between the short label
 *      on the left and the winner on the right, and those two stay on their own edges. That is the shape he
 *      asked for - "not left justified, put him in the center" - with the two anchors this card needs, since
 *      the label and the winner are what the eye runs down the bracket to find. **HE CHOSE THIS OVER CENTRING
 *      ALL THREE TOGETHER** when asked which he meant.
 *
 * THE COLOURS ARE ON THE NAMES, ONE SPAN EACH, so the middle column's own alignment is what places them and
 * nothing here depends on how long a club's name is.
 *
 * 16px IS ALSO THE HALF OF A PAIR: the row's own line box is 1.5 (the body's), so 16px is a 24px line and the
 * pattern of the app's other result lines is kept. Past about 19px a long line of two club names, two
 * scorelines and a winner starts to be truncated by its own card, which is the thing `fs-teams` ellipsises
 * rather than wraps - and the probe watches that flag.
 */
.finals-match {
  display: flex; align-items: center; gap: 12px;
  padding: 5px 12px; background: var(--panel-2); border: 1px solid var(--line);
  border-radius: 8px; margin-bottom: 8px;
}
.finals-match .label { color: var(--muted); font-size: 11px; text-transform: uppercase; letter-spacing: .6px; min-width: 42px; }
/*
 * A PLAYED FINALS MATCH, AS A RESULT LINE.
 *
 * Colin, first: *"put it in a nice format where it's going to say ... Tasmania versus Footscray, TAS 13 9 87,
 * Footscray 18 15 123. And the teams are all in a white font ... the winner in the font colour of the club,
 * because there's room for that to fit into that area."*
 *
 * Then, reading this card: *"all of the font color is white - we can't have that, it should be in the colors of
 * the club's"*. So EACH CLUB'S NAME now carries its own club colour (`.fs-club`, written inline by
 * `renderFinals` out of `CLUB_COLOUR`), the SCORELINE stays in the card's own plain colour, and the winner out
 * at the right keeps the club colour it always had. The numbers are tabular so they line up down the card, and
 * set a shade softer than the club name so the name still reads first.
 *
 * `.fs-teams` REFUSES TO WRAP AND ELLIPSISES INSTEAD, like every other result line in this app: a line that
 * cannot fit its card is caught by the probe rather than left to look fine on one machine.
 *
 * `text-align: center` IS NOT HOW THIS IS CENTRED - the flex row is (see above). It is here so that if the line
 * ever does have to ellipsise, the overflow is cut from both ends rather than from the right only.
 */
.finals-match .fs-teams {
  flex: 1 1 auto; min-width: 0; text-align: center;
  font-size: 16px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.finals-match .fs-num { font-variant-numeric: tabular-nums; color: var(--text); }
/* THE WINNER, name and score: bold and one size up (Colin, 5 Oct 2026). */
.finals-match .fs-won { font-weight: 700; font-size: 18px; }
.finals-match .fs-winner { white-space: nowrap; }
/* THE VENUE DEDUCTION, MARKED WHERE IT HAPPENS. The score variables card shows the strength that goes into
   the draw; when the visitor is a number short that is the reason, and a small red-amber mark beside the
   number says so rather than leaving the owner to wonder why it is lower than what the coach entered. */
.venue-short { color: var(--warn); font-size: .85em; font-weight: 600; }
/* THE HEADING OVER A FINALS WEEK'S ROWS - and it NAMES THE ROUND, not the week number: "Wild Card Round",
   "Qualifying & Elimination Finals", "Semi Finals", "Preliminary Finals", "Grand Final". The names are
   `FINALS_WEEK_NAMES` in app.js, and the week number they replaced is kept on the element as `data-week`.
   Colin, reading this card: *"it says week one instead of saying wild card round ... so they've got to be
   fixed."* The type is left as it was - he did not ask for these, and a heading that shouts over the rows it
   is heading is worse than a quiet one. */
.weekhead { font-size: 12px; text-transform: uppercase; letter-spacing: .8px; color: var(--muted); margin: 16px 0 8px; }

/* ---- Welcome screen: the sign-in cards, with the photograph beside them ----
   The photograph is a real ELEMENT here, not a background on the page. That is
   the whole trick: anything inside it is ON the photograph by construction, so
   "title in the sky" and "copy on the grass" become ordinary flex alignment
   instead of plotted coordinates.

   This replaced a version that carried the picture as a background on a fixed
   layer and then tried to PLOT the words onto it with a hand-typed offset. That
   could not work: the picture is contain-scaled and right-anchored, so its
   edges move with the window, and a typed number does not. */
.auth-stage {
  display: flex;
  flex-direction: row;
  align-items: stretch;
  gap: var(--col-gap);
  width: 100%;
  /* NOT capped and centred any more. The 1560px cap left ~194px of dark page
     down the right-hand side and the picture floating short of the edge; the
     picture now runs to the edge like a wallpaper. The cards stay a fixed width
     on the left, so nothing but the gap between them and the picture changes. */
  max-width: none;
  margin: 0;
  padding: 4px 0 4px;
  /* Fill the window. The photograph used to be only as tall as the stack of
     cards beside it - `align-items: stretch` on a row whose height came from
     the cards - which capped it at about 680px and left it floating in dark
     space like a framed picture. Given the height, the picture is sized by the
     width it has (see .photo), so it now reads as a wallpaper.
     THE 44px IS THE PAGE'S OWN OVERHEAD, and it is NOT the bar: what the stage has to leave room for is
     the 22px above it and the 22px below it that the layout already spends. Measured by
     tools/_check-front-door-fit.mjs, which prints both paddings, the stage, the photograph and the card
     stack.
     ⚠ AND THE BAR IS UP ON THIS SCREEN NOW, so the stage gives up its height as well, read from `--bar-h`
     rather than guessed - the same rule the cards' height follows (`--card-h`). Colin asked for the bar back
     here: *"yeah that sounds much better - put it back"* - the price is 53px of the photograph's frame, and
     what it buys is New season and History being reachable from the very first screen.
     ⚠ AND THE PADDING BELOW THE STAGE WAS WORTH 18px OF SCROLL. At Colin's 1887x951 the front door came out
     969px - eighteen over - and none of it was the bar: the stage was being driven by its CONTENT (the three
     cards, 889px) plus 36px of its own padding. The bottom padding is the part that does nothing except push
     the page over, so it is 8px rather than 30. */
  min-height: calc(100vh - var(--bar-h, 53px) - 44px);
}

/* The three sign-in cards: a fixed, comfortable width, stacked, on the left.
 *
 * ⚠ `max-width` IS THE OTHER HALF OF THE GUARANTEE ABOVE. The column is a fixed 380px and `--ui-col` is now never
 * allowed below that plus the page padding - but a fixed basis on a FLEX item is a demand, not a limit: on a window
 * narrower than the column the item would still ask for 380 and push past the panel. Capping it against the panel
 * means the two agree by construction rather than by arithmetic that happens to work out. */
.cards {
  flex: 0 0 var(--cards-w);
  max-width: var(--ui-col);
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

/* WHO IS SIGNED IN, as the first thing on the left-hand column. Colin: "if you've already
   logged in and you do go all the way back to the home page it should say you're signed in
   at the top left hand side it should be the first bit of information up there signed in
   as:". Accent-coloured name against muted wording, so the session reads at a glance
   without competing with the card headings under it. */
/* THE SESSION LINES on the home page - who is signed in, and which tier they are on.
   NOT `.whoami`: that class is the top bar's cluster of Owner / History / Sign out buttons
   (see the rules near .ball), and borrowing it laid these three lines out as flex rows with
   the bar's gaps. They are ordinary paragraphs, one under the other. */
.sessionline { margin: 0; font-size: 13px; color: var(--muted); }
.sessionline strong { color: var(--accent); }
/* THE PAID MARKER. Small, quiet and impossible to mistake for part of the sentence: the
   tier line reads "Tier: Paid member [paid]", and the badge is the thing that catches the
   eye on a screen whose other lines are muted. */
.tierbadge {
  display: inline-block;
  margin-left: .35rem;
  padding: 0 .4rem;
  font-size: .6875rem;
  font-weight: 700;
  letter-spacing: .5px;
  text-transform: uppercase;
  color: #08150f;
  background: var(--accent);
  border-radius: 999px;
  vertical-align: 1px;
}

/* ---- THE ALL-TIME BOARDS ---------------------------------------------------
   One section per board, one row per season or career. The rank sits in a fixed-width gutter
   so the figures line up down the list, which is what makes a board readable at a glance. */
.board { margin-top: 22px; }
.board h3 { margin: 0 0 2px; font-size: 15px; color: var(--accent); }
.board > p { margin: 0 0 6px; }
.boardlist { list-style: none; margin: 0; padding: 0; }
.boardrow {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding: 7px 0;
  border-top: 1px solid var(--line);
}
.boardrank {
  flex: 0 0 1.6rem;
  text-align: right;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.boardwho { flex: 1 1 auto; min-width: 0; line-height: 1.5; }
.boardline { color: var(--text, #e6ece9); }
.bclub, .bscore { white-space: nowrap; }
.bgf { display: inline-block; margin-top: 2px; }
.bflag { color: var(--accent); font-weight: 700; }
.bflags { list-style: none; margin: 4px 0 0; padding: 0 0 0 4px; }
.bflags li { padding: 3px 0; }
.linkbtn { margin-left: 8px; padding: 1px 9px; font-size: 12px; border: 1px solid var(--line); border-radius: 6px; background: transparent; color: var(--accent); cursor: pointer; }
.linkbtn:hover { background: rgba(255,255,255,.08); }
.hostofferbar { position: fixed; top: 0; left: 0; right: 0; z-index: 9998; display: flex; gap: 12px; align-items: center; justify-content: center; flex-wrap: wrap; padding: 10px 16px; background: #3a2f0b; border-bottom: 2px solid var(--warn, #e0b030); color: #fff; }
.hostofferbar button { padding: 6px 14px; border-radius: 8px; border: 1px solid var(--line); background: #1d2421; color: #fff; cursor: pointer; font-weight: 600; }
.hostofferbar .hostofferbtns { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; }
.hostofferbar .hostoffernote { flex-basis: 100%; text-align: center; font-size: 12px; opacity: .85; }
.hostofferbar button.hostofferleave { border-color: #c0504d; background: #3b1a19; }
.hostofferbar button.primary { background: var(--accent); color: #08150f; border-color: var(--accent); }
.streakpop { position: fixed; inset: 0; z-index: 9999; background: rgba(0,0,0,.6); display: flex; align-items: center; justify-content: center; }
.streakbox { background: #161b19; border: 1px solid var(--line); border-radius: 12px; padding: 16px 20px; max-width: min(560px, 92vw); max-height: 86vh; overflow: auto; }
.streakbox h3 { margin: 0 0 2px; }
.streaklist { margin: 8px 0 14px; padding-left: 0; list-style: none; }
.streaklist li { padding: 6px 0; border-top: 1px solid var(--line); }

/* THE COMPUTER NOTE on the history table: amber, the colour the game already uses for "read
   this, it is not what it looks like" - a flag nobody actually won. */
.hist-note { color: var(--warn); font-size: 12px; }

/* ---- The photograph --------------------------------------------------------
   Given the space to the RIGHT of the cards, so the two can never overlap and
   nothing has to be worked around. It is a size container, which lets the
   picture's box be stated in terms of the space actually available (cqw / cqh)
   rather than the viewport - exact even when a scrollbar is present. */
.photo-stage {
  flex: 1 1 auto;
  min-width: 0;
  container-type: size;
  display: flex;
  /* NO HEIGHT OF ITS OWN, and pinned to the top of the line: the picture inside
     is exactly one viewport tall, so this block never sizes the flex line. If it
     does, the stack of cards stretches to match the picture and the page ends up
     40px taller than the window - a scrollbar on a screen that has none today. */
  height: 0;
  align-self: flex-start;
  align-items: flex-start;
  /* BLEED. The picture is a WALLPAPER: it reaches the top, bottom and right edges
     of the window instead of sitting inside the page's frame. These cancel the
     page padding and the stage's top padding, putting the picture's top edge at
     y=0; its own 100vh height then takes it to the bottom edge.
     Colin: "increase the photo background so it looks like wall paper, it still
     looks like a photo because its not fully height, top and bottom". */
  margin-top: calc(-1 * (var(--layout-pad) + 6px));
  margin-right: calc(-1 * var(--layout-pad));
}

.photo {
  position: relative;
  /* COVER, NOT CONTAIN, AND THE HEIGHT OF THE WINDOW.
     This box used to be contain-fitted: `height: min(100cqh, 100cqw / aspect)`
     meant the height was whichever of "the space available" and "the height this
     width implies" was SMALLER, and because the photograph is wider than it is
     tall (1.36) the WIDTH always won. So a taller window never made the picture
     taller - it only grew the dark band around it, and the picture stopped short
     above and below. That is exactly what Colin was seeing. Measured before this
     at 1600x900: picture 1130x832 in an 1150x832 space, 28px dark above and 40px
     below; at 1366x768 it covered 86% of the height.
     Now the picture fills the box and crops the edges. The crop is horizontal
     whenever the photograph is the wider shape, so the welcome copy's vertical
     insets (3cqh / 5cqh, measured against the picture's own sky and grass) still
     land where they always did. */
  width: 100%;
  /* ⚠ `100vh` WAS THE WHOLE WINDOW, AND THE BAR IS UP HERE NOW. The picture is a wallpaper and it was told to be
     exactly one window tall - measured at Colin's 1887x951 that made it 951px starting 39px down the page, so
     the front door came out **990px in a 951px window** even after the page's padding was trimmed, and the
     picture was the thing setting the height rather than the cards. It gives up the bar's height now, read from
     `--bar-h` rather than guessed, exactly as `.auth-stage` does. `tools/_check-front-door-fit.mjs` prints the
     picture's own rect, which is how this was found. */
  height: calc(100vh - var(--bar-h, 53px));
  max-width: none;
  /* Still a container, so the words inside can be inset by a percentage OF THE
     PICTURE rather than of the viewport. */
  container-type: size;
}

.photo img {
  display: block;
  width: 100%;
  height: 100%;
  /* The box already has the picture's shape, so this fills it exactly. `cover`
     only guards against a file whose real shape differs from the measured one. */
  object-fit: cover;
}

/* On the wide welcome screen the page's own background is switched OFF, because
   .photo draws the picture instead and it must not be drawn twice. Every other
   screen keeps the page background exactly as before.

   1200px is not arbitrary - but it IS arithmetic. The copy is roughly 170px tall
   and has to land in the picture's green field, which is the bottom 30% of the
   picture's height. The picture is contain-fitted into the space right of the
   380px cards, so that green band only reaches 170px once the window is about
   1280px wide. Narrower than that the picture is simply too small to put words
   on, and the layout stacks instead. Measured: at 1280 the green band is 180px
   and it fits; at 1200 it is 162px and the copy spills out of it.
   If the copy gets longer or shorter, this threshold moves with it. */
@media (min-width: 1280px) {
  body.has-bg-image:has(#screen-auth:not(.hidden))::before { display: none; }
}

.card {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 16px 18px;
}
/* THE FRONT DOOR'S CARDS ARE TIGHTER, and only that screen's: three of them stacked plus the bar has to fit
   one window (see the note over `main:has(#screen-auth…)`). 12px of padding still reads as a card. */
#screen-auth .card { padding: 12px 18px; }
/* The reserved line under each form, trimmed on the same screen for the same reason: three of them at 16px
   plus their margins is 66px of the page held for messages that are usually not there. */
#screen-auth .card .error { min-height: 13px; margin-top: 4px; }
/* ==========================================================================================
 * ⚠ THE CARD HEADINGS ARE ONE THING: CAPITALS, ONE COLOUR, ONE SIZE.
 *
 * Colin: *"you got 'I have an account' which has got a white font, whereas the one above it 'your games' has got a
 * green font and the one below it 'I'm new here' has got a white font. Can't we make them capitals those
 * headings?"*
 *
 * MEASURED on the live front door: `Your games` was `rgb(53,208,127)` (the accent) and the other two were
 * `rgb(238,242,240)` - because `card-guest` has its own `h3 { color: var(--accent) }` rule and the plain cards do
 * not. Three cards, two colours, and the odd one out was the one carrying a special case.
 *
 * SO THE HEADINGS COME FROM ONE RULE and the special case is gone. Capitals, the muted label colour the rest of the
 * game's section headings already use, so they read as headings rather than as three different announcements.
 * `text-transform` rather than retyping the markup, so the words in index.html stay readable.
 * ========================================================================================== */
.card h3 {
  margin: 0 0 6px;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: .8px;
  text-transform: uppercase;
  color: var(--muted);
}
.card label { margin-top: 8px; }
.card input[type=text], .card input[type=password] { margin-top: 4px; padding: 9px 11px; }
.card .error { min-height: 16px; margin-top: 6px; }
/* ⚠ NOT `button.wide` ANY MORE: the front door's buttons stopped carrying that class when they were sized to
   their words (see the criterion note above), so this has to name what it actually means - the action button at
   the foot of a card - or "Log in" would sit flush against the password box. */
.card > button { margin-top: 12px; }

/* ==========================================================================================
 * ⚠ THE FRONT DOOR IS SIZED TO ITS WORDS, NOT TO ITS CARD. THE CRITERION, WRITTEN DOWN.
 *
 * Colin, on the home and sign-in screens: *"We go back to the home page, return to your game button too wide, login
 * button much much much too wide, Create my account too wide, your name field too wide."* He then asked the fair
 * question behind it: *"what precisely criteria do you use to decide how wide a button's gonna be?"* The honest
 * answer was that there had been NO criterion - the cards carry `width: 100%` from whoever wrote them first, and
 * every control inherited it. Measured, `.cards` is `--cards-w: 380px`, so all six controls in these three cards
 * were being drawn at exactly 342px whatever they held:
 *
 *      #doLogin    "Log in"                      342px for 77px of text    -> 266px of nothing
 *      #loginName  a coach name                  342px for 110px of text   -> 233px of nothing
 *      #regName    a coach name                  342px for 110px of text   -> 233px of nothing
 *      #doRegister "Create my account"            342px for 161px of text   -> 181px of nothing
 *      #playGuest  "Play without an account"      342px for 198px of text   -> 145px of nothing
 *
 * SO HERE IS THE CRITERION, and every number below is an instance of it rather than a taste:
 *
 *   1. A CONTROL IS SIZED TO ITS CONTENTS, NEVER TO ITS CONTAINER. A button is its own label plus 18px of padding
 *      either side, and the only thing that may widen it is a stated minimum hit size (44px).
 *   2. A FIELD IS SIZED TO THE LONGEST REALISTIC THING TYPED INTO IT - and `size` is how that is said, in
 *      characters, so the browser measures the font for us. One club name, one 6-digit code.
 *   3. FULL WIDTH IS OPT IN, AND THERE ARE THREE LEGITIMATE CASES: a table, a rule, and a LONE primary action at
 *      the foot of a form with nothing beside it. Nothing on this screen is a lone action - "Log in" sits in a
 *      form that already reads top to bottom - so nothing on it is full width.
 *   4. ANY EXCEPTION CARRIES ITS REASON IN A COMMENT, HERE.
 *
 * ⚠ AND THEY ARE NO LONGER `wide` IN THE MARKUP AT ALL. The `wide` class is still exactly what it says and is
 * still used where a lone action wants the row; these three buttons simply are not that case. The width cap here
 * is the backstop, so the fault cannot come back through a class anyone adds later.
 *
 * `font: inherit` IS A SECOND FIX IN THE SAME BREATH. A bare `<input>` does not inherit the page font, so every
 * one of these boxes measured `font-family: Arial` while its own label, its own button and the rest of the game
 * were on the `system-ui` stack. It was the only Arial text on the screen and it is why the fields looked wrong
 * as well as wide.
 *
 * ⚠ AND A THIRD FIX, WHICH THE FIRST TWO MADE NECESSARY: A FIELD MUST SHOW ITS OWN PLACEHOLDER, AND THE BOX IS
 * SIZED BY THE `size` ATTRIBUTE IN THE MARKUP RATHER THAN BY A NUMBER HERE.
 *
 * Sizing these in characters first CLIPPED the words - a screenshot showed "Leave blank and you" and "At least 6
 * characte" cut off mid-word. Capping them in pixels instead then made them either too wide or too narrow, because
 * a pixel value here cannot know the font: 15rem is 212px of `system-ui` at 15px, which is not the same as 13
 * characters of it. `size` on a TEXT INPUT is a width in characters and the browser does the measuring, so the
 * criterion lives in the markup where it can be read, and this rule only stops the box overflowing its card.
 *
 * (On a SELECT, `size` means something else entirely - it draws an open list. That is why the club pickers are
 * sized here and not there; see the note on #soloTeam.)
 * ========================================================================================== */
/* ==========================================================================================
 * ⚠⚠ ONE WIDTH FOR EVERY BOX ON THIS SCREEN, AND `field-sizing: content` IS GONE.
 *
 * Colin: *"why aren't they lined up ... you got a coach's name field a totally different background colour, you
 * gotta get 'em all synchronized and unified."*
 *
 * `field-sizing: content` WAS THE CAUSE OF THE MISALIGNMENT, and it is a good illustration of why a rule that
 * sounds right can be wrong. It sizes a field to ITS OWN contents - which sounds exactly like the width criterion
 * this file adopted (§11) - but a form is not a row of independent labels:
 *
 *   * `loginName` holds "Coach name" and `loginPassword` holds "Your password", so the two boxes came out at
 *     DIFFERENT WIDTHS from each other, at left edges 236 and 293;
 *   * a field with NO placeholder - which is most password boxes at rest - had nothing to size against and
 *     collapsed to its `min-width`;
 *   * and the placeholder was never guaranteed to fit, because the box was being sized to text the browser
 *     measures differently from the way it paints it.
 *
 * THE CRITERION STILL HOLDS, and this is how it is applied here: a field is sized to the longest thing that goes
 * in it - and on a signed-in form the longest thing is a NAME, the same for every field. So every box on the
 * front door is ONE width, which is both the truthful reading of the criterion and the thing he asked for. The
 * `size` attribute in the markup is kept for the field-sizing browsers to honour on any that are left, and the
 * `max-width` here is the ceiling.
 * ========================================================================================== */
#screen-auth .card input[type=text],
#screen-auth .card input[type=password],
#screen-auth .card input[type=search],
#screen-auth .card input[type=email],
#screen-auth .card input[type=tel],
#screen-auth .card input[type=url],
#screen-auth .card input[type=number] {
  font: inherit; font-size: 15px;
  /* AS WIDE AS THE CARD, up to a comfortable reading width - the same for every field, so they line up. */
  width: 100%;
  max-width: 260px;
  min-width: 0;
  field-sizing: fixed;
}
#screen-auth .card button { max-width: 100%; }

.card-guest {
  border-color: var(--accent);
  background: linear-gradient(180deg, rgba(53,208,127,.08), var(--panel));
}
/* ⚠ THE HEADING OVERRIDE IS GONE, and that is the fix rather than a tidy-up. `.card-guest h3 { color: var(--accent) }`
   was the special case that made "Your games" green while the two cards under it were white - the inconsistency
   Colin listed first. The green edge on this card already says it is the friendly one; the heading does not have to
   say it a second time, in a different colour from every other heading on the screen. */

/* The words, laid over the picture. `inset: 0` makes this box EXACTLY the
   picture's box, so its top edge IS the sky and its bottom edge IS the grass.
   Nothing is being estimated. Space-between puts the title at one end and the
   copy at the other.

   NO BACKGROUND of any kind behind the text. A soft scrim was tried once and
   read as a translucent panel, and - sitting a little low - as a dark underline
   beneath the words. */
.photo .welcome {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  text-align: left;
  /* Insets as a percentage OF THE PICTURE, so they scale with it whatever size
     it is drawn at. The top one keeps the title inside the pale sky; the bottom
     one sits the copy in the green field. Both bands were measured from the
     picture's own pixels - tools/measure-welcome.mjs prints where each begins. */
  padding: 3cqh 0 5cqh 4cqw;
  background: none;
}
.photo .welcome .title,
.photo .welcome .copy {
  background: none;
}

/* The title sits at the top of the picture, which IS the sky. No nudge of any
   kind is needed, and there is nothing to keep in step: it is inside the
   photograph already. */
.photo .welcome .title {
  text-align: left;
}

.photo .welcome-title {
  font-size: 32px;
  margin: 0 0 12px;
  background: none;
  border: 0;
  text-decoration: none;
  /* A dark halo keeps the words readable over a pale sky without drawing a box
     or a line. */
  text-shadow: 0 1px 6px rgba(0, 0, 0, .55);
}

/* How to play. Blue-on-blue was never going to work against a sky, so it is a
   solid light pill with dark text - the only way to guarantee contrast on a
   photograph whose colour we do not control. Styled as a BUTTON, with a
   triangle marker, so it does not read as a text input. */
.help-lobby {
  margin: 0;
  padding: 0;
  /* Closed, it must HUG ITS WORDS. It used to be a 520px-wide slab of
     near-white with a five-word label floating in the middle of it, which made
     it both the biggest and the brightest thing on the screen. */
  width: fit-content;
  max-width: 100%;
  background: rgba(248, 250, 249, .92);
  border: 1px solid rgba(255, 255, 255, .5);
  border-radius: 8px;
  text-align: left;
  box-shadow: 0 4px 14px rgba(0, 0, 0, .3);
}
.help-lobby > summary {
  color: #0d2418;
  font-weight: 650;
  font-size: 14px;
  letter-spacing: .2px;
  padding: 9px 14px;
  cursor: pointer;
  list-style: none;
  display: flex;
  align-items: center;
  gap: 9px;
}
/* Our own marker: a small triangle that rotates when open. */
.help-lobby > summary::before {
  content: "";
  width: 0; height: 0;
  border-left: 5px solid #1a7f4b;
  border-top: 4px solid transparent;
  border-bottom: 4px solid transparent;
  transition: transform .15s ease;
  flex: 0 0 auto;
}
.help-lobby[open] > summary::before { transform: rotate(90deg); }
.help-lobby > summary::-webkit-details-marker { display: none; }
/* Open, it becomes a readable panel rather than a pill. */
.help-lobby[open] {
  width: auto;
  max-width: 520px;
  max-height: 46vh;
  overflow-y: auto;
  padding: 0 14px 10px;
}
.help-lobby[open] > summary {
  margin: 0 -14px;
  padding: 11px 14px;
  border-bottom: 1px solid rgba(0, 0, 0, .12);
}
/* When open, the body is dark on light. */
.help-lobby p, .help-lobby li { color: #1b2f22; }
.help-lobby h4 { color: #0f5132; }
.photo .welcome-sub { margin: 0 0 16px; font-size: 15px; }
.photo .welcome-points { justify-content: flex-start; flex-direction: column; gap: 6px; }

/* The copy sits at the foot of the picture, which IS the grass. It needs no
   nudge of any kind: it is already inside the photograph, so the foot of the
   photograph is simply where flex puts it.

   There used to be `transform: translate(var(--grass-shift-x),
   var(--grass-shift-y))` here. NEITHER VARIABLE WAS EVER DEFINED anywhere in
   this stylesheet, which makes the declaration invalid at computed-value time
   and silently collapses it to `transform: none`. That is why "across to the
   grass" did nothing, however many times it was asked for. */
.photo .welcome .copy {
  padding-bottom: 0;
}

/* ==========================================================================================
 * ⚠ THE SEMI-TRANSPARENT WASH OVER THE PHOTOGRAPH IS GONE. It was not tuned - it was REMOVED.
 *
 * WHAT IT WAS FOR. `body::after` paints `rgba(11, 31, 20, .64)` down the left-hand column, and the note above it
 * says exactly why: *"this used to be a wash that faded out to the right, because the interface was laid over the
 * photo and the photo needed dimming underneath it."* The panels are translucent
 * (`background: rgba(23,28,36,var(--bg-image-panel))` with a `backdrop-filter: blur(10px)`), and a translucent
 * panel over a photograph needs the photo dimmed under it or the text loses its contrast.
 *
 * WHY IT IS WRONG NOW. That reason expired when the layout changed. The photograph is `contain`-fitted into the
 * RIGHT-hand region (`body::before` is `inset: 0 0 0 var(--ui-col)`) and the cards live in the LEFT column, so the
 * two no longer overlap at all - which the file's own comment above `body::after` already admits: *"now that the
 * interface has its own column and the photo has the rest, there is nothing to fade."* The wash outlived its
 * reason and was left painting a translucent rectangle across a photograph nothing sits on.
 *
 * AND BELOW 1279px IT WAS ACTIVELY HARMFUL, which is where Colin saw it. The media query under this rule forced it
 * to `width: 100%`, so on any window narrower than that it lay across the ENTIRE photograph - the "stupid
 * transparent background that overlaps onto the photo" he asked to be rid of.
 *
 * The panels no longer depend on it: `body.has-bg-image .panel/.card/.mode` below are given a SOLID background, so
 * the text sits on the panel's own colour rather than on a dimmed photograph. That is also what makes the last
 * line of Colin's request work - *"that semi-transparent background that covers part of the photo"* goes, and the
 * cards stay readable because they are no longer see-through.
 * ========================================================================================== */
body.has-bg-image::after { content: none; display: none; }
/* ⚠ AND THE PANELS STOP BEING SEE-THROUGH, which is the other half of removing it. The wash existed so a
   TRANSLUCENT panel over a photograph stayed readable; a solid panel does not need dimming underneath, so the
   cards are given the same colour at full strength and the `backdrop-filter` is dropped with it. Nothing was
   relying on seeing the picture through a panel - `--bg-image-panel` is already 0.96 - so this changes the look by
   almost nothing and removes the reason for the overlay entirely. */
body.has-bg-image .panel,
body.has-bg-image .card,
body.has-bg-image .mode {
  background: var(--panel);
  backdrop-filter: none;
}

/* Narrow screens: one column. The cards come first because that is what you came
   to do; the welcome copy follows underneath. Sitting words on a photograph is a
   wide-screen idea and is switched off here. */
@media (max-width: 1279px) {
  /* Below this width there is no room for a column beside the picture, so the
     app's-column rules are switched off and everything goes back to a
     full-bleed background with the interface stacked on top of it. */
  body.has-bg-image::before { inset: 0; }
  .topbar { width: 100%; }
  main,
  main:has(#screen-auth:not(.hidden)),
  main:has(#screen-lobby:not(.hidden)) { max-width: 100%; margin: 0; }
  /* The picture is a wallpaper again here, so the stage does not need to fill
     the window. */
  .auth-stage { min-height: 0; }

  /* ONE COLUMN - and this time actually one column. The rule here used to say
     `grid-template-columns: 1fr`, which does nothing whatsoever in a flex
     container, so the layout stayed a ROW: the 380px cards plus the text column
     ran off the side of a phone (measured 173px of overflow at 414px wide). The
     comment claimed one column; the code never made one. */
  .auth-stage {
    flex-direction: column;
    gap: 22px;
  }
  /* flex-basis is a WIDTH in a row but a HEIGHT in a column, so the cards'
     380px basis would become a 380px-tall box. Reset it, and stop the cards
     stretching to the full width of a laptop once there is no picture beside
     them to share the space with. */
  .cards {
    flex: 0 0 auto;
    width: 100%;
    max-width: 460px;
    margin-inline: auto;
  }
  /* There is no room beside the cards for a picture here, so the picture element
     stands down and the page background shows it, exactly as it used to. The
     words go back into normal flow beneath the cards. The bleed margins go with
     it: below this width the picture is the page background, and pulling this
     block out of the page's padding would run the words off the edge. */
  .photo-stage {
    container-type: normal;
    display: block;
    height: auto;
    align-self: auto;
    margin: 0;
  }
  .photo {
    height: auto;
    aspect-ratio: auto;
    container-type: normal;
  }
  .photo img { display: none; }
  .photo .welcome {
    position: static;
    display: block;
    padding: 0;
    text-align: center;
  }
  .photo .welcome .title { text-align: center; }
  .photo .welcome-title { font-size: 26px; }
  .photo .welcome-points { align-items: center; }
  .help-lobby { margin: 22px auto 0; max-width: 520px; }
}

/* On a short screen, tighten the cards so nothing important falls below the
   fold. The whole point of the layout is that you can see it at a glance.
   This matters more than it looks: adding the name box to the "Just play" card
   pushed the column past the bottom of an 808-tall window, and the thing that
   fell off was "Create my account". */
@media (min-width: 1280px) and (max-height: 900px) {
  .card { padding: 11px 15px; }
  .card h3 { font-size: 13.5px; margin-bottom: 3px; }
  .card .muted.small { margin: 3px 0; font-size: 12px; }
  .card label { margin-top: 5px; font-size: 12px; }
  .card input[type=text], .card input[type=password] { margin-top: 3px; padding: 7px 10px; }
  .card > button { margin-top: 9px; }
  .card .error { min-height: 14px; margin-top: 4px; }
  .photo .welcome-title { font-size: 28px; }
  .photo .welcome-sub { font-size: 14px; }
  .auth-stage { --col-gap: 26px; padding-bottom: 18px; }
}

/* A GUEST'S ACCOUNT CLUSTER, IN THE BAR - not a banner above the panel.
 *
 * It was a full-width box at the top of the lobby, which cost the page 86px: a guest's lobby was taller than a
 * member's, so it did not fit the window, so it scrolled - and anything scrolled goes under the sticky bar.
 * Colin saw exactly that: *"look at the colfooty bar it's blocking ... there's a card behind it I can't make out
 * what it is"*, and a refresh put it right, which is what a page that fits does.
 *
 * In the bar it costs no height at all: the row is already there, and a guest has no "signed in as ... Sign out"
 * to put in it. It reads as the account it is: **guest · you are Player 28 · [Save this account]**.
 */
.guestbar { display: flex; align-items: center; gap: .5rem; font-size: .8125rem; white-space: nowrap; }
.guestbar .muted { color: var(--muted); }
.guestbar .small-btn { padding: .25rem .625rem; border-radius: .375rem; font-size: .8125rem; }
/* The in-game help, which sits on a DARK panel.
   SCOPED with :not(.help-lobby) deliberately. The welcome screen's help panel
   wears BOTH classes - `class="help help-lobby"` - and these rules carry one
   element selector MORE than the .help-lobby ones, so they silently won on the
   LIGHT panel: the body copy came out #c3ccd8 on a rgba(248,250,249,.92)
   background, `em` and .help-key came out near-white, the heading was bright
   green and the marker was the wrong glyph. Pale on pale - unreadable. */
details.help:not(.help-lobby) {
  margin-top: 22px;
  border-top: 1px solid var(--line);
  padding-top: 14px;
}
details.help:not(.help-lobby) > summary {
  cursor: pointer;
  color: var(--accent-2);
  font-size: 13px;
  font-weight: 600;
  list-style: none;
  user-select: none;
}
details.help:not(.help-lobby) > summary::-webkit-details-marker { display: none; }
details.help:not(.help-lobby) > summary::before { content: "\25B8  "; color: var(--muted); }
details.help:not(.help-lobby)[open] > summary::before { content: "\25BE  "; }
details.help:not(.help-lobby) h4 {
  margin: 16px 0 4px;
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: .6px;
  color: var(--accent);
}
details.help:not(.help-lobby) p,
details.help:not(.help-lobby) li { font-size: 13px; color: #c3ccd8; margin: 6px 0; }
details.help:not(.help-lobby) ol { margin: 6px 0; padding-left: 20px; }
details.help:not(.help-lobby) em { color: var(--text); font-style: normal; font-weight: 600; }

/* The highlighted rule. Its colour is per-theme now: the old `!important` here
   forced near-white text onto the LIGHT welcome panel, and no amount of
   specificity could override it, so the text was simply invisible. */
.help-key { border-left: 3px solid var(--accent); padding-left: 10px; }
details.help:not(.help-lobby) .help-key { color: var(--text); }
.help-lobby .help-key { color: #1b2f22; }

/* The system-owner page form. The global input style only covers text inputs,
   so the number and password fields here need their own. */
#screen-owner input[type=number],
#screen-owner input[type=password] {
  display: block;
  width: 100%;
  margin-top: 6px;
  padding: 10px 12px;
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: 8px;
  color: var(--text);
  font-size: 15px;
}
#screen-owner input[type=number]:focus,
#screen-owner input[type=password]:focus {
  outline: 2px solid var(--accent-2);
  outline-offset: 1px;
}
#screen-owner label.checkrow {
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--text);
}
#screen-owner label.checkrow input {
  width: auto;
  margin: 0;
}
#screen-owner .good { font-size: 13px; }

/* ⚠ THE OWNER PAGE USES THE WHOLE SCREEN, WITH NO PHOTO. Colin, 3 Oct 2026: "we don't need a photo in the owner page
   ... rows of columns of all those configurations instead of squashing them on the left hand side". Once it is
   unlocked the panel spans the window and the settings sit in cards across it; the locked page stays a small box. */
body:has(#screen-owner:not(.hidden))::before,
body:has(#screen-owner:not(.hidden))::after { display: none; }
/* The page column (`main#app`) is capped at about 44% of the window everywhere else, to leave room for the photo. */
body:has(#screen-owner:not(#screen-owner.hidden)) main#app:has(#ownForm:not(.hidden)) { max-width: none; padding-bottom: 0; }
#screen-owner .panel:has(#ownForm:not(.hidden)) { padding-bottom: 8px; }
#screen-owner .panel:has(#ownForm:not(.hidden)) {
  max-width: none;
  width: auto;
  margin: 12px 16px;
}
/* NEWSPAPER COLUMNS, not a grid: a grid lines its cards up in rows, so every row was as tall as its tallest card and
   the short ones left holes. Columns pack each card straight under the one above. */
#screen-owner .own-grid {
  columns: 260px;
  column-gap: 12px;
  margin-top: 10px;
}
#screen-owner .own-card {
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 10px 12px;
  display: grid;
  gap: 8px;
  min-width: 0;
  break-inside: avoid;
  margin-bottom: 12px;
}
#screen-owner .own-card .sectionhead { margin: 0; }
#screen-owner .own-card:has(#ownPauseVotes) { padding: 6px 10px; gap: 3px; margin-bottom: 8px; }
#screen-owner .own-card:has(#ownPauseVotes) .sectionhead { font-size: 11px; }
#screen-owner .pausevotes { display: grid; grid-template-columns: repeat(9, 1fr); gap: 1px 2px; }
#screen-owner .pausevotes label { display: flex !important; flex-direction: column; align-items: center; gap: 0 !important; font-size: 10px; color: var(--muted); line-height: 1; }
#screen-owner .pausevotes input[type=number] { width: 27px !important; height: 18px !important; padding: 0 2px !important; text-align: center; font-size: 12px; }
#screen-owner .own-card label { margin: 0; font-size: 13px; }
/* A number and its label on ONE line - the label, then a short box - so the whole page fits a screen. A select or a
   text box keeps the full width under its label: its options are sentences. */
#screen-owner .own-card label:has(> input[type=number]),
#screen-owner .own-card label:has(> input[type=password]) {
  display: grid;
  grid-template-columns: 1fr 76px;
  align-items: center;
  gap: 8px;
}
#screen-owner .own-card label:has(> input[type=password]) { grid-template-columns: 1fr 150px; }
#screen-owner .own-card input[type=number],
#screen-owner .own-card input[type=password] {
  margin-top: 0;
  padding: 5px 8px;
  font-size: 14px;
}
#screen-owner .own-card select,
#screen-owner .own-card input[type=text] { margin-top: 4px; width: 100%; }
#screen-owner .own-card .muted.small { font-size: 12px; line-height: 1.35; }
#screen-owner .own-card p { margin: 0; }
#screen-owner .own-wide { column-span: all; margin-top: 2px; }
/* The four hills SIDE BY SIDE along the bottom rather than stacked - stacked they were the tallest thing on the page. */
#screen-owner .own-wide .hills { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 10px; }
#screen-owner .own-top { display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
#screen-owner .own-top p { margin: 0; }
#screen-owner .own-actions { margin: 0; }
#screen-owner #ownToGame { margin: 0 0 10px; }
/* Unlocked: the title and Return to your game share one line. */
#screen-owner .panel:has(#ownForm:not(.hidden)) > h2 { display: inline-block; margin: 0 14px 8px 0; vertical-align: middle; }
#screen-owner .panel:has(#ownForm:not(.hidden)) > #ownToGame { margin: 0 0 8px; vertical-align: middle; }
#screen-owner .own-wide .hill-bars { height: 56px; }
#screen-owner .own-wide .hill-note { display: none; }
#screen-owner .own-card { gap: 6px; padding: 8px 12px; }

/* ---- THE CHAT CARD ---------------------------------------------------------
   The seventh window. Colin: "there has to be some sort of communication ... let
   the host know what's going on, and also can talk to each other." And on its
   shape: "let the information scroll away ... if it doesn't scroll away there'll
   be no room to put anything. So we're going to make it a scrolling message
   board." So there is NO fixed status line: the coaches and the game are rows in
   one log, oldest at the top, newest at the bottom, and the log is what scrolls.

   THE LIST SCROLLS, NOT THE CARD. The title bar and the drag handles are children
   of the card, so anything the CARD scrolls takes them with it - the same rule the
   ladder and the fixture follow. `overflow: hidden` on the card, `overflow-y: auto`
   on #chatList. */
#chatCard {
  display: flex;
  flex-direction: column;
  min-height: 120px;
  /* The card's normal padding would leave the input row jammed against the bottom
     edge; the stream is flush to the sides so the scrollbar is inside the card
     rather than on its border. See the title-bar note below. */
  padding: 20px 10px 10px;
  overflow: hidden;
}
/* ON THE GAME SCREEN the card sits in the right-hand column with the ladder and
   the points box: full width, and it is the card that gives way when the column is
   short (the ladder and the buttons must never be pushed off it). */
#screen-game .col-side > #chatCard { flex: 0 1 auto; }
/* IN ARRANGE MODE THE CARD GIVES ITS TOP STRIP TO THE TITLE BAR, which is an
   absolute overlay pinned to the card's top edge - the same 20px fix the points box
   carries, for the same reason. */
#chatCard:has(> .wingrip) { padding-top: 20px; }
/* THE WAITING ROOM. The cards are moved into these hosts while the lobby is up (see
   placeScreenCards in app.js), so they need the same side-by-side treatment the game
   screen's column gives them: the panel keeps its width, the cards take the rest of
   the screen, and each host has a definite height for its card to fill.
   ONE RULE PER HOST, off the class they both carry - the chat's host and the history's
   (`#chatHostWaiting`, `#historyHostWaiting`) - so a third card moved to this room needs
   no rule of its own, exactly as a third card in SCREEN_CARDS needs no function of its
   own. The chat's own sizing is unchanged, byte for byte, by being written this way. */
#screen-waiting > .chathost { display: flex; gap: 16px; align-items: flex-start; }
#chatHostWaiting > #chatCard {
  flex: 1 1 44%;
  min-width: 260px;
  max-width: 560px;
  /* A DEFINITE height, because the list inside it scrolls and a flex item with no
     definite height has nothing to scroll within. 140px is the top bar plus the
     page padding - the same budget the game screen's --card-h spends. */
  height: calc(100vh - 140px);
  min-height: 240px;
}
/* THE HISTORY CARD'S COLUMN IN THE SAME ROW, and it is the column that takes the slack:
   the ready-to-start panel keeps its own width and the seasons get what is left, because
   the card's table is the widest thing this room can hold (six columns, and the grand
   final cell alone is 260px of nowrap). MEASURED with the card laid out by this rule, on
   Colin's own 1887x951 window: the card comes out 743px wide and the six columns (682px of
   card) fit inside it without scrolling. At 150% text the room's items are laid out in a
   smaller design space and the card comes out 601px, narrower than the table - which is
   why the scroller under this rule is allowed to scroll sideways rather than clip. */
#historyHostWaiting { flex: 1 1 690px; min-width: 0; }
/* AND THE PANEL GIVES WAY RATHER THAN GROWING. It is `flex: 1 1 auto` when only the chat is
   up (that is the rule above, and it is what the room has always done); with the history
   card up the slack belongs to the card, or the panel quietly takes the width the six
   columns needed - measured before this rule: a 517px panel and a 621px card whose table
   was cut off by 65px. */
#screen-waiting:has(#historyHostWaiting:not(.hidden)) > .panel { flex: 0 1 auto; }
#historyHostWaiting > #historyCard {
  width: 100%;
  /* `min-width: 0` so a narrow room really does squeeze the card - a flex item's automatic
     minimum is its min-content width, which is the whole table, and the row would then
     spill out of the app's column instead of letting the seasons scroll (see the
     sideways-scroll rule under this one). */
  min-width: 0;
  /* THE UNITS ARE THE TRAP HERE, and this card deliberately does NOT copy the chat's
     `calc(100vh - 140px)` beside it - that one is written in the room's own design pixels
     and comes out 880px tall here, ten of them past the bottom of a 951px window.
     140px IS THE SAME BUDGET THE CHAT USES (the top bar plus the page padding, which is
     where its own height comes from), and dividing the viewport by the text size is the
     discipline the whole file follows for anything inside the ZOOMED body - see `--card-h`
     at the top of this file and `measureBar`/`measureStrip` in app.js. Measured with
     tools/_check-history-waiting.mjs at 100% AND at 150% text: the card's bottom lands 59px
     above the bottom of the window at both. */
  height: calc(100vh / var(--ui-zoom) - 140px);
  min-height: 240px;
}
/* NOTHING IN THIS CARD IS EVER CUT OFF SIDEWAYS. The game screen's card is 1319px wide by
   default and `overflow-x: hidden` there is right - the table fits. This room is narrower
   than that on any window Colin uses, and a column that is silently sliced (measured: the
   "Fourth" column, 65px past the card's edge) is worse than one the reader can scroll to.
   On his own 1887px window the table fits and this scrollbar never appears. */
#historyHostWaiting > #historyCard .history-scroll { overflow-x: auto; }
/* THE WAITING ROOM IS A ROW, AND ONLY WHILE A CARD IS UP.
   The hosts are plain divs after the ready-to-start panel, so on their own they would
   stack UNDER that panel and push the club picker, the coach list and the Start
   button off the bottom of the screen. With a card up, the section itself
   becomes the row: the panel on the left, the cards beside it. Written as a rule
   rather than left to the client, because `applyLayout` runs before the first view
   arrives and a room that reflows a moment later is exactly the flicker this
   project keeps having to fix.
   `:has(... :not(.hidden))` and NOT a change to the markup: when a card is put
   away - which is where the history starts, for everybody - the waiting room is the
   block it has always been, byte for byte. */
body:has(#screen-waiting:not(.hidden)) #screen-waiting:has(.chathost:not(.hidden)) {
  display: flex;
  align-items: flex-start;
  gap: 16px;
}
#screen-waiting:has(.chathost:not(.hidden)) > .panel { flex: 1 1 auto; min-width: 0; }
/* AND THE ROOM'S COLUMN IS WIDER WHILE THE HISTORY CARD IS UP.
   The app's usual column is `clamp(420px, 44%, 880px)` - 830px at Colin's 1887px window -
   and three things do not fit in 830: the ready-to-start panel, the chat board and a
   six-column seasons table. The card is the thing the reader has just asked for, so the
   room gives it the width, exactly as the lobby widens its own column for its cards (see
   "THE LOBBY'S COLUMN IS WIDER THAN EVERY OTHER SCREEN'S" above).
   ONLY WHEN THE CARD IS REALLY LAID OUT BY THIS ROW: once an account has an ARRANGEMENT,
   `body.layout-custom` makes the card a positioned window of its own (windows.js places
   it) and the row has nothing in it to widen for - so the column is left alone and the
   reader's own arrangement is untouched. */
/* ⚠ FIXED 6 Oct 2026 (Colin: "what has made that so big ... why did it have an impact on the background?"). Two faults:
   (a) the rule fired whenever the History card's HOST was in the room, even with the card put away (`body.hide-history` - every
       new account starts that way), so a fresh member got a 1700px column with nothing in it;
   (b) the photograph is sized from `--ui-col` (everything to the right of the column), so widening the column shrank the picture
       to a thumbnail. The photo now has its own `--photo-col`, which stays at the usual column even when the column is widened. */
body:not(.layout-custom):not(.hide-history):has(#screen-waiting:not(.hidden)):has(#historyHostWaiting:not(.hidden)) {
  --ui-col: clamp(420px, 94%, 1700px);
  --photo-col: max(clamp(420px, 44%, 880px), calc(var(--cards-w) + 2 * var(--layout-pad)));
}
/* IN ARRANGE MODE THE CARD IS MOVED OUT OF THE HOST'S FLOW ENTIRELY (it becomes a
   positioned window - see the layout-custom and arranging rules above), so the host
   must not reserve a column for it. There is nothing else in the host, so taking it
   out of the row is enough. The sizing rules above still match through
   `display: contents` - the host is out of the layout, not out of the document - so a
   fresh account's card is the same width and height in arrange mode as out of it. */
body.layout-custom #screen-waiting .chathost,
body.arranging #screen-waiting .chathost { display: contents; }
/* AN OWNER PAGE HAS NO COMPETITION TO TALK IN, so the board is not there at all.
   Written as a rule rather than left to the client because `applyLayout` runs
   before the first view arrives, and a card that appears for one frame and then
   vanishes is exactly the kind of flicker this project keeps having to fix. */
body:has(#screen-owner:not(.hidden)) #chatCard { display: none; }

/* AND ON EVERY OTHER SCREEN THE BOARD DOCKS TO THE RIGHT-HAND EDGE.
 *
 * Colin: "the chat card has to always be visible ... when a new page arrives it has to always be
 * on the top and be visible and ready to use to keep the communication going". So the lobby, the
 * records and the boards all get the same card, docked down the right-hand side where it is out
 * of the way of what the reader came to do and still on screen. The game screen keeps its own
 * column and the waiting room keeps its row beside the panel; this is the slot for the rest.
 *
 * IT DOES NOT EAT THE PHOTOGRAPH'S WHOLE SIDE. The dock is 320px of a 1887px window at Colin's
 * size, so ~740px of picture is still clear to its left (measured by tools/_diag-chat-and-resume
 * .mjs). It also does not block anything it is not over: the dock itself takes no pointer events,
 * only the card inside it does - otherwise an invisible 320px strip down the right of every screen
 * would swallow every click on the photograph area. */
.chatdock {
  position: fixed;
  top: var(--bar-h, 60px);
  right: 0;
  bottom: 0;
  width: 320px;
  z-index: 30;
  display: flex;
  align-items: stretch;
  gap: 8px;
  padding: 8px 10px 10px;
  pointer-events: none;
}
/* A FULL-HEIGHT CARD, not a strip. Left to its own content the docked card measured **300x103**
   with one message on it - a chat panel the height of a postage stamp that grows as people talk.
   It now fills the dock, the way the waiting room's board fills its column, so the stream is
   always the same size and the input stays where the reader last saw it. */
.chatdock > #chatCard {
  flex: 1 1 auto;
  min-height: 0;
  height: 100%;
  max-height: 100%;
  pointer-events: auto;
}
.chatdock.hidden { display: none !important; }
/* ARRANGE MODE TAKES THE CARD OUT OF THE FLOW ANYWAY (it becomes a positioned window with its
   own saved rectangle - see the layout-custom rules above), so the dock must not reserve a
   column for it or the reader gets an empty 320px gutter with the card floating somewhere else. */
body.layout-custom .chatdock,
body.arranging .chatdock { display: contents; }
/* AND THE SAME FOR THE LOBBY'S HOST, for the same reason. `#historyHostLobby` was added when the history card had
   to be readable from the lobby (see SCREEN_CARDS); it is an empty div with no styling, but it becomes a LAID-OUT
   BOX the moment the history card is a fixed window inside it, and a card that is `position: fixed` does not need a
   parent in the flow. `display: contents` takes the host out of the layout and leaves the card exactly where
   `windows.js` puts it - the same treatment the waiting room's hosts already get. */
body.layout-custom #historyHostLobby,
body.arranging #historyHostLobby { display: contents; }
/* A WATCH COMPETITION HAS NO COACHES, so there is nobody to talk to and nobody to
   talk as - the card is hidden outright however this account has it stored. It goes
   on the BODY because that is where every other card's hide rule lives, and it is
   set from renderChat, which is the only place that knows whether the competition
   in view is a watch one. See renderChat in app.js. */
body.hide-chat-watch #chatCard { display: none; }

/* The head: the card's name, centred like every other card's, and the unread dot
   beside it. */
#chatCard .chathead {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: .5rem;
  position: relative;
  flex: 0 0 auto;
}
#chatCard .chattitle {
  font-size: 17px;
  font-weight: 700;
  letter-spacing: .4px;
}
/* THE UNREAD DOT. It is up while the card is down - or while the reader is scrolled
   up reading something else - and something has arrived. It carries the COUNT, not
   just a dot: Colin's question at the table is "how much have I missed", and a bare
   dot cannot answer it. */
#chatCard .chatdot {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.25rem;
  height: 1.25rem;
  padding: 0 .3rem;
  font-size: .75rem;
  font-weight: 700;
  color: #08150f;
  background: var(--accent);
  border-radius: 999px;
}

/* THE STREAM. `position: relative` so "jump to latest" can float over its bottom
   right corner without taking a row of its own - the button appears and disappears
   as the reader scrolls, and a button that pushes the input row down as it arrives
   is one more thing moving under the cursor. */
#chatCard .chatstream {
  position: relative;
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
  margin-top: 6px;
}
#chatCard .chatlist {
  list-style: none;
  margin: 0;
  padding: 2px 4px 2px 0;
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
  /* A LONG WORD CANNOT WIDEN THE BOARD. Colin's own rule for the whole screen is
     that a card must not grow because of what somebody typed, and one pasted URL
     with no spaces in it did exactly that before this. */
  overflow-wrap: anywhere;
  font-size: 13px;
  line-height: 1.35;
}
#chatCard .chatrow { margin-bottom: 7px; }
/* WHO THIS MESSAGE IS FOR, above the field. Hidden unless the server says there is somebody this
   coach may privately message - see renderChatWhisper. */
.chatwhisper {
  display: flex;
  align-items: center;
  gap: .4rem;
  margin-bottom: .35rem;
}
.chatwhisperlabel {
  font-size: 11px;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: .6px;
}
.chatwhisperpick {
  flex: 1;
  min-width: 0;
  font: inherit;
  font-size: 12px;
  padding: 2px 4px;
  color: var(--text);
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: 4px;
}
/* THE MARK ON A PRIVATE MESSAGE. Private rows are read by the two people in them and nobody else, so
   the tag is a reminder rather than a warning - but it has to be unmistakable when scrolling back
   through a season's board. */
#chatCard .chatrow.private .chatpriv {
  font-size: 10px;
  padding: 1px 5px;
  color: #072015;
  background: var(--accent-2, #7fc6a0);
  border-radius: 999px;
  white-space: nowrap;
}
/* A private row is tinted down its left edge so a run of them reads as one conversation. */
#chatCard .chatrow.private .chattext {
  border-left: 2px solid var(--accent-2, #7fc6a0);
  padding-left: .45rem;
}
#chatCard .chatrow.private { cursor: pointer; }
/* A COACH'S ROW. The club is named in the club's own colour - the same
   clubName/clubColour/brandOf helpers the ladder and the fixture use, so a club
   renamed to Mexico is Mexico here too. */
#chatCard .chatrow .chatmeta {
  display: flex;
  align-items: baseline;
  gap: .4rem;
  flex-wrap: wrap;
}
#chatCard .chatrow .chattime { color: var(--muted); font-size: 11px; }
#chatCard .chatrow .chattext { margin: 1px 0 0; }
/* A SYSTEM ROW: the game speaking. Muted, italic, indented, no author - so nobody
   mistakes the game for a person. It occupies one normal row like everything else.
   `.chatline` is what lets the host's Delete sit BESIDE that phrase instead of under
   it: the indent and the italic stay on the phrase, and the button is a sibling that
   inherits neither. */
#chatCard .chatrow.system .chatline {
  display: flex;
  align-items: baseline;
  gap: .4rem;
}
#chatCard .chatrow.system .chattext {
  margin: 0;
  padding-left: .6rem;
  border-left: 2px solid var(--line);
  color: var(--muted);
  font-style: italic;
}
/* THE HOST'S CONTROLS: take the line down, or silence the coach. Built for a host
   only - see renderChat. The Delete is on a coach's row AND on the game's own, since
   Colin's ruling covered both; Mute is only ever on a coach's. */
#chatCard .chatrow .chatacts {
  display: flex;
  gap: .3rem;
  margin-left: auto;
}
#chatCard .chatrow .chatact {
  font: inherit;
  font-size: 10px;
  line-height: 1;
  padding: 2px 5px;
  color: var(--muted);
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: 4px;
  cursor: pointer;
}
#chatCard .chatrow .chatact:hover { color: var(--text); border-color: #3f5148; }

/* JUMP TO LATEST: only up when the reader is NOT at the bottom. */
#chatCard .chatjump {
  position: absolute;
  right: .4rem;
  bottom: .4rem;
  font: inherit;
  font-size: 11px;
  font-weight: 600;
  padding: .25rem .5rem;
  color: var(--text);
  background: rgba(11, 31, 20, .95);
  border: 1px solid var(--accent);
  border-radius: 999px;
  cursor: pointer;
}

/* THE INPUT ROW: the field, and Send beside it. Kept at the bottom of the card. */
#chatCard .chatinput {
  display: flex;
  gap: .4rem;
  flex: 0 0 auto;
  margin-top: 6px;
}
#chatCard .chatinput input {
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
  padding: 6px 8px;
  font-size: 13px;
  color: var(--text);
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: 6px;
}
#chatCard .chatinput .primary {
  flex: 0 0 auto;
  padding: 0 .8rem;
  font-size: 13px;
}
/* The engine's refusal, verbatim. An empty line still costs height, so it goes. */
#chatCard #chatNote { margin: 4px 0 0; font-size: 12px; }
#chatCard #chatNote:empty { display: none; }

/* ===========================================================================
 * THE FINALS, DRAWN BIGGER — AND THE GRAND FINAL BIGGER AGAIN.
 *
 * Colin: *"we're gonna make everything slightly bigger font ... with a bigger font, a bigger clock ...
 * because the finals are more important"*, and of the grand final: *"it requires a bigger clock, a bigger
 * font and more of the hoo ha ... because it's the big game."*
 *
 * The classes are put on `#screen-game` by `renderGame` (see `finalsOnScreen` there), never on the body, so
 * these rules cannot reach the lobby, the history card or the owner page — and they stop applying by
 * themselves when a season round is drawn again.
 *
 * THE LINE BOXES ARE SET, NOT LEFT TO INHERITANCE, on the coach's own row. That row carries pinned
 * line-heights from the one-point font bump (`.sc-me .sc-team` and friends); without these the finals would
 * leave his name at 21px inside a 28.5px line box while every other row got 31.5px, and the rows would come
 * out different heights by exactly the amount that bump exists to avoid.
 * ======================================================================== */

/* ---- 1. ONE NAMED BLOCK PER MATCH (§4.7) -------------------------------- */
/*
 * A finals match gets its own name, its own TEAM/Q1..Q4 head row and its own game — because the card's one
 * shared head row belongs to a week of nine anonymous games, and a final is not anonymous.
 */
.sc-final { display: block; }
.sc-finalname {
  text-align: center;
  /* The name of the game is the heading of its block, so it is set like one: spaced, quiet, and with a
     hairline under it that says where the block begins. It is NOT muted-grey like the column headings -
     this is the one line on the card Colin asked to be able to read at a glance. */
  font-size: 15px;
  font-weight: 650;
  letter-spacing: 1.1px;
  text-transform: uppercase;
  color: var(--text);
  padding-bottom: 5px;
  border-bottom: 1px solid var(--line);
  margin-bottom: 4px;
}

/* ---- 2. THE FINALS STEP -------------------------------------------------- */
#screen-game.finals .sc-finalname { font-size: 17px; letter-spacing: 1.3px; }
#screen-game.finals .sc-team { font-size: 21px; }
#screen-game.finals .sc-q { font-size: 19px; }
#screen-game.finals .sc-total { font-size: 22px; }
#screen-game.finals .sc-me .sc-team { font-size: 22px; line-height: 33px; }
#screen-game.finals .sc-me .sc-q { font-size: 20px; line-height: 30px; }
#screen-game.finals .sc-me .sc-total { font-size: 23px; line-height: 29.325px; }
/* The dial takes its height from the game block, so the FLOOR is what makes it bigger — the svg is capped
   just above it. 5.5rem is 88px against the season's 3.75rem/60px. The slot widens with it, or the wider
   dials would be squeezed by a 76px column. */
#screen-game.finals .sc-game { min-height: 5.5rem; }
#screen-game.finals .sc-clockslot,
#screen-game.finals .sc-clock { flex: 0 0 92px; }
#screen-game.finals .sc-clock svg { max-height: 92px; }

/* ---- 3. THE GRAND FINAL STEP --------------------------------------------- */
#screen-game.grand-final .sc-finalname { font-size: 19px; letter-spacing: 1.6px; }
#screen-game.grand-final .sc-team { font-size: 23px; }
#screen-game.grand-final .sc-q { font-size: 21px; }
#screen-game.grand-final .sc-total { font-size: 24px; }
#screen-game.grand-final .sc-me .sc-team { font-size: 24px; line-height: 36px; }
#screen-game.grand-final .sc-me .sc-q { font-size: 22px; line-height: 33px; }
#screen-game.grand-final .sc-me .sc-total { font-size: 25px; line-height: 31.875px; }
#screen-game.grand-final .sc-game { min-height: 6.5rem; }
#screen-game.grand-final .sc-clockslot,
#screen-game.grand-final .sc-clock { flex: 0 0 112px; }
#screen-game.grand-final .sc-clock svg { max-height: 112px; }

/* ===========================================================================
 * SEASONS LOOKING FOR COACHES — the front door's noticeboard, and the host's advert.
 *
 * Colin: *"when I create a competition as a host — and let's call it Create a season — how can I
 * advertise that I'm looking for players? Looking for coaches. If a stranger just opens up the
 * site, there should be some codes that will be available for people to join, the names of the
 * teams that they don't have the option to coach ... here is the code, you can't coach this team
 * because it's taken."*
 *
 * TWO PIECES, and they are the two ends of one conversation: the TICK in the waiting room that a
 * host switches on (it sits beside the code box, because the code is what it publishes), and the
 * LIST on the front door that a stranger reads. Both are drawn by app.js — renderAdvert and
 * paintOpenSeasons — and both are styled here so the noticeboard's rows and the lobby's own
 * "open competitions" rows keep the same grammar: a name, a quiet meta line, a button.
 * ======================================================================== */

/* ---- 1. THE HOST'S TICK, IN THE WAITING ROOM ---------------------------- */
/* THE CONFIRMATION LINES TAKE NO ROOM UNTIL THEY ARE SAYING SOMETHING.
   They are written by `setText` and emptied the same way (see makeArmer), and an empty `<p>` still
   costs a line box - which on the waiting room is height the Start row above it does not get. The
   same trick the chat's note uses (`#chatNote:empty`). */
#cancelWaitingHint:empty,
#cancelCompHint:empty { display: none; }

/* A dashed frame rather than a solid one: this is a switch that publishes something OUTSIDE this
   room, so it reads as a note pinned beside the code box rather than as part of the form a host
   fills in. Nothing here is sized in a way that can move the Start row above it. */
.advertbox {
  margin-top: 12px;
  padding: 10px 12px;
  background: var(--panel-2);
  border: 1px dashed var(--line);
  border-radius: var(--radius);
}
/* The same label-tick shape as the size box's club list (.clubtick), so the two switches on this
   screen are the same thing to look at. */
.advertisetick {
  display: flex;
  align-items: center;
  gap: 9px;
  margin: 0;
  font-size: 14px;
  color: var(--text);
  cursor: pointer;
}
.advertisetick input {
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
  margin: 0;
  accent-color: var(--accent);
}
.advertbox p { margin: 6px 0 0; }
/* An empty line takes no height, so the box is one row until there is something to say under it —
   the same rule the lobby's error lines are held to (see `:empty` there). */
.advertbox p:empty { display: none; }

/* ---- 2. THE NOTICEBOARD ON THE FRONT DOOR -------------------------------- */
.openlist { list-style: none; padding: 0; margin: 10px 0 0; }

/**
 * ⚠ THE LIST SCROLLS RATHER THAN GROWING WITHOUT LIMIT, and this is a measured decision rather
 * than a taste one.
 *
 * The front door's cards are a fixed 380px column on the left of the photograph, and the route
 * answers up to TWENTY advertised seasons: twenty rows would be about 1400px of card, which would
 * bury every way in that this page exists to offer. So the LIST is capped and scrolls inside
 * itself, and the cost of the whole block stops growing at the fourth season.
 *
 * THE NUMBERS, measured by tools/_check-advertise.mjs §8 at Colin's own window (1887x951) and at
 * 1280x808:
 *
 *   with nothing advertised      the page is  969px /  824px   (the card is not drawn at all)
 *   with four advertised         the page is 1308px / 1135px   - a 339px / 311px block
 *
 * ★ AND THE HONEST HALF OF IT: at 1887x951 the front door was ALREADY 969px tall, 18px over the
 * window, before any of this existed (the session line and the tier lines have been added since the
 * note in index.html about fitting 808). So this block does not "keep the page fitting" - nothing
 * could - and a page that is 339px taller only while a host is advertising is the price of putting
 * the codes where he asked for them. What IS promised is that the price is FIXED: the probe asserts
 * the whole cost is capped however many seasons are advertised.
 *
 * IT IS THE ONE INNER SCROLLBAR IN THE APP, and Colin's rule about those is about the POINTS BOX
 * (*"we don't want scroll bars inside the points box ... so that they don't flip up and down with
 * all this extra content"*) - where the box's height is the number board's. Nothing here is
 * competing for a fixed height: a list that scrolls is the honest way to say "there are more of
 * these than fit", and it is what stops the block growing with the twentieth advert.
 */
#authOpenSeasons {
  max-height: 205px;
  overflow-y: auto;
}
/**
 * ★ BUT AN OPEN ROW IS SHOWN WHOLE, and this is the half of the cap that matters to the reader.
 *
 * Measured on the real page: an opened row is about 180px, so inside a 205px list either the
 * season's own name or the "Join this season" button was clipped out of sight - the first version of
 * this panel cut the button off, and scrolling the list to reveal the button then cut the name off
 * instead. Neither is acceptable for the one press this panel exists to serve, so WHILE A ROW IS
 * OPEN the cap comes off: the card grows by the panel the reader just opened, and closing it puts
 * the page back exactly as it was. The cap still does its job on the twenty-season front page,
 * because that is the state a reader arrives in.
 */
#authOpenSeasons:has(.openseason.open) { max-height: none; }
.openseason {
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 9px 11px;
  margin-bottom: 8px;
}
.openseason:last-child { margin-bottom: 0; }
/* An OPEN row is nudged to the accent, so a reader who has opened three of them can see which one
   they are reading, and the Details button's own state is not the only clue. */
.openseason.open { border-color: var(--accent); }
.openhead { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; }
/* `min-width: 0` so a long competition name WRAPS instead of shoving the Details button off the
   card — a flex item's default minimum is its content, which is exactly how a long name breaks a
   row like this one. */
.opennames { min-width: 0; }
.openseason .meta { color: var(--muted); font-size: 12px; }
.opendetails { flex: 0 0 auto; }
.opentaken { list-style: none; padding: 0; margin: 6px 0; }
.opentaken li { font-size: 13px; padding: 1px 0; }
.opentaken .muted { color: var(--muted); }
.openmore {
  margin-top: 8px;
  padding-top: 8px;
  border-top: 1px solid var(--line);
}
.openmore p { margin: 4px 0; }
.openmore .openjoin { margin-top: 7px; }

/* NIGHT SERIES ROUND CARD: the week's finished games stack above the one on now (renderThisRound). */
.sc-game.sc-done { opacity: 0.85; }
.sc-etnote { margin: 0 0 6px 0; text-align: center; }

/* NIGHT SERIES WEEK ONE: EIGHT GAMES ON ONE CARD, NO SCROLLBAR.
   Colin: "we have to try and get all eight games on the round card ... on a normal resolution monitor ... shrink
   the font slightly if we have to". Measured before: at 1536x730 (a 1080p laptop at 125%) the eight games took
   532px in a 425px list. Only the Night card (`sc-stack`, set in renderThisRound) is compacted - a league round
   and the finals are untouched: type 19/17 -> 15/14px, rows 25.5 -> 19px, the dial 76 -> 40px, the floor 56 -> 40px. */
#liveScoresList.sc-stack { gap: .25rem; }
/* ⚠ A NIGHT GAME DOES NOT STRETCH. The league card's games share the card's height (`flex: 1 1 0`), which is right
   for nine games and made ONE game 693px tall, sat in the middle of the card - Colin: "the rubbish is coming out in
   the middle of the screen ... it's not at the top". A Night week stacks from the top, one game under another. */
#liveScoresList.sc-stack { justify-content: flex-start; }
#liveScoresList.sc-stack .sc-game { min-height: 2.5rem; gap: 10px; flex: 0 0 auto; }
#liveScoresList.sc-stack .sc-clockslot,
#liveScoresList.sc-stack .sc-clock { flex: 0 0 40px; }
#liveScoresList.sc-stack .sc-clock svg { max-height: 40px; }
#liveScoresList.sc-stack .sc-headrow { gap: 10px; padding-bottom: 3px; }
#liveScoresList.sc-stack .sc-team { font-size: 15px; line-height: 19px; }
#liveScoresList.sc-stack .sc-q { font-size: 14px; line-height: 19px; }
#liveScoresList.sc-stack .sc-total { font-size: 16px; line-height: 19px; }
#liveScoresList.sc-stack .sc-me .sc-team { font-size: 16px; line-height: 19px; }
#liveScoresList.sc-stack .sc-me .sc-q { font-size: 15px; line-height: 19px; }
#liveScoresList.sc-stack .sc-me .sc-total { font-size: 17px; line-height: 19px; }
#liveScoresList.sc-stack .sc-head .sc-team,
#liveScoresList.sc-stack .sc-head .sc-q { font-size: 13px; line-height: 17px; }
#liveScoresList.sc-stack .sc-etnote { margin: 0; font-size: 12px; line-height: 15px; }

/* THE HISTORY WINDOW ON SEASON SETUP (#historyHostLobby) - floating, with a title bar, moved by the bar and sized
   by the corner (wireHistoryWindow in app.js). It opens AS WIDE AS ITS WIDEST ROW and every season is ONE LINE -
   Colin: "default when it opens to as wide as the information in it ... we want them as one line bits of
   information". The !important is deliberate: a saved game-screen arrangement (windows.js) writes inline
   left/top/width/height/zoom onto this same card, and here the reader's own move and size must win. */
#historyHostLobby > #historyCard {
  position: fixed !important;
  left: var(--hist-x, 50%) !important;
  top: var(--hist-y, 84px) !important;
  transform: var(--hist-shift, translateX(-50%));
  width: var(--hist-w, max-content) !important;
  height: var(--hist-h, auto) !important;
  max-width: calc(100vw - 16px);
  max-height: calc(100vh - 96px);
  zoom: 1 !important;
  z-index: 70 !important;
  margin: 0 !important;
  padding: 0 !important;
  box-shadow: 0 12px 40px rgba(0, 0, 0, .55);
}
#historyHostLobby > #historyCard .historyhead {
  cursor: move;
  user-select: none;
  padding: 8px 12px;
  margin-bottom: 6px;
  border-bottom: 1px solid var(--line);
  background: rgba(255, 255, 255, .04);
}
#historyHostLobby > #historyCard .history-scroll { overflow: auto; padding-bottom: 16px; }
#historyHostLobby > #historyCard table.history th,
#historyHostLobby > #historyCard table.history td { white-space: nowrap; }
#historyHostLobby > #historyCard table.history .hist-season,
#historyHostLobby > #historyCard table.history .hist-place { max-width: none; }
.hist-grip { display: none; }
#historyHostLobby > #historyCard .hist-grip {
  display: block;
  position: absolute; right: 2px; bottom: 2px;
  width: 16px; height: 16px;
  cursor: nwse-resize;
  background: linear-gradient(135deg, transparent 50%, var(--muted) 50%, var(--muted) 60%, transparent 60%,
    transparent 70%, var(--muted) 70%, var(--muted) 80%, transparent 80%);
  opacity: .7;
}

/* THE HISTORY WINDOW'S TWO TABS - Seasons and Night Series (renderHistory). */
.histtabs { display: inline-flex; gap: 4px; margin-right: auto; margin-left: 12px; }
.histtab {
  font-size: 12px; padding: 3px 10px; border-radius: 999px; cursor: pointer;
  background: transparent; color: var(--muted); border: 1px solid var(--line);
}
.histtab.on { color: var(--text); border-color: var(--accent); background: rgba(255, 255, 255, .06); }
/* The arrangeable-card chrome (windows.js' title strip and edge handles) is for the GAME screen's layout. In the
   floating History window it would be a second title bar whose drag does nothing, so it is put away here - the
   window's own title bar and corner grip move and size it. */
#historyHostLobby > #historyCard > .wingrip,
#historyHostLobby > #historyCard > .winhandle { display: none !important; }

/* "LOST CONTACT" - fixed under the top bar, over the game, until everybody is back (renderAbsence). */
.absentbanner {
  position: fixed; top: 64px; left: 50%; transform: translateX(-50%);
  z-index: 80; max-width: min(720px, calc(100vw - 32px));
  padding: 8px 14px; border-radius: 8px;
  background: rgba(120, 72, 0, .92); color: #fff; font-size: 14px; line-height: 1.4;
  box-shadow: 0 6px 24px rgba(0, 0, 0, .45);
}
.absentbanner > div + div { margin-top: 4px; }

/* THE WAITING ROOM: a coach who came in with the code and has not picked yet, and the host's Remove beside them. */
.choosing { color: #e0a63a; }
.bootbtn { margin-left: 8px; }

/* NIGHT SERIES FIXTURE: which game of the week a row is (nightRowHeading). Week 1 has room beside "Week 1"; the
   Quarter Finals and Preliminary Finals carry it on a second line, centred under the week's name. */
.sf-gameno { font-weight: 600; color: var(--muted); }
.sf-stack { display: inline-flex; flex-direction: column; align-items: center; line-height: 1.2; }

/* THE SHOT TIMING CARD'S QUARTER BUTTONS - pick which quarter of the round the times are for (renderShotTiming). */
.shots-qpick { display: inline-flex; gap: 4px; margin-left: 8px; vertical-align: middle; }
.shots-q {
  padding: 1px 8px;
  font-size: 12px;
  border-radius: 6px;
  border: 1px solid var(--line);
  background: var(--panel-2);
  color: var(--text);
  cursor: pointer;
}
.shots-q.on { border-color: var(--accent); color: var(--accent); font-weight: 700; }

/* THE SHOT CLOCK BUTTON beside a waiting coach's name (awaitingItems in app.js): a countdown, then "Play 2". */
.shotclock-btn {
  margin-left: 8px;
  padding: 1px 8px;
  border-radius: 6px;
  border: 1px solid var(--line);
  background: var(--panel-2);
  color: var(--muted);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}
.shotclock-btn.ready { background: var(--accent); color: #0b1a10; border-color: var(--accent); font-weight: 700; cursor: pointer; }
#shotClockMine { margin: 0 0 6px; font-weight: 600; }
.stall-modal { position: fixed; inset: 0; z-index: 9500; display: flex; align-items: flex-start; justify-content: center; background: rgba(0,0,0,.6); overflow-y: auto; padding: 56px 12px 24px; box-sizing: border-box; }
.stall-modal.hidden { display: none; }
.stall-box { margin: 0 auto; width: min(480px, 92vw); padding: 20px 22px; border-radius: 12px; background: var(--panel, #1b2330); border: 2px solid var(--accent, #35d07f); box-shadow: 0 10px 40px rgba(0,0,0,.6); }
.stall-box h3 { margin: 0 0 8px; font-size: 18px; }
.stall-buttons { display: flex; flex-direction: column; gap: 8px; margin-top: 14px; }
.stall-buttons button { padding: 10px 14px; font-size: 15px; text-align: left; }
.shotclock-host { position: fixed; top: 52px; left: 50%; transform: translateX(-50%); z-index: 9000; display: flex; flex-direction: column; gap: 6px;
  padding: 10px 16px; border-radius: 10px; background: #5a1414; color: #fff; border: 2px solid #ff6b6b; box-shadow: 0 6px 24px rgba(0,0,0,.5); font-size: 15px; }
.shotclock-host.hidden { display: none; }
.shotclock-host .shotclock-btn { margin-left: 10px; padding: 6px 14px; font-size: 15px; }

/* MATCH TIMINGS on the Owner page (renderMatchTimings in app.js): every goal and behind of a match on the game clock. */
#screen-owner .own-timings { margin-top: 10px; }
#screen-owner .own-timings-pick { display: flex; flex-wrap: wrap; gap: 12px; }
#screen-owner .own-timings-pick label { display: grid; gap: 4px; min-width: 220px; }
#screen-owner .tm-sum { margin: 6px 0 0; font-size: 13px; }
#screen-owner .tm-table { width: 100%; border-collapse: collapse; margin-top: 8px; font-size: 13px; }
#screen-owner .tm-table th, #screen-owner .tm-table td { padding: 3px 8px; border-bottom: 1px solid var(--line); text-align: left; }
#screen-owner .tm-table .num { text-align: right; font-variant-numeric: tabular-nums; }
#screen-owner .tm-table .tm-goal td { font-weight: 700; }
#screen-owner .tm-table .tm-qhead td { background: var(--panel-2); color: var(--accent); font-weight: 700; padding-top: 8px; }
#screen-owner .tm-table .tm-qfoot td { color: var(--muted); font-size: 12px; }

/* ---- THE CLUB CARD (Colin, 4 Oct 2026) ---- */
a.clublink { color: inherit; text-decoration: none; cursor: pointer; }
a.clublink:hover { text-decoration: underline; }
/* Floats over the board until it has been arranged, so opening it never squeezes the ladder column. */
#clubCard { position: fixed; left: 40%; top: 12%; width: 30%; height: 62%; z-index: 50; display: flex; flex-direction: column; overflow: hidden; padding: 20px 10px 10px; min-height: 120px; }
#clubBody { flex: 1 1 auto; min-height: 0; overflow-y: auto; overflow-x: hidden; }
#clubBody .cc-name { margin: 0 0 2px; font-size: 18px; font-weight: 700; }
#clubBody .cc-name { display: flex; justify-content: space-between; align-items: center; }
.cc-close { background: none; border: 0; color: var(--muted); font-size: 20px; line-height: 1; padding: 0 4px; cursor: pointer; }
.cc-close:hover { color: var(--text); }
#clubBody .cc-sub { margin: 0 0 6px; font-size: 12px; color: var(--muted); }
#clubBody .cc-form { margin: 0 0 8px; font-size: 14px; font-weight: 700; word-break: break-all; }
table.cc-games { width: 100%; border-collapse: collapse; font-size: 12px; }
table.cc-games th { text-align: left; color: var(--muted); font-weight: 500; padding: 2px 4px; border-bottom: 1px solid var(--line); font-size: 11px; text-transform: uppercase; }
table.cc-games td { padding: 1px 4px; border-bottom: 1px solid #212936; white-space: nowrap; }
table.cc-games .num { text-align: center; font-variant-numeric: tabular-nums; }

/* ---- THE SEASON IS OVER, in the points box (Colin, 5 Oct 2026) ---- */
#seasonOver { text-align: center; padding: 4px 6px; }
/* The cup is sized to the card it is in, so the Play again button is never pushed below the card's edge. */
#seasonOver svg { display: block; margin: 0 auto 2px; height: var(--cup-h, 196px); width: auto; filter: drop-shadow(0 0 10px rgba(255, 210, 63, .35)); }
#seasonOver p { margin: 2px 0; }
#seasonOver .seasonover-head { margin: 2px 0; font-size: 18px; font-weight: 800; color: #ffd23f; letter-spacing: .5px; }
#seasonOver button { margin-top: 6px; padding: 6px 24px; font-size: 15px; font-weight: 700; }
/* While it is up it is the only thing in the points box. */
.controls-scroll:has(> #seasonOver:not(.hidden)) > :not(#seasonOver) { display: none !important; }

/* ---- THE CELEBRATION: the cup picture in the points box, and its owner page (Colin, 5 Oct 2026) ---- */
#premiersCupImg { display: block; margin: 0 auto 2px; height: var(--cup-h, 196px); width: auto; max-width: 95%; object-fit: contain; }
.cel-cup { display: flex; gap: 16px; align-items: center; margin: 6px 0 12px; }
.cel-cup-pic { width: 110px; height: 110px; flex: 0 0 auto; display: flex; align-items: center; justify-content: center;
  border: 1px dashed var(--line); border-radius: 8px; background: var(--panel-2); }
.cel-cup-pic img { max-width: 100%; max-height: 100%; object-fit: contain; }
.cel-cup-controls p { margin: 3px 0; }
.cel-fontrow { margin: 4px 0 12px; }
.cel-fonts { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 6px; }
.cel-font { width: 112px; padding: 6px 4px; border: 2px solid var(--line); border-radius: 8px; background: #c9ced6; color: #2b3038; cursor: pointer; text-align: center; line-height: 1.1; }
.cel-font b { display: block; font-size: 34px; font-weight: 700; }
.cel-font span { display: block; margin-top: 2px; font: 600 11px system-ui, sans-serif; color: #1c2025; }
.cel-font.on { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent); }
.cel-clubs { display: grid; grid-template-columns: repeat(auto-fill, minmax(470px, 1fr)); gap: 4px 18px; margin: 8px 0; }
.cel-club { display: flex; align-items: center; gap: 8px; font-size: 13px; }
.cel-club .cel-name { flex: 1 1 auto; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cel-club input[type=color] { width: 34px; height: 24px; padding: 0; border: 1px solid var(--line); border-radius: 4px; background: none; cursor: pointer; }
.cel-club input[type=color]:disabled { opacity: .25; cursor: default; }
.cel-club label.cel-third { display: flex; align-items: center; gap: 3px; margin: 0; }

/* ---- THE TROPHY CASE (Colin, 5 Oct 2026) ----
   Cups stack down the right edge under Sign out; when a column reaches the bottom the next one starts to its LEFT
   (column + wrap-reverse). The case spans the width of the screen but takes no clicks - only the cups do. */
#trophyCase {
  position: fixed; right: 12px; width: calc(100vw - 24px); z-index: 20; pointer-events: none;
  display: flex; flex-direction: column; flex-wrap: wrap-reverse; align-content: flex-start; align-items: flex-end; gap: 4px 10px;
}
#trophyCase.hidden { display: none; }
#trophyCase .trophy {
  pointer-events: auto; display: flex; flex-direction: column; align-items: center; text-align: center;
}
/* Readable on the sky AND on the crowd: a thin dark edge (four 1px shadows) inside a soft light glow. */
#trophyCase .trophy svg {
  display: block; width: auto;
  filter: drop-shadow(1px 0 0 #000) drop-shadow(-1px 0 0 #000) drop-shadow(0 1px 0 #000) drop-shadow(0 -1px 0 #000)
          drop-shadow(0 0 3px rgba(255, 255, 255, .95)) drop-shadow(0 0 6px rgba(255, 255, 255, .6));
}
#trophyCase .trophy-cap {
  margin-top: 1px; font-size: 9px; font-weight: 800; letter-spacing: .3px; color: #000; white-space: nowrap;
  text-shadow: 0 0 2px #fff, 0 0 3px #fff, 0 0 5px rgba(255, 255, 255, .9), 0 1px 1px rgba(255, 255, 255, .8);
}
#trophyReset.hidden { display: none; }

/* A cup that has been dragged is taken out of the column flow and sits at its own spot on the screen. */
#trophyCase .trophy { cursor: grab; touch-action: none; user-select: none; }
#trophyCase .trophy.placed { position: fixed; }
#trophyCase .trophy.dragging { cursor: grabbing; z-index: 30; filter: drop-shadow(0 8px 14px rgba(0, 0, 0, .7)); }

/* ---- THE BACKGROUND PICKER (Colin, 5 Oct 2026) ---- */
#bgModal { position: fixed; inset: 0; z-index: 9000; background: rgba(0, 0, 0, .55); display: flex; align-items: center; justify-content: center; }
#bgModal.hidden { display: none; }
#bgModal .bg-card { width: min(760px, 94vw); max-height: 88vh; overflow: auto; background: var(--panel); border: 1px solid var(--line); border-radius: 12px; padding: 14px 16px; }
#bgModal .bg-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; }
.bg-set { margin: 8px 0 3px; color: var(--muted); font-size: 11px; text-transform: uppercase; letter-spacing: .6px; }
.bg-gallery { display: grid; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); gap: 8px; }
.bg-thumb { position: relative; padding: 0; border: 2px solid var(--line); border-radius: 8px; background: var(--panel-2); cursor: pointer; overflow: hidden; color: var(--text); }
.bg-thumb img { display: block; width: 100%; height: 74px; object-fit: cover; }
.bg-thumb span { display: block; padding: 2px 4px; font-size: 11px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.bg-thumb.on { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent) inset; }
.bg-row { display: flex; align-items: center; gap: 10px; margin: 10px 0 2px; flex-wrap: wrap; }
.bg-fits button.on { background: var(--accent); color: #04150c; border-color: transparent; }
.bg-own { border-top: 1px solid var(--line); margin-top: 10px; padding-top: 8px; }
.own-bgs { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 8px; margin: 8px 0; }
.own-bg { border: 1px solid var(--line); border-radius: 8px; background: var(--panel-2); padding: 4px; font-size: 12px; }
.own-bg img { display: block; width: 100%; height: 80px; object-fit: cover; border-radius: 5px; }

/* ---- THE SEASON BOOK buttons, in the season-over panel and the My seasons table ---- */
.seasonbook { margin-top: 8px; font-size: 12px; color: var(--muted); }
.seasonbook .bookbtn, a.bookbtn { display: inline-block; margin: 2px 3px; padding: 3px 10px; border: 1px solid var(--accent); border-radius: 6px; color: var(--accent); text-decoration: none; font-weight: 600; font-size: 12px; }
.seasonbook .bookbtn:hover, a.bookbtn:hover { background: var(--accent); color: #04150c; }
.seasonbook .bookbtn.off { border-color: #5b6b64; color: #b9c7c1; cursor: default; opacity: 1; border-style: dashed; }
.seasonbook .bookbtn.off:hover { background: none; color: var(--muted); }

/* THE MARBLE EDITOR on the owner page (renderMarbleEditor): one number box per goal count. */
.marble-editor { margin-top: 2px; padding: 4px 8px; border: 1px solid var(--line, #3a4a40); border-radius: 8px; }
.marble-head #ownMarbleReset { margin-left: auto; }
.marble-head { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; margin-bottom: 3px; }
.marble-head button { margin-left: auto; }
.marble-row { display: flex; flex-wrap: wrap; gap: 6px; }
#screen-owner .own-card .marble-cell { display: flex; flex-direction: row; align-items: center; gap: 4px; margin: 0; }
.marble-goal { font-size: 12px; opacity: 0.75; }
#screen-owner .own-card .marble-cell input { width: 54px; margin: 0; padding-top: 2px; padding-bottom: 2px; text-align: center; }

/* The "Saved." line floats, so saving never makes the Owner page taller than one screen. */
#screen-owner #ownSaved { position: fixed; bottom: 10px; right: 18px; margin: 0; padding: 4px 12px; border-radius: 6px; background: rgba(20, 40, 28, 0.92); z-index: 5; }

/* The percentage beside each club colour (Celebration page). */
.cel-club input[type=number] { width: 52px; margin: 0 6px 0 0; padding: 1px 2px; text-align: right; }
.cel-club input[type=number]:disabled { opacity: .25; }

.cel-club label.cel-third { white-space: nowrap; font-size: 12px; }

/* Celebration page: the percentage boxes were stretching across the row (the Owner page's own number-box rule is more specific
   than the one above) - pinned small here, with the up/down arrows always showing. */
.cel-clubs { grid-template-columns: repeat(auto-fill, minmax(540px, 1fr)); }
#screen-owner .cel-club .cel-name { flex: 0 0 118px; }
#screen-owner .cel-club input[type=color] { flex: none; width: 34px; }
#screen-owner .cel-club input[type=number] { flex: none; width: 62px; max-width: 62px; margin: 0 8px 0 0; padding: 1px 0 1px 4px; text-align: left; }
#screen-owner .cel-club input[type=number]::-webkit-inner-spin-button { opacity: 1; height: 22px; cursor: pointer; }

.cel-clubs { grid-template-columns: repeat(auto-fill, minmax(600px, 1fr)); }
.cel-club .cel-test { flex: none; padding: 1px 10px; font-size: 12px; margin: 0; }

/* The date and season number sit in their own column, so they line up under each other whatever the season names are. */
table.history .hist-date { white-space: nowrap; }

/* THE HISTORY TABLE'S HEADINGS ARE CENTRED, and so are the figures under them (Colin, 5 Oct 2026) - only the season's name and the
   coach's name stay on the left, because they are names to be read along. */
table.history thead th { text-align: center; }
table.history td.hist-date,
table.history td.hist-points,
table.history td.hist-gf,
table.history td.hist-place,
table.history td.hist-extreme,
table.history td.hist-centre { text-align: center; }

/* THE HEADINGS STAY PUT while the seasons scroll beneath them (Colin, 5 Oct 2026). A solid background, so a row sliding up behind
   a heading is hidden rather than showing through it. */
#historyCard table.history thead th {
  position: sticky; top: 0; z-index: 2; background: var(--panel, #161d1a);
  box-shadow: 0 1px 0 var(--line);
}

/* The hills card's "Strength to draw" on ONE line (label, then the box) - gives back the height the Pause votes card needs
   so the Owner page still fits 1920x1080. */
#screen-owner .own-wide > label:has(> #ownHillStrength) { display: flex; align-items: center; gap: 10px; }
#screen-owner .own-wide > label:has(> #ownHillStrength) select { width: 280px; }

/* UNSAVED CHANGES on the Owner page: the warning takes the place of the description line (so the page keeps its height) and the
   button to press glows amber. */
#screen-owner .ownDirty { margin: 0; color: #ffd36b; font-weight: 700; font-size: 14px; }
#screen-owner button.attention { box-shadow: 0 0 0 3px #e0b030, 0 0 12px 2px rgba(224,176,48,.7); animation: ownpulse 1.2s ease-in-out infinite; }
@keyframes ownpulse { 50% { box-shadow: 0 0 0 3px #e0b030, 0 0 2px 0 rgba(224,176,48,.2); } }

/* The Tiers card: name and password each on ONE line (label, then the box) so the new Create button does not push the Owner page
   past one screen. */
#screen-owner .own-card label:has(> #tierName),
#screen-owner .own-card label:has(> #tierPassword) { display: grid; grid-template-columns: 1fr 140px; align-items: center; gap: 8px; }
#screen-owner .own-card label:has(> #tierPassword) .muted { display: block; }
#screen-owner #tierResult:empty, #screen-owner #tierError:empty { display: none; }

.pausebar { top: 0; background: #0f2a3a; border-bottom-color: #4aa3d8; }
.pauserow { display: flex; align-items: center; gap: 8px; margin: 8px 0; }

.boardtabs { display: flex; gap: 8px; margin: 10px 0 0; }
.boardtabs button.on { background: var(--accent); color: #08150f; border-color: var(--accent); font-weight: 700; }

#trophyCase .trophy-more { align-self: flex-start; margin: 6px; padding: 6px 12px; border-radius: 999px; border: 1px solid var(--line); background: rgba(0,0,0,.55); color: var(--accent); font-weight: 700; cursor: pointer; }

/* SHORT WINDOWS (Colin's list, item 6): below 720px high the points card - the one that carries the long "your game is Week 1
   game 8" notes in a Night Series - uses smaller type and tighter spacing so far less of it scrolls (measured at 1440x680: its
   contents went from 419px to 315px tall). Windows at 1080 high are untouched. */
@media (max-height: 720px) {
  #controlPanel .controls-scroll { font-size: 12px; line-height: 1.25; }
  #controlPanel .controls-scroll p { margin: 3px 0; }
  #controlPanel .controls-scroll button { padding: 2px 6px; min-width: 0; font-size: 12px; }
}

/* BONUS BUTTONS: a different colour from the number buttons, a small count when a button has more than one use. */
.bonusrow { margin-top: 8px; }
.bonusbtns { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 4px; }
.bonusbtn { position: relative; min-width: 46px; padding: 8px 10px; border-radius: 8px; border: 2px solid #c9871b; background: #4a3208; color: #ffd36b; font-weight: 800; cursor: pointer; }
.bonusbtn:disabled { opacity: .45; cursor: default; }
.bonusbtn.armed { background: #e0a020; color: #1d1300; box-shadow: 0 0 0 3px rgba(224,160,32,.55); }
.bonusbtn .bcount { position: absolute; top: -7px; right: -7px; background: #fff; color: #5a3b00; font-size: 10px; padding: 0 4px; border-radius: 999px; }
.bstar { color: #ffd36b; font-weight: 700; }
/* THE PER-GAME BONUS (Colin, 11 Oct 2026): four named, coloured buttons - cool (Spark) to hot (Turbo) - ONE per game. Picking one locks it in: the others go grey
   and every number button takes its colour. */
.bonusbtns { gap: 8px; align-items: stretch; }
.bonusbtn.pick { position: relative; min-width: 74px; height: 42px; padding: 0 12px; border-radius: 10px; border: 2px solid var(--tank-c2, #888); background: linear-gradient(180deg, var(--tank-c2, #ddd), var(--tank-c1, #aaa)); color: #111; font-size: 15px; font-weight: 800; letter-spacing: .3px; display: inline-flex; flex-direction: column; align-items: center; justify-content: center; line-height: 1.05; }
.bonusbtn.pick .block { font-size: 9px; font-weight: 700; letter-spacing: .4px; text-transform: uppercase; }
.bonusbtn.pick.locked, .bonusbtn.pick.locked:disabled { background: #3a3f3d; border-color: #555; color: #8a908d; opacity: 1; }
.bonusbtn.pick:disabled:not(.locked):not(.armed) { opacity: .5; }
.bonusbtn.pick.armed { box-shadow: 0 0 0 3px rgba(255,255,255,.75), 0 0 14px 2px var(--tank-c2); opacity: 1; }
.bonusbtn.pick:not(:disabled):hover { transform: translateY(-1px); }
.bonusbtn.pick .bcount { top: -8px; right: -8px; background: #fff; color: #222; font-size: 10px; }
.tank-1 { --tank-c1: #1d8fd1; --tank-c2: #6fd3ff; }
.tank-2 { --tank-c1: #12a578; --tank-c2: #5fe3b4; }
.tank-3 { --tank-c1: #c79a06; --tank-c2: #ffd84a; }
.tank-4 { --tank-c1: #e0560f; --tank-c2: #ff9a4d; }
.tank-5 { --tank-c1: #c5162a; --tank-c2: #ff6b78; }
/* A PICKED BONUS TURNS EVERY NUMBER BUTTON ITS COLOUR - the sign that it is locked in. */
#nominationButtons.bonus-tint .numbtn { background: linear-gradient(180deg, var(--tank-c2), var(--tank-c1)); color: #111; border-color: var(--tank-c2); font-weight: 800; }
#nominationButtons.bonus-tint .numbtn:disabled { opacity: .55; }
/* LIVE NOW (Owner): competitions, their coaches, a chat reader */
#ownLive .live-list { display: grid; gap: 10px; margin: 8px 0 14px; }
#ownLive .live-comp { border: 1px solid var(--line); border-radius: 10px; padding: 8px 10px; background: rgba(0,0,0,.18); }
#ownLive .live-head { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
#ownLive .live-head .live-chatbtn { margin-left: auto; }
#ownLive .live-coaches { display: grid; gap: 3px; margin-top: 6px; }
#ownLive .live-coach { display: grid; grid-template-columns: 14px minmax(110px, 1fr) minmax(90px, 1fr) minmax(70px, auto) 72px; gap: 8px; align-items: center; font-size: 13px; }
#ownLive .dot { width: 10px; height: 10px; border-radius: 50%; background: #555; display: inline-block; }
#ownLive .dot.on { background: #3ddc84; box-shadow: 0 0 6px #3ddc84; }
#ownLive .live-chat { margin-top: 8px; max-height: 260px; overflow: auto; border: 1px solid var(--line); border-radius: 8px; padding: 6px; font-size: 13px; background: rgba(0,0,0,.25); }
#ownLive .live-msg { display: flex; gap: 6px; align-items: baseline; padding: 2px 0; }
#ownLive .live-msg.sys { color: var(--muted); font-style: italic; }
#ownLive .live-msg .lm-text { flex: 1; overflow-wrap: anywhere; }
#ownLive .lm-del { padding: 0 6px; min-width: 0; }
#ownBonus .bonus-patterns { display: grid; gap: 4px; margin: 6px 0 10px; max-width: 420px; }
#ownBonus .bp-head, #ownBonus .bp-row { display: grid; grid-template-columns: 90px repeat(4, 56px) 80px; gap: 6px; align-items: center; font-size: 13px; }
#ownBonus .bp-head { color: var(--muted); font-size: 12px; }
#screen-owner #ownBonus .bp-row input[type=number] { display: inline-block !important; width: 52px !important; margin: 0 !important; padding: 2px 4px !important; }
#ownBonus .bp-total { color: var(--muted); font-size: 12px; }
#ownBonus .bonus-line, #ownBonus .tierrow { margin: 6px 0; font-size: 13px; }
#ownBonus .bonus-uses { display: flex; gap: 14px; flex-wrap: wrap; margin-top: 4px; }
#ownBonus .bonus-use { display: flex; align-items: center; gap: 6px; font-size: 13px; }
#ownBonus input[type=number] { width: 64px; }
/* The bonus page's boxes sit INLINE in their sentence (the Owner page's general input style stacks them). */
#screen-owner #ownBonus .tierrow input[type=number],
#screen-owner #ownBonus .bonus-use input[type=number],
#screen-owner #ownBonus .bonus-line input[type=number] { display: inline-block !important; width: 64px !important; margin: 0 4px !important; padding: 2px 4px !important; }
#screen-owner #ownBonus .bonus-line label, #screen-owner #ownBonus .bonus-use { display: inline-flex !important; align-items: center; gap: 6px; }

/* THE OWNER PAGE'S SIDE LIST OF CATEGORIES (Colin, 6 Oct 2026). The four sub-page buttons that used to sit in the top row now live in the
   list (their old buttons stay in the page, hidden, because the sub-pages still toggle them). */
#screen-owner #ownTimingsBtn, #screen-owner #ownCelebrateBtn, #screen-owner #ownBonusBtn, #screen-owner #ownBackgroundsBtn { display: none; }
#screen-owner .own-layout { display: flex; gap: 14px; align-items: flex-start; margin-top: 10px; }
#screen-owner .own-nav { flex: 0 0 210px; display: flex; flex-direction: column; gap: 5px; position: sticky; top: 8px; }
#screen-owner .own-nav button { text-align: left; padding: 8px 12px; border-radius: 8px; border: 1px solid var(--line); background: var(--panel-2); color: var(--text, #e6ece9); cursor: pointer; font-size: 13px; }
#screen-owner .own-nav button:hover { border-color: var(--accent); }
#screen-owner .own-nav button.on { background: var(--accent); color: #08150f; border-color: var(--accent); font-weight: 700; }
#screen-owner .own-content { flex: 1 1 auto; min-width: 0; }
#screen-owner .own-grid > .own-card.own-hide { display: none; }

#askDialog .streakbox { min-width: min(420px, 90vw); }
#askDialog button.danger { background: #8a1f1f; border: 1px solid #c33; color: #fff; font-weight: 700; }

.brand-g { display: grid; grid-template-columns: auto 64px auto 64px auto 64px; gap: 6px 8px; align-items: center; }
.brand-g input[type=number] { width: 64px; }
.brand-g .cel-third { grid-column: 1 / -1; font-size: 12px; }
.brand-guernsey { margin: 6px 0; }

/* THE MASCOT PARADE (web/mascots.js): a see-through layer over the page; each mascot crosses the screen once and removes itself. */
#mascotLayer { position: fixed; inset: 0; pointer-events: none; z-index: 45; overflow: hidden; }
#mascotLayer .mascot { position: absolute; left: 0; line-height: 1; translate: -25vw 0; animation: mascotX 7s linear forwards, mascotFly 1.3s ease-in-out infinite; text-shadow: 0 4px 10px rgba(0,0,0,.5); }
#mascotLayer .mascot-jet { animation-duration: 3.6s, 1.3s; }
#mascotLayer .mascot-run { animation: mascotX 8s linear forwards, mascotRun .42s ease-in-out infinite; }
#mascotLayer .mascot-hop { animation: mascotX 9s linear forwards, mascotHop 1.5s ease-in-out infinite; }
#mascotLayer .mascot-spin { animation: mascotX 8s linear forwards, mascotSpin 2.4s linear infinite; }
#mascotLayer .mascot-swing { animation: mascotX 8s linear forwards, mascotSwing .9s ease-in-out infinite; }
@keyframes mascotX { to { translate: 125vw 0; } }
@keyframes mascotFly { 0%, 100% { transform: translateY(-14px) rotate(-6deg); } 50% { transform: translateY(14px) rotate(6deg); } }
@keyframes mascotRun { 0%, 100% { transform: translateY(0) rotate(-4deg); } 50% { transform: translateY(-16px) rotate(4deg); } }
@keyframes mascotHop { 0%, 100% { transform: translateY(0) scaleY(.9); } 50% { transform: translateY(-34vh) scaleY(1.05); } }
@keyframes mascotSpin { to { transform: rotate(360deg); } }
@keyframes mascotSwing { 0%, 100% { transform: rotate(-18deg); } 50% { transform: rotate(18deg); } }

/* THE BOUT (web/mascots.js): the mascots sit on the ground line and are moved by the Web Animations API. */
#mascotLayer .mascot { position: absolute; left: 0; bottom: 12vh; translate: none; animation: none; line-height: 1; will-change: transform; text-shadow: 0 5px 12px rgba(0,0,0,.55); }
#mascotLayer .mascot-fx { bottom: 18vh; text-shadow: none; }
#mascotLayer .mascot-pic img { display: block; width: auto; max-width: none; pointer-events: none; filter: drop-shadow(0 5px 10px rgba(0,0,0,.5)); }   /* a club picture instead of its emoji (10 Oct 2026) */

#mascotLayer .mascot-cup { bottom: 12vh; text-shadow: none; filter: drop-shadow(0 6px 10px rgba(0,0,0,.5)); }
#ownMascots .mascot-test { gap: 10px; flex-wrap: wrap; align-items: center; }
#ownMascots .mascot-test select { max-width: 240px; }
#ownMascots .mascot-rows { display: grid; gap: 4px; margin: 8px 0; max-height: 46vh; overflow: auto; }
#ownMascots .mascot-row { display: grid; grid-template-columns: 230px 70px 170px 90px 70px; gap: 8px; align-items: center; font-size: 13px; }
#ownMascots .mascot-row input, #ownMascots .mascot-row select { padding: 3px 6px; }
#ownMascots .mr-emoji { font-size: 20px; text-align: center; }
#ownMascots .mascot-row.has-pic { grid-template-columns: 230px 170px 90px 70px; }
#ownMascots .mascot-row.has-pic .mr-emoji { display: none; }
#ownMascots .mr-club { display: flex; align-items: center; gap: 8px; }
#ownMascots .mr-pic { height: 38px; width: 56px; object-fit: contain; flex: none; }   /* the club's mascot picture, in place of the emoji */

/* THE DRAWN MASCOTS (web/mascots.js DRAWINGS): wings flap, the body bobs, a hurt mascot gets crossed eyes. */
#mascotLayer .mascot-drawn { line-height: 0; text-shadow: none; filter: drop-shadow(0 6px 8px rgba(0,0,0,.5)); }
#mascotLayer .mascot-drawn .face { display: block; width: 100%; height: 100%; }
#mascotLayer .mascot-drawn svg { width: 100%; height: 100%; overflow: visible; display: block; }
#mascotLayer .mascot-drawn .d-wing { transform-box: fill-box; transform-origin: 80% 85%; animation: dWing .3s ease-in-out infinite alternate; }
#mascotLayer .mascot-drawn .d-body { transform-box: fill-box; animation: dBob .42s ease-in-out infinite alternate; }
#mascotLayer .mascot-drawn.attack .d-wing { animation-duration: .1s; }
#mascotLayer .mascot-drawn.attack svg { transform: rotate(8deg) translateX(6px); }
#mascotLayer .mascot-drawn.cheer .d-wing { animation-duration: .16s; }
#mascotLayer .mascot-drawn .eye-x { display: none; }
#mascotLayer .mascot-drawn.hurt .eye-open { display: none; }
#mascotLayer .mascot-drawn.hurt .eye-x { display: inline; }
#mascotLayer .mascot-drawn.hurt .d-wing { animation: none; transform: rotate(35deg); }
#mascotLayer .mascot-drawn.hurt .d-body { animation: none; }
@keyframes dWing { from { transform: rotate(-26deg); } to { transform: rotate(16deg); } }
@keyframes dBob { from { transform: translateY(0); } to { transform: translateY(-5px); } }

#ownMascots .mascot-onoff { margin: 0 0 8px; padding: 6px 10px; border: 1px solid var(--line); border-radius: 8px; background: rgba(255,255,255,.04); }

/* THE BYE BANNER in the points box (renderAllocation): impossible to miss. */
.bye-banner { margin: 6px 0; padding: 10px 8px; text-align: center; font-size: 1.9rem; font-weight: 800; letter-spacing: .18em;
  color: #0b1a10; background: var(--accent, #35d07f); border-radius: 10px; flex: 0 0 auto; }
.bye-banner.hidden { display: none !important; }

/* THE HOST'S BUTTON FOR A SLOW COACH used to be green and lost among the green; now it is amber and large. */
.shotclock-btn.ready { background: #ffb020 !important; color: #1b1200 !important; border-color: #ffb020 !important; font-weight: 800; font-size: 14px; padding: 4px 12px; animation: shcpulse 1.4s ease-in-out infinite; }
@keyframes shcpulse { 50% { box-shadow: 0 0 0 4px rgba(255,176,32,.35); } }
.help-btn { margin-top: 6px; width: 100%; padding: 8px 10px; font-weight: 700; background: #5a1414; color: #fff; border: 2px solid #ff6b6b; border-radius: 8px; cursor: pointer; }
.help-btn.hidden { display: none; }

.help-msg { margin: 4px 0; padding: 6px 8px; font-size: 13px; border-left: 3px solid #ff6b6b; background: rgba(255,107,107,.1); }
.help-msg.hidden { display: none; }
#ownHelpLabels { width: 100%; }

/* ALL CLUB HISTORY on the Owner page: one narrow column per season, two digits wide. */
.history-scroll { overflow: auto; max-width: 100%; }
.history-table { border-collapse: collapse; font-size: 12px; font-variant-numeric: tabular-nums; }
/* GRIDLINES between the seasons and a tight fit, so a lot of seasons fit across the page (Colin, 13 Oct 2026) */
.history-table th, .history-table td { padding: 1px 2px; border-bottom: 1px solid var(--line); border-left: 1px solid rgba(255,255,255,.18); }
.history-table .hc-club { border-left: none; }
.history-table .hc-club { text-align: left; white-space: nowrap; padding-right: 8px; position: sticky; left: 0; background: var(--panel, #1b2330); }
.history-table .hc-n { width: 1.6rem; min-width: 1.6rem; text-align: center; white-space: nowrap; }
.history-table .hc-n.hc-two { width: 3.3rem; min-width: 3.3rem; }
.hc-h { color: #7db7ff; }
.hc-f { color: #ffb347; font-weight: 700; }
.hc-sl { color: var(--muted); padding: 0 1px; }
.history-table th.hc-n { color: var(--muted); font-weight: 600; }
.history-table td.hc-top .hc-f { color: #ffd34d; font-weight: 900; }

/* the Owner link looks like the other buttons on the bar */
a#openOwner.small-btn { display: inline-block; font: inherit; font-size: .75rem; color: var(--text); cursor: pointer; }
a#openOwner.small-btn.hidden { display: none; }

/* a club that played that season under another name (a rename): dotted underline, the name is in the hover */
.history-table td.hc-worn { text-decoration: underline dotted rgba(255,255,255,.7); text-underline-offset: 3px; }

/* MY SEASONS toolbar: download and open a saved file */
.hist-toolbar { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin: 0 0 8px; }
.hist-file { cursor: pointer; display: inline-flex; align-items: center; }

/* THE SEASON BOARDS AS A TABLE (Colin, 14 Oct 2026): one row a season, two-line headings, the grand final on one line. */
.boardtablewrap { overflow-x: auto; }
.boardtable { border-collapse: collapse; width: 100%; font-size: 13px; }
.boardtable th { background: transparent; text-align: left; font-size: 11px; font-weight: 600; line-height: 1.15; color: var(--muted, #9aa8a2); padding: 4px 8px 4px 0; border-bottom: 1px solid var(--line, #2a3a33); vertical-align: bottom; }
.boardtable td { padding: 4px 8px 4px 0; border-bottom: 1px solid var(--line, #1f2d27); white-space: nowrap; vertical-align: middle; }
.boardtable th.num, .boardtable td.num { text-align: right; padding-right: 12px; font-variant-numeric: tabular-nums; }
.boardtable .boardrank { width: 1%; text-align: right; padding-right: 10px; }
.boardtable .bgf { margin-top: 0; }
tr.boardrow { display: table-row; padding: 0; border-top: 0; }
/* The boards screen is as wide as its tables need, up to the window (the default panel was too narrow for a grand final line). */
#screen-boards .panel { width: max-content; min-width: min(520px, 100%); max-width: calc(100vw - 24px); }

/* THE BOARDS GRID (Colin, 14 Oct 2026): "if they're really small ... let's get some of these tables side by side". Wide season tables take a row; the small coach/figure tables flow side by side. */
.boardgrid { display: flex; flex-wrap: wrap; gap: 4px 36px; align-items: flex-start; }
.boardgrid .board { margin-top: 18px; }
.boardgrid .boardwide { flex: 1 1 100%; min-width: 0; }
.boardgrid .boardsmall { flex: 0 1 auto; min-width: 0; max-width: 100%; }
.boardsmall > p.muted { max-width: 22em; }

/* THE SEASON POOL CARDS (Owner -> Points): one small card a club count, side by side. */
.pool-minis { display: flex; flex-wrap: wrap; gap: 10px; }
.pool-mini { flex: 0 1 190px; border: 1px solid var(--line, #2a3a33); border-radius: 8px; padding: 8px 10px; }
.pool-mini h4 { margin: 0 0 6px; font-size: 13px; color: var(--accent); }
.pool-mini label { display: flex; justify-content: space-between; align-items: center; gap: 8px; font-size: 12px; margin: 3px 0; }
.pool-mini input { width: 64px; }
.pool-mini .pool-out { margin: 6px 0 0; line-height: 1.5; }
#screen-owner .own-pool-card { column-span: all; }
.own-pool-card .sectionhead .muted { text-transform: none; letter-spacing: 0; font-weight: 400; display: block; margin-top: 4px; }

/* COMPACT BOARDS (Colin, 15 Oct 2026): columns only as wide as they need, close together, kept to the left; the screen is not used up. */
#screen-boards .panel { width: min(1000px, calc(100vw - 24px)); min-width: 0; max-width: none; }
.boardtable { width: auto; }
.boardtable th, .boardtable td { padding: 3px 7px 3px 0; }
.boardtable th.num, .boardtable td.num { padding-right: 9px; }
.boardgrid { gap: 2px 26px; }
/* ALIGNMENT (Colin, 15 Oct 2026): every heading centred; figures (points left ...) and the date centred under it; coach, club, season and grand final left. */
.boardtable th { text-align: center; }
.boardtable td.num, .boardtable td.bdate { text-align: center; }
.boardtable th.num { text-align: center; padding-right: 9px; }
.boardtable td.num { padding-right: 9px; }
.boardtable th.boardrank { text-align: center; }
/* BOARD HEADINGS STAND OUT, THE RESULTS DO NOT (Colin, 15 Oct 2026): big accent headings with a thick rule across, plain-weight results. */
.boardgrid .board { margin-top: 26px; }
.board h3 { font-size: 22px; line-height: 1.2; margin: 0 0 6px; padding-bottom: 5px; border-bottom: 4px solid var(--accent); letter-spacing: .01em; }
.board > p.muted { margin: 0 0 8px; }
.boardtable strong, .boardtable b, .boardtable .bscore b, .boardtable td { font-weight: 400; }
.boardtable td strong { color: inherit; }
.boardtable .bclub, .boardtable .bscore { font-weight: 400; }
/* BOARD HEADINGS CENTRED IN CAPITALS, MORE ROOM BETWEEN BOARDS, AND A MARGINAL ALTERNATING TONE (Colin, 15 Oct 2026). */
.board h3 { text-align: center; text-transform: uppercase; }
.board > p.muted { text-align: center; }
.boardgrid { gap: 18px 22px; }
.boardgrid .board { margin-top: 0; padding: 12px 16px 14px; border-radius: 8px; background: rgba(255, 255, 255, 0.025); }
.boardgrid .board:nth-child(even) { background: rgba(255, 255, 255, 0.065); }

/* The paid member's tick in the points box (Hold the computer's quarter score). */
.poolbox .holdscore { display: flex; align-items: center; gap: 6px; margin-top: 6px; font-size: 12px; color: var(--muted); cursor: pointer; }
.poolbox .holdscore.hidden { display: none; }
/* THE ROUND CARD IN THE FINALS GROWS DOWN, IT DOES NOT SCROLL INSIDE ITSELF (Colin, 15 Oct 2026: "I don't want a scrollbar to come into play - just push down the results below the teams playing ...
   and if they go past the bottom of the page then introduce the scroll bar"). The games played this week stack above the one on now and the bracket sits under them; the WHOLE card scrolls only when
   all of that is taller than the card. */
.panel.grow:has(> #finalsBox:not(.hidden)) { overflow-y: auto; }
.panel.grow:has(> #finalsBox:not(.hidden)) > #liveScores:not(.hidden) { flex: 0 0 auto; max-height: none; }
.panel.grow:has(> #finalsBox:not(.hidden)) > #liveScores #liveScoresList { flex: 0 0 auto; overflow: visible; }
.panel.grow:has(> #finalsBox:not(.hidden)) > #finalsBox:not(.hidden) { flex: 0 0 auto; overflow: visible; }

/* PHONES (Colin, 18 Oct 2026): cards stack full width and you scroll; no floating, so no grips. A big "Reset all cards" is always in the View panel. */
.viewphone { display: none; }
@media (max-width: 759px) {
  .viewphone { display: inline-block; }
  .arrangerow, #arrangeOn, label:has(#arrangeOn), #arrangeOn + *, .viewrow:has(#arrangeOn) { display: none; }
  body.phone-stack .wingrip { display: none; }
}
/* the stacked game screen: one column that is exactly the screen wide (a card's own content must never stretch it), every card full width */
@media (max-width: 759px) {
  main:has(#screen-game:not(.hidden)) { padding: 6px 6px 10px; }
  body.phone-stack #screen-game .grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: 10px; }
  body.phone-stack #screen-game .grid > *,
  body.phone-stack #screen-game .col-side,
  body.phone-stack #screen-game .col-side > aside.panel { min-width: 0; width: auto; max-width: 100%; }
}
@media (max-width: 759px) {
  .topbar, .topbar .brand, .topbar .topcontrols { flex-wrap: wrap; max-width: 100%; }
  .topbar .signed-in { display: none; }
  .topbar .topcontrols { gap: 6px; }
}
@media (max-width: 759px) { body.phone-stack .topbar { position: static; } }
/* PHONE ORDER: the round card first (it is what you play), then the points, the ladder, and the long fixture last */
@media (max-width: 759px) {
  body.phone-stack #screen-game .panel.grow { order: -3; }
  body.phone-stack #screen-game #controlPanel { order: -2; }
  body.phone-stack #screen-game .col-side { order: -1; }
  body.phone-stack #screen-game .fixture-panel { order: 5; }
}
/* PHONE: the round card's type is the same small size as the fixture card's, so a whole round fits on the screen (Colin, 18 Oct 2026) */
@media (max-width: 759px) {
  body.phone-stack #screen-game .sc-team,
  body.phone-stack #screen-game .sc-q,
  body.phone-stack #screen-game .sc-total,
  body.phone-stack #screen-game .sc-me .sc-team,
  body.phone-stack #screen-game .sc-me .sc-q,
  body.phone-stack #screen-game .sc-me .sc-total,
  body.phone-stack #screen-game.finals .sc-team,
  body.phone-stack #screen-game.finals .sc-q,
  body.phone-stack #screen-game.finals .sc-total,
  body.phone-stack #screen-game.grand-final .sc-team,
  body.phone-stack #screen-game.grand-final .sc-q,
  body.phone-stack #screen-game.grand-final .sc-total { font-size: 12px; line-height: 17px; letter-spacing: 0; }
  body.phone-stack #screen-game .sc-row, body.phone-stack #screen-game .sc-headrow { column-gap: .3rem; grid-template-columns: minmax(0, 1fr) repeat(3, 1.9rem) 3.6rem; justify-content: stretch; }
}
@media (max-width: 759px) {
  body.phone-stack #screen-game .sc-clockslot, body.phone-stack #screen-game .sc-clock,
  body.phone-stack #screen-game.finals .sc-clockslot, body.phone-stack #screen-game.finals .sc-clock,
  body.phone-stack #screen-game.grand-final .sc-clockslot, body.phone-stack #screen-game.grand-final .sc-clock { flex: 0 0 34px; }
  body.phone-stack #screen-game .sc-clock svg { max-height: 34px; }
  body.phone-stack #screen-game .sc-game { gap: 8px; min-height: 2.6rem; }
}
/* PHONE: the extra cards (club, chat, history, scores, shots) are ordinary stacked cards too - not floating windows at 40% across (Colin, 18 Oct 2026: the West Sydney club card "starts nearly in the centre and goes off to the right") */
@media (max-width: 759px) {
  body.phone-stack #screen-game #clubCard, body.phone-stack #screen-game #chatCard, body.phone-stack #screen-game #historyCard,
  body.phone-stack #screen-game #scoreVarsCard, body.phone-stack #screen-game #shotsCard {
    position: static; left: auto; top: auto; right: auto; bottom: auto; width: auto; max-width: 100%; height: auto; max-height: 75vh; margin: 0; zoom: 1;
    box-sizing: border-box; min-width: 0; overflow: auto;
  }
  body.phone-stack #screen-game #clubCard { order: 1; }
  body.phone-stack #screen-game #chatCard, body.phone-stack #screen-game #historyCard { order: 6; }
}

/* "Still there?" - the page has stopped asking the server because nobody has touched it for hours */
.idlebar { position: fixed; left: 50%; bottom: 14px; transform: translateX(-50%); z-index: 9990; padding: 10px 18px; border-radius: 999px; background: #3a2f0b; color: #fff; border: 1px solid var(--warn, #e0b030); font-size: 15px; box-shadow: 0 6px 20px rgba(0,0,0,.5); }
.idlebar.hidden { display: none; }

/* PAID MEMBERSHIP card (PayPal) */
#payModal { position: fixed; inset: 0; z-index: 9000; background: rgba(0, 0, 0, .55); display: flex; align-items: center; justify-content: center; }
#payModal.hidden { display: none; }
#payModal .pay-card { width: min(420px, 94vw); max-height: 88vh; overflow: auto; background: var(--panel); border: 1px solid var(--line); border-radius: 12px; padding: 14px 16px; }
#payModal #payButtons { margin-top: 10px; min-height: 10px; }
#payModal #payButtons.hidden { display: none; }
#payModal #payMsg.ok { color: var(--accent); }
#payModal #payMsg.bad { color: var(--warn, #e0b030); }
@media (min-width: 760px) { #authPayRow { display: none !important; } }
