/* OurTrainHub - stylesheet
 *
 * Built to the OurTrainHub brandbook v1.0 (brand/brandbook.html).
 * The colours are the brand and they do not change per club - there is no
 * club accent and no palette to resolve. What varies per install is the team
 * NAME in the top bar, and nothing else.
 *
 * The green is one hue (120 deg) at three stops:
 *   --mark      #228B22  THE BRAND GREEN. The wordmark and the tile's T, in
 *                        both themes. WCAG exempts a logotype from contrast
 *                        entirely, so this is a legibility call rather than a
 *                        compliance one, and it reads everywhere it appears:
 *                        4.39 on the sign-in card, 3.78 on the tile.
 *                        It is the mark's colour and nothing else's.
 *   --acc       #1D7A1D  FILLS AND LINES on a light surface. Buttons, the
 *                        active rail item, selected chips, In, every border
 *                        and bar, and any accent-coloured word. White sits on
 *                        it at 5.45; it reads on the page at 4.88.
 *   --acc-text  = --acc on a light surface, #3FD13F on ink.
 * The nav pill's selected item is --acc too, so every green surface in the
 * interface is the same one. #3FD13F is now left doing a single job: accent
 * TYPE on ink, in the night theme, where --acc is 3.05 and cannot be a word.
 *
 * #228B22 itself takes white at only 4.39, which misses AA for a 14px label -
 * so the working fill is the same green two shades down, where white clears at
 * 5.45. At button size the two are all but indistinguishable.
 *
 * The one thing to watch: a green fill is 1.17 against the turf in a drill
 * diagram. Nothing filled sits on a diagram - the mobile nav floats over the
 * drill grid but the pill itself is ink, which separates it from anything.
 *
 * Daylight is the default so the app reads as the same company as OurClubHub
 * in a screenshot. The night theme is a toggle away, and it flips exactly one
 * accent token: --acc-text becomes the bright green, because the type stop on
 * ink is 3.05 and fails.
 */

/* Brandbook §05 and §20: Archivo, self-hosted, because a ground with no
   reception is exactly where this app gets opened. Four faces, 64 KB all up,
   SIL OFL - see fonts/LICENSE.txt. build-demo.js inlines these as data URIs
   so the single-file preview carries its own type. */
@font-face {
  font-family: Archivo; font-style: normal; font-weight: 400;
  font-display: swap; src: url('fonts/archivo-400.woff2') format('woff2');
}
@font-face {
  font-family: Archivo; font-style: normal; font-weight: 600;
  font-display: swap; src: url('fonts/archivo-600.woff2') format('woff2');
}
@font-face {
  font-family: Archivo; font-style: normal; font-weight: 700;
  font-display: swap; src: url('fonts/archivo-700.woff2') format('woff2');
}
@font-face {
  font-family: Archivo; font-style: normal; font-weight: 800;
  font-display: swap; src: url('fonts/archivo-800.woff2') format('woff2');
}

/* ---------- theme tokens ---------- */

:root {
  /* daylight - the default. Platform values, inherited verbatim. */
  --bg:         #F3F2F2;   /* ground  */
  --panel:      #FFFFFF;   /* surface */
  --panel-2:    #EAE9E9;   /* tint-neutral - inert fills only */
  --tint-acc:   #E7F7E7;   /* the green tint - away banners, notices */
  --line:       #201E1D;   /* the 2px ink hairline - panels, table heads */
  --edge-lg:    #201E1D;   /* a control you act on directly: a button, an
                              input, a selected tab. Ink on light; on dark it
                              has to lighten, because the panel-edge grey is
                              2.55 on ink and you cannot see a button that is
                              not there. */
  --edge:       #605D5D;   /* the boundary of a QUIETER control - 5.83 on ground.
                              WCAG 1.4.11 wants 3:1 on anything you have to
                              see to know a control is there, and a 14%-ink
                              hairline is 1.6. Buttons, chips and inputs get
                              this; row dividers get --line-soft. */
  --line-soft:  rgba(32,30,29,.14);   /* dividers between rows. Decoration. */
  --text:       #201E1D;   /* ink        14.86 on ground */
  --muted:      #605D5D;   /* neutral700  5.83 on ground */
  --muted-2:    #444141;   /* neutral800  9.04 - body copy */

  --mark:       #228B22;   /* the brand green - the mark, in both themes */
  --acc:        #1D7A1D;   /* fills and lines - white on it at 5.45 */
  --acc-on:     #FFFFFF;   /* what sits on an accent fill - 5.45 */
  --acc-text:   #1D7A1D;   /* the same green; only dark flips it */

  /* One green, not two. "In" was its own dark green while the accent was
     orange; with a green brand that would be two greens meaning different
     things in the same row. So In IS the accent, Out is red, and Maybe went
     neutral - which is more honest about what "maybe" is anyway. */
  --ok:         #1D7A1D;   /* In    - the accent, white on it */
  --on-ok:      #FFFFFF;
  --ok-text:    #1D7A1D;   /* the In tally figure */
  --bad:        #B8232F;   /* Out   - 5.66 on ground */
  --on-bad:     #FFFFFF;
  --bad-line:   #B8232F;
  --hold:       #605D5D;   /* Maybe - neutral 700, ground type on it */
  --on-hold:    #F3F2F2;

  /* The ground palette. Depiction, not brand - it never leaves a diagram. */
  --turf:       #2C6B46;
  --turf-mow:   #33774F;
  --turf-dark:  #DFE7DF;
  --chalk:      #FFFFFF;
  --chalk-op:   .95;
  --mask-op:    .68;

  --team-a:     #FFFFFF;
  --team-b:     #B8232F;
  --team-c:     #F0A63C;
  /* A zone used to borrow the brand accent. A green accent on green turf is
     2.99 and unreadable, and the chrome has no business inside a diagram
     anyway - so a marked-out area takes the marker colour, which is what you
     would actually mark it with. */
  --marker:     #F0A63C;   /* cones, zones, netball ring - 3.10 on turf */
  --ball:       #A4522A;   /* leather; it reads by its chalk outline */
  /* Indoor codes are not played on grass. Depiction, same as the turf. */
  --court:      #2F5E74;   /* netball - a blue-green sports floor */
  --court-wood: #8A5A2B;   /* basketball - timber */
  --strip:      #C2B49A;   /* a cricket square: worn, pale, not green */

  --shadow:    0 1px 2px rgba(45,43,43,.14);
  --shadow-md: 0 3px 10px rgba(45,43,43,.16);
  --shadow-lg: 0 12px 32px rgba(45,43,43,.22);

  /* Brandbook §09: radius is zero. Everywhere. The pill nav is the one
     deliberate exception, and it is a full 999px, never a soft corner. */
  --radius: 0;
  --hair: 2px solid var(--line);
  --font: Archivo, system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --navh: 64px;
}

/* Night - training happens under lights. Applied when the device asks for
   dark and you have not overridden it... */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg:         #201E1D;
    --panel:      #2D2B2B;
    --panel-2:    #444141;
    --tint-acc:   #1C2E1C;
    --line:       #605D5D;
    --edge-lg:    #9B9797;   /* 5.75 on ink */
    --edge:       #9B9797;
    --line-soft:  rgba(243,242,242,.16);
    --text:       #F3F2F2;
    --muted:      #9B9797;   /* 5.75 on ink, 4.87 on panel */
    --muted-2:    #C9C6C6;

    --mark:       #228B22;   /* the logo does not change colour */
    --acc:        #1D7A1D;   /* the same fill; white on it is 5.45 anywhere */
    --acc-on:     #FFFFFF;
    --acc-text:   #3FD13F;   /* the one token that flips - 8.22 on ink */

    --ok:         #1D7A1D;
    --on-ok:      #FFFFFF;
    --ok-text:    #3FD13F;
    --bad:        #F08A80;   /* 6.84 on ink */
    --on-bad:     #201E1D;
    --bad-line:   #F08A80;
    --hold:       #9B9797;
    --on-hold:    #201E1D;

    /* Only the surround follows the theme. The ground itself, the chalk and
       the players are depiction and are identical in both - see :root. */
    --turf-dark:  #14120F;
    --mask-op:    .76;

    --shadow:    0 1px 2px rgba(0,0,0,.4);
    --shadow-md: 0 3px 10px rgba(0,0,0,.45);
    --shadow-lg: 0 12px 32px rgba(0,0,0,.55);
  }
}

/* ...and whenever you pick night explicitly. */
:root[data-theme="dark"] {
  --bg:         #201E1D;
  --panel:      #2D2B2B;
  --panel-2:    #444141;
  --tint-acc:   #1C2E1C;
  --line:       #605D5D;
  --edge-lg:    #9B9797;
  --edge:       #9B9797;
  --line-soft:  rgba(243,242,242,.16);
  --text:       #F3F2F2;
  --muted:      #9B9797;
  --muted-2:    #C9C6C6;

  --mark:       #228B22;
  --acc:        #1D7A1D;
  --acc-on:     #FFFFFF;
  --acc-text:   #3FD13F;

  --ok:         #1D7A1D;
  --on-ok:      #FFFFFF;
  --ok-text:    #3FD13F;
  --bad:        #F08A80;
  --on-bad:     #201E1D;
  --bad-line:   #F08A80;
  --hold:       #9B9797;
  --on-hold:    #201E1D;

  --turf-dark:  #14120F;
  --mask-op:    .76;

  --shadow:    0 1px 2px rgba(0,0,0,.4);
  --shadow-md: 0 3px 10px rgba(0,0,0,.45);
  --shadow-lg: 0 12px 32px rgba(0,0,0,.55);
}

/* ---------- base ---------- */

* { box-sizing: border-box; }

html {
  font-size: 15px;
  -webkit-text-size-adjust: 100%;
  background: var(--bg);
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  font-weight: 400;
  line-height: 1.55;
  overscroll-behavior-y: none;
  font-synthesis-weight: none;
  -webkit-font-smoothing: antialiased;
}

a { color: inherit; }

/* Brandbook §05: 800 for display, tightened. */
h1, h2, h3 {
  margin: 0 0 .4em; font-weight: 800; line-height: 1.06;
  letter-spacing: -.03em; text-wrap: balance;
}
h1 { font-size: 1.7rem; letter-spacing: -.04em; }
h2 { font-size: 1.15rem; }
h3 { font-size: 1rem; }
p { margin: 0 0 .8em; }
strong, b { font-weight: 700; }

code.uid {
  font-size: .78em; font-weight: 600; background: var(--panel-2);
  padding: 1px 5px; word-break: break-all; font-family: var(--font);
}
small.muted, .muted { color: var(--muted); }
.hint { color: var(--muted); font-size: .9rem; margin: .4em 0; }
.right { text-align: right; }
.hidden { display: none !important; }
.mt { margin-top: 1.2em; }

/* Brandbook §09: focus is the type stop at 3px, offset 3. */
:focus-visible { outline: 3px solid var(--acc-text); outline-offset: 3px; }

/* No page zoom.
 *
 * `touch-action: manipulation` is the half that actually does the work being
 * asked for: it removes the double-tap-to-zoom gesture, which is the one that
 * fires by accident when a coach taps a drill twice in a hurry. It leaves
 * pinch alone, and it works in every browser.
 *
 * The viewport meta carries the other half - maximum-scale=1, user-scalable=no
 * - which the iOS and Android app shells honour. Safari has deliberately
 * ignored user-scalable since iOS 10 on accessibility grounds, so anyone using
 * Add to Home Screen can still pinch. That is Apple's call, not a bug here.
 *
 * The drill canvas sets touch-action: none for itself further down, because it
 * handles its own drags and needs the browser to keep out of them entirely. */
body { touch-action: manipulation; }

/* ---------- loading ---------- */
/*
 * Brandbook §06 (03 · Working and won) and §17: the rule IS the progress bar,
 * in Green 700 on a neutral track. It is the one loading form in the product -
 * no spinners, no skeletons, no wording where a bar will do.
 *
 * Indeterminate here. §17 asks for a determinate fill wherever a count is
 * genuinely known ("copying 12 sessions" has a denominator, so use it) - set
 * a width on .loadbar and drop the animation for that case.
 *
 * Under reduced motion the global rule below turns the animation off and the
 * bar rests at 60%, which is what §06 means by "becomes a determinate fill" -
 * a still bar rather than an empty track. */
.loadtrack { height: 6px; background: var(--panel-2); overflow: hidden; }
.loadbar {
  height: 100%; width: 60%; background: var(--acc-text);
  animation: oth-bar 1.4s linear infinite;
}
@keyframes oth-bar {
  from { transform: translateX(-100%); }
  to   { transform: translateX(100%); }
}

/* Pull to refresh, on touch screens. Same 6px rule as every other loading
   state in the product - the pull fills it by hand, and letting go hands the
   fill over to the animation above. It sits UNDER the top bar (refresh.js
   measures where that ends) so the bar it belongs to stays readable, and it
   never takes a tap: the finger that drew it is already on the page. */
.ptr {
  position: fixed; left: 0; right: 0; z-index: 30;
  opacity: 0; pointer-events: none;
  transition: opacity .12s linear;
}
.ptr.on { opacity: 1; }
.ptr-track { height: 6px; background: var(--panel-2); overflow: hidden; }
.ptr-bar { height: 100%; width: 0; background: var(--acc-text); }
/* Short of the trigger the fill is muted; at the trigger it is the accent, so
   "let go now" is a colour change rather than a word. */
.ptr:not(.ready):not(.going) .ptr-bar { background: var(--muted); }
/* Let go, and the same track becomes the indeterminate bar. */
.ptr.going .ptr-bar {
  width: 60%; animation: oth-bar 1.4s linear infinite;
}
.ptr.leaving { opacity: 0; }
.ptr.leaving, .ptr:not(.on) { transition: opacity .2s linear, transform .2s ease-out; }

/* A quiet line under the last panel - the "add another club" link, which most
   people never need and nobody should trip over. */
.fine-line { margin: .2rem 0 1rem; text-align: center; }

/* The add-a-team fields sit on one line where there is room and stack when
   there is not - three short inputs that belong together. */
.row-fields { display: flex; flex-wrap: wrap; gap: .8rem; }
.row-fields .fld { flex: 1 1 12rem; min-width: 0; }
td.right { text-align: right; white-space: nowrap; }

/* A panel the demo shows but will not let you change. Muted rather than
   hidden: somebody deciding whether this app suits their club needs to see
   what is in Settings, and a page with six sections missing answers that
   question wrongly. Still readable - this is greyed out, not switched off. */
.panel.locked { opacity: .62; pointer-events: none; }
.panel.locked h2 { color: var(--muted); }
.locked-note {
  margin: 0 0 .5rem; font-size: .68rem; font-weight: 700;
  letter-spacing: .08em; text-transform: uppercase; color: var(--muted);
}
/* The controls inside read as controls, so they must not read as LIVE ones. */
.panel.locked .btn,
.panel.locked input,
.panel.locked select,
.panel.locked textarea { cursor: default; }

.boot { padding: 4rem 1.5rem; text-align: center; color: var(--muted); }
.boot.error { color: var(--bad); }

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}

/* ---------- shell ---------- */

.topbar {
  display: flex; align-items: center; gap: .7rem;
  padding: .6rem 1rem;
  padding-top: calc(.6rem + env(safe-area-inset-top));
  background: var(--panel);
  border-bottom: var(--hair);
  position: sticky; top: 0; z-index: 40;
}

/* Brandbook §18: our mark appears once, at 32px, beside the club's own name.
   Three letters down the square, T in the accent, block at the central 64%. */
.oth-tile {
  width: 32px; height: 32px; flex: 0 0 auto;
  background: #201E1D; display: grid; place-items: center;
}
.oth-tile span {
  display: flex; flex-direction: column; text-align: center;
  font-weight: 800; font-size: 9px; line-height: .88;
  letter-spacing: -.05em; color: #F3F2F2;
}
.oth-tile span i { font-style: normal; color: #228B22; }

.brand { display: flex; flex-direction: column; line-height: 1.2; min-width: 0; }
.brand strong {
  font-size: .95rem; font-weight: 800; letter-spacing: -.02em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.brand span {
  font-size: .64rem; font-weight: 600; color: var(--muted);
  text-transform: uppercase; letter-spacing: .14em;
}
.who { margin-left: auto; display: flex; align-items: center; gap: .4rem; flex: 0 0 auto; }

.theme-btn {
  display: grid; place-items: center;
  width: 36px; height: 36px; cursor: pointer;
  background: transparent; box-shadow: inset 0 0 0 2px var(--edge-lg);
  border: 0; color: var(--text);
}
.theme-btn svg { width: 18px; height: 18px; }
.theme-btn:hover { box-shadow: inset 0 0 0 2px var(--acc-text); }

/* The shell is capped and centred, the way the brandbook's own pages are.
   Capping `main` alone - which is what this used to do - left the rail and the
   content hard against the left edge with a thousand pixels of nothing beside
   them on a desktop monitor.

   1680 rather than the brandbook's 1280 because this is an application, not a
   page of prose: the drill grid gains a column or two and the diagram editor
   gains real drawing room. Anything that is actually read for meaning caps
   itself at a sensible measure further down. */
.body { display: flex; align-items: flex-start; max-width: 1680px; margin-inline: auto; }

/* Laptop rail. Labels run in full here (This week, Drill library). */
.nav {
  width: 194px; flex: 0 0 194px; padding: 1rem .6rem;
  position: sticky; top: 57px; align-self: flex-start;
  display: flex; flex-direction: column; gap: 2px;
}
.nav a {
  display: flex; align-items: center; gap: .6rem;
  padding: .55rem .7rem;
  text-decoration: none; color: var(--muted); font-size: .92rem; font-weight: 600;
}
.nav a svg { width: 18px; height: 18px; flex: 0 0 auto; }
.nav .nav-short { display: none; }
.nav a:hover { background: var(--panel-2); color: var(--text); }
/* Brandbook §16: the active item is an accent fill with ink type. It is
   chrome, so it does not count against one-accent-per-view. */
.nav a.on { background: var(--acc); color: var(--acc-on); font-weight: 700; }
.nav a.on svg { color: var(--acc-on); stroke-width: 2.6; }

main {
  flex: 1 1 auto; min-width: 0;
  padding: 1.4rem 1.6rem 5rem;
}

/* Width by what the screen is for, not one number for all of them.

   Left uncapped: the drill and block grids, which turn extra width into extra
   columns, and the two editors, where it becomes drawing room on the diagram.

   Capped: everything read top to bottom. A run sheet stretched across a
   desktop monitor puts a drill name 300px from its own duration, which is
   worse than the whitespace it was meant to fill. */
/* The cap goes on the shell, not on main, so the rail travels with the content
   and the pair stays centred. Capping main alone leaves the rail stranded out
   at the edge with a gap between it and the page it belongs to. */
.body[data-route="/"],
.body[data-route="/history"],
.body[data-route="/sessions"],
.body[data-route="/settings"],
.body[data-route="/rollover"] { max-width: 1374px; }

/* ---------- primitives ---------- */

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .4rem;
  min-height: 40px; padding: .45rem .95rem;
  box-shadow: inset 0 0 0 2px var(--edge-lg); border: 0;
  background: transparent; color: var(--text);
  font: inherit; font-size: .88rem; font-weight: 700; letter-spacing: -.01em;
  cursor: pointer; text-decoration: none; white-space: nowrap;
}
.btn:hover { box-shadow: inset 0 0 0 2px var(--acc-text); }
/* One accent action per view. If a screen has two primaries, one is not. */
.btn.primary {
  background: var(--acc); color: var(--acc-on); box-shadow: none;
}
.btn.primary:hover { background: var(--acc); filter: brightness(1.06); }
.btn.ghost { background: transparent; box-shadow: inset 0 0 0 2px var(--edge); color: var(--muted); }
.btn.ghost:hover { box-shadow: inset 0 0 0 2px var(--edge-lg); color: var(--text); }
.btn.danger { color: var(--bad); box-shadow: inset 0 0 0 2px var(--bad-line); }
.btn.danger:hover { box-shadow: inset 0 0 0 2px var(--bad); }
.btn.sm { min-height: 34px; padding: .25rem .6rem; font-size: .8rem; }
.btn.wide { width: 100%; }
.btn:disabled { background: var(--panel-2); color: var(--muted); box-shadow: none; cursor: not-allowed; }

.linkish {
  background: none; border: 0; color: var(--acc-text); cursor: pointer;
  font: inherit; font-weight: 600; padding: 0;
  text-decoration: underline; text-underline-offset: 3px;
}
.linkish.sm { font-size: .82rem; }

/* Brandbook §14: a word, then a colour. Every chip carries its label. */
.pill {
  display: inline-block; max-width: 100%; padding: .12rem .5rem;
  background: transparent; box-shadow: inset 0 0 0 2px var(--edge);
  font-size: .68rem; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--muted);
}
/* Coach is a plain status label, so it stays neutral. Demo is a warning about
   what you are looking at, so it gets the accent - one per view. */
.pill.coach { color: var(--text); box-shadow: inset 0 0 0 2px var(--edge-lg); }
.pill.warn { color: var(--acc-text); box-shadow: inset 0 0 0 2px var(--acc-text); }

/* A focus tag is a filled chip, so its type is --muted-2 rather than --muted:
   Neutral 700 on the tint is 5.38 in daylight but only 3.5 at night, and a
   filled surface needs its own text colour rather than the page's. */
.tag {
  display: inline-block; padding: .1rem .45rem; margin: 0 .25rem .25rem 0;
  background: var(--panel-2);
  font-size: .74rem; font-weight: 600; color: var(--muted-2);
}

/* Brandbook §14, corrected from the prototype: phases are words, not hues.
   The one exception is Away, which changes where a player has to go. */
.phase {
  display: inline-block; padding: .1rem .5rem;
  font-size: .68rem; font-weight: 700; letter-spacing: .05em;
  text-transform: uppercase; white-space: nowrap;
  color: var(--muted); box-shadow: inset 0 0 0 2px var(--edge);
}
.phase.away { color: var(--acc-text); box-shadow: inset 0 0 0 2px var(--acc-text); }

.page-head {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 1rem; margin-bottom: 1.2rem; flex-wrap: wrap;
}
.page-head .sub { color: var(--muted); font-size: .88rem; margin: 0; }
.back { color: var(--muted); text-decoration: none; font-size: .82rem; font-weight: 600; display: inline-block; margin-bottom: .2rem; }
.back:hover { color: var(--text); }
.row-actions { display: flex; gap: .5rem; flex-wrap: wrap; }
.eyebrow {
  text-transform: uppercase; letter-spacing: .18em; font-size: .66rem;
  color: var(--muted); font-weight: 600;
}

.panel {
  background: var(--panel); box-shadow: inset 0 0 0 2px var(--line);
  padding: 1rem 1.1rem; margin-bottom: 1rem;
}
.panel-head { display: flex; align-items: center; justify-content: space-between; gap: .6rem; margin-bottom: .7rem; flex-wrap: wrap; }
.panel-head h2 { margin: 0; }
.pane { background: var(--panel); box-shadow: inset 0 0 0 2px var(--line); padding: 1rem 1.1rem; }
.pane-title { margin-bottom: .7rem; }
.pane.read h3 {
  margin-top: 1.2em; color: var(--muted); font-size: .68rem; font-weight: 600;
  text-transform: uppercase; letter-spacing: .18em;
}
.pane.read h3:first-child { margin-top: 0; }
.pane.read ul { margin: 0 0 .8em; padding-left: 1.1em; }
.pane.read li { margin-bottom: .3em; }

/* Running text stops at a measure you can actually read. The shell is allowed
   to be 1680 wide because grids and the diagram editor use every pixel of it;
   a coaching point set in one 1400px line does not. Tables, grids and the
   canvas are deliberately not capped here. */
.pane.read p, .pane.read li, .pane.read ul { max-width: 78ch; }
.empty p, .notes, .sub { max-width: 78ch; }

.split { display: grid; grid-template-columns: 1fr; gap: 1rem; align-items: start; }
/* A grid item's minimum is its min-content unless you say otherwise, so a long
   run-sheet row or a wide pill pushed this column past the screen and took the
   whole page sideways with it. The track decides the width; the content wraps
   to fit. */
.split > * { min-width: 0; }

/* Two columns only once there is room for a usable drawing canvas next to the
   form. On iPad landscape (1024) a split leaves the ground about 350px wide,
   which is no good for placing cones, so it stays stacked and full width. */
@media (min-width: 1100px) {
  .split { grid-template-columns: 1fr 1.25fr; }
  .split.wide-left { grid-template-columns: 1.35fr 1fr; }
}

/* Stacked: sketch first, then write it up. */
@media (max-width: 1099px) {
  .split > .pane:last-child:not(:only-child) { order: -1; }
  .split.wide-left > .pane:last-child { order: 0; }
  .stage { max-width: 900px; }
}

/* Brandbook §17: name the thing, say what fills it, never an illustration. */
/* An empty state stands in for a panel in the page flow - "Nothing scheduled"
   sits exactly where the next session's card would - so it has to keep a
   panel's rhythm. Without the margin it butts straight up against the Recent
   panel under it on This week, and against Already run on Sessions. */
.empty {
  text-align: center; padding: 3rem 1rem; color: var(--muted);
  box-shadow: inset 0 0 0 2px var(--edge);
  border: 0; outline: 2px dashed var(--edge); outline-offset: -2px;
  margin-bottom: 1rem;
}
.empty h3 { color: var(--text); }

/* ---------- the splash ---------- */
/*
 * Layer 2 of two. Capacitor holds a native splash from the moment the icon is
 * tapped; this is the same white with the mark rising on it, and the two MUST
 * carry the same ground or the join shows as a flash. The other copies of that
 * colour live in capacitor.config.json and tools/build-assets.js, and
 * test/native.test.js fails if any of the three drift apart.
 *
 * Deliberately NOT themed. It is a brand moment, the one place the mark gets to
 * be the whole screen, so it does not follow daylight/night - which is why the
 * colours below are literals rather than tokens and cannot be reached by the
 * dark blocks further down.
 *
 * EVERY RESTING STATE HERE IS THE FINISHED STATE. The keyframes have a `from`
 * and no `to`, so they animate toward what the element already is. Reduced
 * motion - which this file turns off globally - therefore leaves the mark
 * sitting there complete, rather than parked off-screen behind a transform
 * that will never run. A splash that fails to a blank page is worse than one
 * that never animated.
 */
.splash {
  position: fixed; inset: 0; z-index: 200;
  background: #FFFFFF;
  display: flex; align-items: center; justify-content: center;
  padding: 2rem;
  transition: opacity .26s ease;
}
.splash.going { opacity: 0; pointer-events: none; }

/* Sized to the mark, so the rule under it is the mark's own width rather than
   a number that needs maintaining. Nudged left because a mathematically
   centred block of left-aligned type reads as sitting slightly right. */
.splash-inner {
  width: fit-content; max-width: calc(100vw - 4rem);
  transform: translateX(-.25em);
}

/* Block, because the words are block-level and stack on their own - flex was
   only ever buying the left alignment block gives for free.

   The masks are called .splash-word rather than anything shorter: `.sw` is
   already a 10x10 team-colour swatch further down this file, and inheriting
   its width and height squashed every word to a sliver. */
.splash-mark {
  display: block;
  font-weight: 800; font-size: clamp(2.75rem, 15vw, 4.5rem);
  letter-spacing: -.05em; text-transform: uppercase;
  color: #201E1D;           /* 16.60 on white */
}
/* The mask each word rises out of. Three traps live in this one rule:

   - line-height must not go under the font size. The brandbook stacks at .88
     and doing that here cut the tops off the O and the H, because inside
     overflow:hidden the mask IS the line box. It is 1.02 here, and the tight
     stack comes from the negative margin instead - 1.02 - .14 lands back on
     the .88 the brandbook asks for.
   - negative tracking shortens the last glyph's advance, so the mask trimmed
     the right of the N in TRAIN. padding-right gives the word its edge back.
   - the padding has to sit on the mask, not the word, or it shifts the stack. */
.splash-mark .splash-word {
  display: block; overflow: hidden;
  line-height: 1.02; margin-bottom: -.14em; padding-right: .12em;
}
.splash-mark .splash-word:last-child { margin-bottom: 0; }
.splash-mark b { display: block; font-weight: inherit; }
.splash-mark .splash-word:nth-child(2) b { color: #228B22; }   /* 4.39 on white - large type only */

.splash-rule { height: 4px; width: 100%; background: #201E1D; margin: .9rem 0 .8rem; }

.splash-tag {
  margin: 0; font-size: .95rem; font-weight: 600;
  letter-spacing: .01em; color: #605D5D;   /* 6.52 on white */
}
/* Shown only when there is genuinely something to wait for.
   
   It is tempting to fade this in on a timer with the rest of the sequence, but
   on a warm cache the splash leaves a quarter-second later and a progress bar
   that appears and vanishes in 260ms reads as a glitch rather than as
   progress. So app.js adds .working only if the app is STILL not ready once
   the mark has finished arriving - which makes the bar mean what it says. */
.splash .loadtrack { margin-top: 1.4rem; background: #EAE9E9; }
.splash .loadbar { background: #1D7A1D; }
.splash-load { opacity: 0; transition: opacity .3s ease; }
.splash.working .splash-load { opacity: 1; }

/* Rise, then wipe, then the tagline - the two locked behaviours from the
   brandbook, in that order, once. About 1.64s end to end. */
@keyframes splash-rise { from { transform: translateY(105%); } }
@keyframes splash-wipe { from { transform: scaleX(0); } }
@keyframes splash-fade { from { opacity: 0; } }

.splash-mark b { animation: splash-rise 620ms cubic-bezier(.2, .8, .2, 1) both; }
.splash-mark .splash-word:nth-child(2) b { animation-delay: 120ms; }
.splash-mark .splash-word:nth-child(3) b { animation-delay: 240ms; }
.splash-rule {
  transform-origin: left;
  animation: splash-wipe 420ms cubic-bezier(.2, .8, .2, 1) 880ms both;
}
.splash-tag { animation: splash-fade 320ms ease 1320ms both; }

/* ---------- forms ---------- */

/* EVERY control that takes input, not just the ones wrapped in a .fld. The
   drill-diagram editor and the run sheet build their own inputs, and without
   this they arrive as bare browser defaults with nothing to say they can be
   tapped.

   `appearance: none` is the load-bearing part on a phone. WKWebView draws its
   own select and date field and ignores the ring below entirely, which is why
   dropdowns and dates looked flat and untappable on iOS. Taking the native
   drawing away means drawing the select's arrow ourselves, below.

   `min-width: 0` matters as much as the border: an input's natural minimum is
   its `size` attribute, around twenty characters, and inside a flex or grid
   track that becomes a floor nothing can shrink past. That is what made the
   session page scroll sideways. */
input, select, textarea, .search {
  width: 100%; min-width: 0;
  padding: .5rem .65rem;
  background: var(--panel);
  box-shadow: inset 0 0 0 2px var(--edge-lg); border: 0; border-radius: 0;
  color: var(--text); font: inherit; font-size: .92rem;
  -webkit-appearance: none; appearance: none;
}
input:focus, select:focus, textarea:focus, .search:focus {
  outline: 3px solid var(--acc-text); outline-offset: 2px;
}
input::placeholder, textarea::placeholder { color: var(--muted); opacity: 1; }

/* A read-only field is not a field you fill in, and it should not sit there
   looking like one. Used by the demo's team code, which is already filled in
   and has only one correct value. Still selectable - somebody may want to
   copy it - so not `disabled`, which would also drop it from the form. */
input[readonly] {
  background: var(--panel-2); color: var(--muted);
  box-shadow: inset 0 0 0 2px var(--edge); cursor: default;
}
input[readonly]:focus { outline-color: var(--edge); }

/* Tick boxes keep the platform's own drawing. They already read as controls,
   and stripping the appearance leaves an invisible square. */
input[type="checkbox"], input[type="radio"] {
  width: auto; min-width: 0; padding: 0; box-shadow: none;
  -webkit-appearance: auto; appearance: auto;
}

/* The arrow the native select no longer draws. One grey that carries on both
   the white ground and the ink one, because it is an affordance, not text. */
select {
  padding-right: 2.1rem; cursor: pointer;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%238A8686' stroke-width='1.6' stroke-linecap='square'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right .7rem center; background-size: 10px 6px;
}

/* Safari centres and shrinks a stripped date/time field, and collapses an empty
   one to nothing. Give them the same body as every other control. */
input[type="date"], input[type="time"], input[type="datetime-local"] {
  min-height: 38px; text-align: left; cursor: pointer;
}
input[type="date"]::-webkit-date-and-time-value,
input[type="time"]::-webkit-date-and-time-value { text-align: left; }
input[type="date"]::-webkit-calendar-picker-indicator,
input[type="time"]::-webkit-calendar-picker-indicator { opacity: .55; cursor: pointer; }

.fld { display: block; margin-bottom: .85rem; }
.fld > span {
  display: block; font-size: .74rem; font-weight: 700; color: var(--text);
  margin-bottom: .3rem; letter-spacing: .01em;
}
.fld > span em { font-style: normal; font-weight: 400; color: var(--muted); }
.fld textarea { resize: vertical; line-height: 1.55; }
/* A field whose button belongs to the field rather than to the form - the
   Verify beside a mobile number on sign-up. */
.verify-in { display: flex; gap: .5rem; align-items: stretch; }
.verify-in input { flex: 1 1 auto; min-width: 0; }
.verify-in .btn { flex: 0 0 auto; white-space: nowrap; }
.verify-in input[readonly] { color: var(--muted); box-shadow: inset 0 0 0 2px var(--edge); }
.sent { margin: 0 0 .6rem; font-size: .88rem; color: var(--muted); }
.sent strong { color: var(--text); font-weight: 700; }
.sent .linkish { font-size: inherit; }

.fld-row { display: flex; gap: .7rem; flex-wrap: wrap; }
.fld-row .fld { flex: 1 1 140px; min-width: 120px; }
.fld-row .fld.grow { flex: 2 1 200px; }
.fld.inline { display: flex; align-items: center; gap: .4rem; margin: 0; }
.fld.inline > span { margin: 0; white-space: nowrap; }
.fld.inline input, .fld.inline select { width: auto; min-width: 90px; }
.fld.inline.wide input { min-width: 180px; }
.fld.check { display: flex; align-items: center; gap: .45rem; align-self: flex-end; padding-bottom: .5rem; flex: 1 1 auto; }
.fld.check input { width: 20px; height: 20px; flex: 0 0 auto; accent-color: var(--acc-text); }
.fld.check span { margin: 0; }
.err { color: var(--bad); font-size: .85rem; font-weight: 600; min-height: 1.2em; }

.filters { margin-bottom: 1.1rem; }
/* Filter groups say what they filter. The phase chips read as themselves;
   the focus tags are just a row of words without a name on them. */
.filter-group { margin-top: .7rem; }
.filter-head {
  margin: 0 0 .35rem; font-size: .7rem; font-weight: 700;
  letter-spacing: .08em; text-transform: uppercase; color: var(--muted);
}
/* The My drills tick lives with the focus tags, below them. The whole label is
   the target, not the 18px box - a tick box on its own is the smallest control
   in the app and the one most likely to be tapped standing on a boundary line. */
.filter-check {
  display: inline-flex; align-items: center; gap: .5rem;
  cursor: pointer; -webkit-user-select: none; user-select: none;
}
.filter-check input {
  width: 18px; height: 18px; flex: 0 0 auto; accent-color: var(--acc-text);
  cursor: pointer;
}
.filter-check span { font-size: .85rem; color: var(--text); }

.search { margin-bottom: .6rem; }
.chips { display: flex; flex-wrap: wrap; gap: .35rem; margin-bottom: .45rem; }
.chip {
  padding: .3rem .7rem; cursor: pointer;
  background: transparent; box-shadow: inset 0 0 0 2px var(--edge); border: 0;
  color: var(--muted); font: inherit; font-size: .8rem; font-weight: 600; white-space: nowrap;
}
.chip:hover { color: var(--text); box-shadow: inset 0 0 0 2px var(--edge-lg); }
/* Selected filters are the accent doing accent work: a fill, ink type. */
.chip.on {
  background: var(--acc); color: var(--acc-on); box-shadow: none; font-weight: 700;
}
.chips.small .chip { font-size: .78rem; padding: .2rem .6rem; }
/* "yours" on the age chip that matches the side's own age group. A quieter
   weight so it reads as a note on the label rather than part of the name -
   and it inherits colour, so it stays legible once the chip is selected and
   the ground underneath becomes the accent. */
.chip .chip-note {
  font-weight: 500; font-size: .92em; opacity: .72; margin-left: .3em;
}

/* ---------- add to home screen ----------
   A card that sits over the page, not a strip welded to the bottom edge. It
   is an interruption and should read as one, and it has to survive every
   repaint the router does without being re-rendered by it.

   OPAQUE, from a token that exists. The first version of this used
   var(--surface), which is not a token in this file - so the background
   resolved to nothing, the banner was transparent, and the page scrolled
   underneath the words. --panel is #FFFFFF and #2D2B2B, which is what every
   other raised surface here uses.

   Safe-area padding so it clears the home indicator on a notched phone,
   where the buttons would otherwise sit under the swipe bar. */
.installbar {
  position: fixed; z-index: 60;
  left: .75rem; right: .75rem;
  bottom: calc(.75rem + env(safe-area-inset-bottom, 0px));
  max-width: 30rem; margin: 0 auto;
  background: var(--panel); color: var(--text);
  box-shadow: inset 0 0 0 2px var(--line), var(--shadow-lg);
  padding: 1rem 1.1rem 1.1rem;
}
/* The close sits in the corner and keeps its own 44px, so the text can run
   the full width without wrapping around it. */
.installbar .ib-close {
  position: absolute; top: .25rem; right: .25rem;
  width: 44px; height: 44px; padding: 0;
  display: grid; place-items: center;
  background: transparent; border: 0; cursor: pointer;
  color: var(--muted); font: inherit; line-height: 1;
}
.installbar .ib-close:hover { color: var(--text); }
.installbar .ib-close svg { width: 16px; height: 16px; }
.installbar .ib-text { padding-right: 2.6rem; }
.installbar .ib-text strong {
  display: block; font-size: 1rem; font-weight: 800; color: var(--text);
}
.installbar .ib-text p {
  margin: .3rem 0 0; font-size: .88rem; line-height: 1.5; color: var(--muted-2);
}
.installbar .ib-acts { display: flex; gap: .5rem; margin-top: .9rem; }
.installbar .ib-acts .btn { flex: 1; }
/* The glyph rides in the sentence at the size of the words around it, so
   "tap [icon]" reads as one instruction rather than text and a picture. */
.installbar .ib-glyph {
  width: 1.1em; height: 1.1em; vertical-align: -.22em; margin: 0 .12em;
  color: var(--acc-text);
}

/* ---------- cards & grids ---------- */

.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: .9rem; }
/* "Nothing matches" is a message about the whole library, so it reads as one -
   sitting in a single 220px column made it look like a result. */
.grid > .empty { grid-column: 1 / -1; }
.grid.blocks { grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); }

.card {
  display: block; background: var(--panel);
  box-shadow: inset 0 0 0 2px var(--line);
  overflow: hidden; text-decoration: none;
  transition: box-shadow .12s;
}
.card:hover { box-shadow: inset 0 0 0 2px var(--acc-text); }
/* The thumbnail owns the 4:3 box and the diagram fills it.
   `.pitch` carries `height: 100%` for the editor stage, and that stops its own
   `aspect-ratio` from resolving in a card - the SVG then sized itself portrait,
   grew past the card, and `overflow: hidden` quietly ate the drill's name.
   So the ratio belongs to the frame here, not to the drawing. */
.drill-card .thumb {
  aspect-ratio: 4 / 3; overflow: hidden;
  background: var(--turf-dark); border-bottom: var(--hair);
}
.drill-card .thumb .pitch { width: 100%; height: 100%; aspect-ratio: auto; }
.card-body { padding: .65rem .75rem .75rem; }
.card-body h3 { margin: 0 0 .35rem; font-size: .93rem; }
.meta { display: flex; align-items: center; gap: .45rem; flex-wrap: wrap; font-size: .78rem; color: var(--muted); }
.meta .mins { font-variant-numeric: tabular-nums; font-weight: 600; }
.tags { margin-top: .4rem; }
/* A player's card says when the drill is on instead of listing focus tags -
   "On Thu, 17 Sept" is the thing they came to find out. */
.card-when {
  margin: .4rem 0 0; font-size: .78rem; font-weight: 700;
  color: var(--acc-text); font-variant-numeric: tabular-nums;
}
.block-card { padding: .9rem 1rem; }
.block-card h3 { font-size: 1rem; }

.ticks { margin: .4em 0; padding-left: 1.05em; }
.ticks li { margin-bottom: .25em; }
.ticks.small li { font-size: .84rem; color: var(--muted); }

/* Brandbook §06: the rule is the progress bar. */
.bar { height: 6px; background: var(--panel-2); overflow: hidden; margin: .35rem 0 .5rem; }
.bar i { display: block; height: 100%; background: var(--acc-text); }

/* ---------- tables & rows ---------- */

.tbl { width: 100%; border-collapse: collapse; font-size: .89rem; }
.tbl td, .tbl th { padding: .5rem .5rem; border-bottom: 1px solid var(--line-soft); vertical-align: top; }
.tbl th {
  font-size: .64rem; font-weight: 600; text-transform: uppercase;
  letter-spacing: .12em; color: var(--muted); text-align: left;
  border-bottom: var(--hair);
}
.tbl tr:last-child td { border-bottom: 0; }
/* The clock column is the accent as type, so the type stop. */
.tbl .tm {
  font-variant-numeric: tabular-nums; color: var(--acc-text);
  white-space: nowrap; width: 3.6rem; font-weight: 700;
}
/* Seven underlined drill names in a run sheet is seven pieces of noise. The
   row is obviously tappable; the underline arrives on hover. */
.tbl a { color: var(--text); text-decoration: none; font-weight: 600; }
.tbl a:hover { text-decoration: underline; text-underline-offset: 3px; }

.row-link {
  display: flex; align-items: center; gap: .8rem;
  padding: .65rem .2rem; border-bottom: 1px solid var(--line-soft);
  text-decoration: none; font-size: .92rem; flex-wrap: wrap;
}
.row-link:last-child { border-bottom: 0; }
.row-link:hover { color: var(--acc-text); }
.row-link.static:hover { color: inherit; }
.row-link .date { color: var(--muted); width: 7.5rem; flex: 0 0 auto; font-size: .84rem; font-variant-numeric: tabular-nums; }
.row-link .ttl { flex: 1 1 40%; font-weight: 600; }
.row-link .muted { font-size: .82rem; }

/* This week's session. The accent edge is the whole emphasis - no fill. */
.hero { box-shadow: inset 0 0 0 2px var(--line); border-left: 6px solid var(--acc-text); }
.hero-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem; margin-bottom: .6rem; flex-wrap: wrap; }
.hero-head h2 { font-size: 1.35rem; margin: .15rem 0 .1rem; }
.focus-line { font-size: .9rem; color: var(--muted); border-left: 2px solid var(--line-soft); padding-left: .6rem; }

/* ---------- away sessions (gym, pool, anywhere but the ground) ---------- */

.pill.cost { color: var(--acc-text); box-shadow: inset 0 0 0 2px var(--acc-text); }

.away-thumb {
  aspect-ratio: 4 / 3; display: flex; flex-direction: column; justify-content: center;
  gap: .25rem; padding: .8rem .9rem; text-align: left;
  background: var(--tint-acc);
  border-left: 6px solid var(--acc-text);
}
.away-venue { font-weight: 800; font-size: .95rem; line-height: 1.2; letter-spacing: -.02em; }
.away-cost {
  font-size: 1.35rem; font-weight: 800; color: var(--acc-text);
  font-variant-numeric: tabular-nums; letter-spacing: -.03em;
}
.away-addr {
  font-size: .76rem; color: var(--muted); line-height: 1.3;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}

/* A drill nobody drew. Plenty never need a diagram - a tempo run, a gym
   session, a cool-down - and a coach writing their own starts on an empty
   canvas they may never touch, so this is an ordinary state, not a failure.
   It reads as a note rather than as a picture: no tint, no accent rail, just
   the two things a library is scanned for and the setup line underneath.
   Deliberately NOT the away treatment - an away session is a different KIND of
   thing, and borrowing its green would say so wrongly. */
.plain-thumb {
  aspect-ratio: 4 / 3; display: flex; flex-direction: column; justify-content: center;
  gap: .3rem; padding: .8rem .9rem; text-align: left;
  background: var(--panel-2); box-shadow: inset 0 0 0 2px var(--edge);
}
.plain-kind {
  font-size: .68rem; font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--muted);
}
.plain-gear { font-weight: 700; font-size: .9rem; line-height: 1.25; letter-spacing: -.01em; }
.plain-setup {
  font-size: .76rem; color: var(--muted); line-height: 1.35;
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
}

/* The run-sheet picker has 60px, so one line and no ground. */
.pick-thumb.pick-plain {
  aspect-ratio: 4 / 3; display: grid; place-items: center; padding: .2rem;
  background: var(--panel-2); box-shadow: inset 0 0 0 2px var(--edge);
  color: var(--muted); font-weight: 700; font-size: .6rem; line-height: 1.15;
  text-align: center; overflow: hidden;
}

/* A read-only drill with nothing drawn has no second column to fill, so it
   does not claim one. Same gap and alignment as .split, one track. */
.solo { display: grid; grid-template-columns: 1fr; gap: 1rem; align-items: start; }
.solo > * { min-width: 0; }

.away-card {
  box-shadow: inset 0 0 0 2px var(--line); border-left: 6px solid var(--acc-text);
  padding: 1rem 1.1rem; background: var(--panel);
}
.away-card .eyebrow { color: var(--acc-text); }
.away-card h2 { margin: .2rem 0 .5rem; font-size: 1.25rem; }
.away-address { font-size: .95rem; margin-bottom: .8rem; white-space: pre-line; }
.away-kv { margin-top: 1rem; grid-template-columns: 5.5rem 1fr; }
.away-kv strong { font-size: 1.05rem; }

.away-note {
  margin-top: 1rem; padding-top: .8rem; border-top: 1px solid var(--line-soft);
  font-size: .82rem; color: var(--muted);
}

/* Banner on a session that is happening somewhere else. Brandbook §13. */
.away-banner {
  border-left: 6px solid var(--acc-text); background: var(--tint-acc);
  padding: .6rem .8rem; margin: 0 0 .85rem;
  display: flex; flex-direction: column; gap: .3rem;
}
.away-line { display: flex; align-items: baseline; gap: .5rem; flex-wrap: wrap; font-size: .88rem; min-width: 0; }
.away-line > * { min-width: 0; overflow-wrap: anywhere; }
.away-line a { color: var(--acc-text); font-weight: 600; text-decoration: underline; text-underline-offset: 3px; }
.away-line.cost strong { color: var(--acc-text); font-variant-numeric: tabular-nums; }

.hist-away {
  margin: .3rem 0 0 6.5rem; font-size: .85rem; color: var(--acc-text); font-weight: 600;
}
@media (max-width: 560px) { .hist-away { margin-left: 0; } }

/* ---------- availability ---------- */

.avail { margin-top: 1rem; padding-top: .9rem; border-top: 1px solid var(--line-soft); }
.panel > .avail:first-child, #avail-session > .avail { margin-top: 0; padding-top: 0; border-top: 0; }
.avail-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: .6rem; flex-wrap: wrap; margin-bottom: .7rem;
}
.avail-head h3 { margin: 0; font-size: 1rem; }
.avail-tally { display: flex; gap: .8rem; flex-wrap: wrap; font-size: .8rem; color: var(--muted); }
.cnt b { font-variant-numeric: tabular-nums; font-size: 1.05rem; font-weight: 800; }
.cnt-in b { color: var(--ok-text); }
.cnt-out b { color: var(--bad); }
.cnt-maybe b { color: var(--muted); }

/* Brandbook §14: In takes the accent, Out is red, Maybe is neutral - and
   the word is always present, because three fills read the same to plenty of
   people. */
.avail-btns { display: flex; gap: .5rem; }
.av-btn {
  flex: 1 1 0; min-height: 48px; cursor: pointer;
  background: transparent; box-shadow: inset 0 0 0 2px var(--edge); border: 0;
  color: var(--text); font: inherit; font-size: .95rem; font-weight: 700;
}
.av-btn:hover { box-shadow: inset 0 0 0 2px var(--edge-lg); }
.av-in.on    { background: var(--ok);  color: var(--on-ok);  box-shadow: none; }
.av-out.on   { background: var(--bad); color: var(--on-bad); box-shadow: none; }
.av-maybe.on { background: var(--hold); color: var(--on-hold); box-shadow: none; }
.avail-you { margin: .6rem 0 0; font-size: .86rem; color: var(--muted); }
.avail-you em { font-style: normal; font-weight: 700; color: var(--text); }

.avail-lists {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: .8rem; margin-top: 1rem;
}
.avail-group h4 {
  margin: 0 0 .35rem; font-size: .64rem; text-transform: uppercase;
  letter-spacing: .16em; font-weight: 600; color: var(--muted);
}
.avail-group ul { list-style: none; margin: 0; padding: 0; }
.avail-group li { font-size: .87rem; padding: .12rem 0; }
.avail-group li em { font-style: normal; color: var(--muted); font-size: .8rem; }
.avail-group.muted-group li { color: var(--muted); }

/* ---------- rollover ---------- */

.tbl.rollover { max-width: 640px; }
.tbl.rollover td, .tbl.rollover th { padding: .4rem .5rem; }
.tbl.rollover th:nth-child(1), .tbl.rollover td:nth-child(1),
.tbl.rollover th:nth-child(2), .tbl.rollover td:nth-child(2) { width: 8.5rem; white-space: nowrap; }
.tbl.rollover tr.dim td { opacity: .55; }
.tbl.rollover .tm { color: var(--acc-text); width: 8.5rem; }

/* ---------- history ---------- */

.week-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: .8rem; flex-wrap: wrap;
  padding-bottom: .7rem; margin-bottom: .3rem; border-bottom: var(--hair);
}
.week-head h2 { margin: 0; font-size: 1.05rem; }
.week-head .sub { margin: 0; font-size: .8rem; color: var(--muted); }
.week-meta { display: flex; align-items: center; gap: .4rem; flex-wrap: wrap; }

.hist { padding: .85rem 0; border-bottom: 1px solid var(--line-soft); }
.hist:last-child { border-bottom: 0; padding-bottom: 0; }
.hist-head { display: flex; align-items: baseline; gap: .6rem; flex-wrap: wrap; }
.hist-date {
  color: var(--acc-text); font-size: .82rem; font-weight: 700;
  font-variant-numeric: tabular-nums; flex: 0 0 auto; min-width: 6.5rem;
}
.hist-title { font-weight: 700; text-decoration: none; }
.hist-title:hover { text-decoration: underline; text-underline-offset: 3px; }
.hist-head .muted { font-size: .82rem; }
.hist-focus {
  margin: .35rem 0 .1rem 6.5rem; font-size: .86rem; color: var(--muted);
  border-left: 2px solid var(--line-soft); padding-left: .6rem;
}
.hist-drills { list-style: none; margin: .45rem 0 0 6.5rem; padding: 0; }
.hist-drills li {
  display: flex; align-items: baseline; gap: .5rem; flex-wrap: wrap;
  padding: .18rem 0; font-size: .88rem;
}
.hist-drills a { text-decoration: none; }
.hist-drills a:hover { color: var(--acc-text); }
.hist-drills .mins { color: var(--muted); font-size: .8rem; font-variant-numeric: tabular-nums; }
.hist-drills em { font-style: normal; color: var(--muted); font-size: .8rem; }
/* A left edge rather than a filled block - muted type on a tint fails on ink. */
.hist-note {
  margin: .55rem 0 0 6.5rem; font-size: .85rem; color: var(--muted);
  border-left: 2px solid var(--line-soft); padding: .1rem 0 .1rem .6rem;
}
.hist.off .hist-title { color: var(--muted); text-decoration: line-through; }
.pill.off { color: var(--bad); box-shadow: inset 0 0 0 2px var(--bad-line); }

h3.mini {
  margin: 1.2em 0 .4em; font-size: .64rem; text-transform: uppercase;
  letter-spacing: .16em; font-weight: 600; color: var(--muted);
}
h3.mini:first-of-type { margin-top: .3em; }
.tally { list-style: none; margin: 0; padding: 0; }
.tally li {
  display: flex; align-items: center; gap: .6rem;
  padding: .32rem 0; border-bottom: 1px solid var(--line-soft); font-size: .89rem;
}
.tally li:last-child { border-bottom: 0; }
.tally li > :first-child { flex: 1 1 auto; min-width: 0; }
.tally b { font-variant-numeric: tabular-nums; color: var(--acc-text); font-weight: 800; flex: 0 0 auto; }
.tally .tag { margin: 0; }

@media (max-width: 560px) {
  .hist-focus, .hist-drills, .hist-note { margin-left: 0; }
  .hist-date { min-width: 0; }
}

.kv { display: grid; grid-template-columns: 9rem 1fr; gap: .3rem .8rem; margin: 0; font-size: .9rem; }
.kv dt { color: var(--muted); font-size: .8rem; }
.kv dd { margin: 0; min-width: 0; }

/* ---------- session run sheet ---------- */

.run-item {
  display: grid;
  grid-template-columns: 3.4rem 1fr auto auto;
  grid-template-areas: "time main mins ctl" ". note note note";
  align-items: start; gap: .4rem .6rem;
  padding: .6rem 0; border-bottom: 1px solid var(--line-soft);
}
.run-item:last-child { border-bottom: 0; }
.run-time {
  grid-area: time; font-variant-numeric: tabular-nums; color: var(--acc-text);
  font-size: .88rem; font-weight: 700; padding-top: .3rem;
}
.run-main { grid-area: main; min-width: 0; }
.run-top { display: flex; align-items: center; gap: .45rem; flex-wrap: wrap; min-width: 0; }
.run-top > * { min-width: 0; }
.run-note {
  grid-area: note; width: 100%; min-width: 0; padding: .35rem .5rem; font-size: .84rem;
  background: var(--bg); box-shadow: inset 0 0 0 2px var(--edge); border: 0; color: var(--text);
  font-family: inherit;
}
.run-note:focus { outline: 3px solid var(--acc-text); outline-offset: 1px; }
.run-mins { grid-area: mins; display: flex; align-items: center; gap: .2rem; }
.run-mins { min-width: 0; }
.run-mins input {
  width: 3.6rem; flex: 0 0 auto; padding: .3rem; text-align: center; background: var(--bg);
  box-shadow: inset 0 0 0 2px var(--edge); border: 0; color: var(--text);
  font: inherit; font-size: .88rem; font-weight: 600; font-variant-numeric: tabular-nums;
}
.run-mins span { font-size: .72rem; color: var(--muted); }
.run-ctl { grid-area: ctl; display: flex; gap: .2rem; flex: 0 0 auto; }
.icon {
  width: 30px; height: 30px; cursor: pointer;
  background: transparent; box-shadow: inset 0 0 0 2px var(--edge); border: 0;
  color: var(--muted); font-size: .72rem; font-weight: 700; line-height: 1;
  display: grid; place-items: center; flex: 0 0 auto;
}
.icon:hover:not(:disabled) { color: var(--text); box-shadow: inset 0 0 0 2px var(--acc-text); }
.icon:disabled { opacity: .35; cursor: not-allowed; }
.icon.danger { color: var(--bad); font-size: 1.1rem; }

.picker { max-height: 640px; overflow-y: auto; display: flex; flex-direction: column; gap: .35rem; -webkit-overflow-scrolling: touch; }
.pick {
  display: flex; align-items: center; gap: .6rem; width: 100%; text-align: left;
  padding: .4rem .5rem; cursor: pointer;
  background: transparent; box-shadow: inset 0 0 0 2px var(--edge); border: 0;
  color: var(--text); font: inherit;
}
.pick:hover { box-shadow: inset 0 0 0 2px var(--acc-text); }
.pick-thumb { width: 60px; flex: 0 0 auto; overflow: hidden; background: var(--turf-dark); }
.pick-thumb svg { width: 100%; display: block; }
.pick-thumb.pick-away {
  aspect-ratio: 4 / 3; display: grid; place-items: center;
  background: var(--tint-acc); border-left: 4px solid var(--acc-text);
  color: var(--acc-text); font-weight: 800; font-size: .68rem;
  text-transform: uppercase; letter-spacing: .08em;
}
.pick-body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }
.pick-body strong { font-size: .88rem; font-weight: 700; }
.pick-body small { font-size: .75rem; color: var(--muted); }
.pick-add { flex: 0 0 auto; color: var(--acc-text); font-size: 1.2rem; font-weight: 700; padding: 0 .3rem; }

/* ---------- choosing a sport ---------- */

.auth-card.wide { max-width: 520px; }
.sportgrid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 2px;
  background: var(--line); box-shadow: 0 0 0 2px var(--line); margin-top: 1.1rem; }
@media (min-width: 480px) { .sportgrid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.sportcard {
  display: flex; flex-direction: column; align-items: center; justify-content: flex-start;
  gap: .5rem; padding: 1rem .6rem; min-height: 108px; cursor: pointer;
  background: var(--panel); border: 0; color: var(--text);
  font: inherit; font-size: .82rem; font-weight: 700; letter-spacing: -.01em; text-align: center;
}
.sportcard svg { width: 30px; height: 30px; flex: 0 0 auto; color: var(--acc-text); }
.sportcard span { line-height: 1.2; }
.sportcard em {
  font-style: normal; font-size: .68rem; font-weight: 600; color: var(--muted);
  letter-spacing: 0; text-transform: none;
}
/* :not(.on) matters: the hover tint is a more specific selector than .on, so
   without it, pointing at the sport you are already in turned a green fill with
   white on it into a pale tint with white on it - invisible. */
.sportcard:hover:not(:disabled):not(.on) { background: var(--tint-acc); }
.sportcard:disabled { cursor: default; }
.sportcard.picked, .sportcard.on { background: var(--acc); color: var(--acc-on); }
.sportcard.picked svg, .sportcard.on svg { color: var(--acc-on); }
.sportcard.on em { color: var(--acc-on); opacity: .85; }
.sportgrid.small .sportcard { min-height: 92px; padding: .8rem .5rem; font-size: .78rem; }
.sportgrid.small .sportcard svg { width: 24px; height: 24px; }

/* ---------- pitch / diagram ---------- */
/* Brandbook §04: depiction, not brand. Turf is green because grass is green.
   Nothing in here leaks into the chrome and nothing from the chrome comes in,
   with one exception: the selection ring, which is chalk so it cannot be
   confused with a cone. */

.pitch { display: block; width: 100%; height: 100%; aspect-ratio: 4 / 3; background: var(--turf-dark); }
.pitch-wrap { box-shadow: inset 0 0 0 2px var(--line); overflow: hidden; }
.pitch .outside { fill: var(--turf-dark); opacity: var(--mask-op); pointer-events: none; }

.pitch .turf { fill: var(--turf); }
.pitch .mow { fill: var(--turf-mow); opacity: .55; }
/* Netball and basketball are played indoors on a hard court, so they get a
   surface of their own rather than grass with lines painted on it. */
.pitch .court { fill: var(--court); }
.pitch .court.wood { fill: var(--court-wood); }
/* A league in-goal is grass, just marked off. */
.pitch .ingoal { fill: var(--turf-mow); opacity: .5; }
/* A cricket square is worn earth, not turf. */
.pitch .strip { fill: var(--strip); }
.pitch .tick-dot { fill: var(--chalk); opacity: calc(var(--chalk-op) * .9); }
.pitch .post-ring { stroke: var(--marker); stroke-width: 3; }
.pitch .line { stroke: var(--chalk); stroke-width: 2.2; opacity: var(--chalk-op); fill: none; }
.pitch .line.faint { opacity: calc(var(--chalk-op) * .45); stroke-width: 1.4; }
.pitch .boundary { stroke: var(--chalk); stroke-width: 3.4; opacity: calc(var(--chalk-op) + .05); }
.pitch .post { stroke: var(--chalk); stroke-width: 3.4; opacity: calc(var(--chalk-op) + .05); }
.pitch .post.behind { stroke-width: 2.4; opacity: calc(var(--chalk-op) * .8); }
.pitch .grid-lines line { stroke: var(--chalk); stroke-width: 1.2; opacity: calc(var(--chalk-op) * .35); }
.pitch .tick { fill: var(--chalk); opacity: calc(var(--chalk-op) * .7); font-size: 15px; font-family: var(--font); font-weight: 600; }

.pitch .ob .hit { fill: transparent; stroke: transparent; stroke-width: 26; }
.pitch.editable .ob { cursor: grab; }
.pitch.editable .ob:hover .dot,
.pitch.editable .ob:hover .shape { filter: brightness(1.2); }
.pitch .ob-player .dot { stroke: #201E1D; stroke-width: 2; }
.pitch .ob-player.team-a .dot { fill: var(--team-a); }
.pitch .ob-player.team-b .dot { fill: var(--team-b); }
.pitch .ob-player.team-c .dot { fill: var(--team-c); }
.pitch .ob-player .lbl {
  text-anchor: middle; font-size: 15px; font-weight: 800;
  font-family: var(--font); fill: #201E1D; pointer-events: none;
}
.pitch .ob-player.team-b .lbl { fill: #FFFFFF; }
.pitch .ob-cone .shape { fill: var(--marker); stroke: #7A4A08; stroke-width: 1.5; }
.pitch .ob-ball .shape { fill: var(--ball); stroke: #F3F2F2; stroke-width: 1.8; }
.pitch .ob-ball .seam { stroke: #F3F2F2; stroke-width: 1.6; }
.pitch .ob-goal .postline { stroke: var(--chalk); stroke-width: 3.5; }
/* A zone is an annotation the coach writes on the ground, so it is the accent. */
.pitch .ob-zone .shape { fill: rgba(240,166,60,.16); stroke: var(--marker); stroke-width: 2.5; stroke-dasharray: 8 6; }
.pitch .ob-zone .zlbl {
  fill: var(--marker); font-size: 16px; font-family: var(--font); font-weight: 800;
  paint-order: stroke; stroke: #201E1D; stroke-width: 3;
}
.pitch .ob-text .txt {
  fill: #FFFFFF; font-size: 18px; font-family: var(--font); font-weight: 800;
  paint-order: stroke; stroke: #201E1D; stroke-width: 3.5;
}
.pitch .ob-text .hit { fill: transparent; stroke-width: 0; }

.pitch .ob-path { color: #FFFFFF; }
.pitch .ob-path .shape { stroke: currentColor; stroke-width: 3; stroke-linecap: round; stroke-linejoin: round; }
.pitch .ob-path .hit { stroke: transparent; stroke-width: 24; }
.pitch .path-kick .shape { stroke-dasharray: 16 9; }
.pitch .path-handball .shape { stroke-dasharray: 3 7; }
.pitch .path-lead { color: #F0A63C; }

.pitch .ob.is-selected .dot,
.pitch .ob.is-selected .shape { stroke: #FFFFFF; stroke-width: 4; }
.pitch .ob-path.is-selected .shape { stroke: #FFFFFF; }

/* ---------- drill editor ---------- */

.editor-bar {
  display: flex; align-items: center; justify-content: space-between;
  gap: .6rem; flex-wrap: wrap; margin-bottom: .6rem;
}
.tools { display: flex; flex-wrap: wrap; gap: .3rem; }
.tool {
  display: inline-flex; align-items: center; gap: .3rem;
  padding: .32rem .6rem; cursor: pointer;
  background: transparent; box-shadow: inset 0 0 0 2px var(--edge); border: 0;
  color: var(--muted); font: inherit; font-size: .78rem; font-weight: 600; white-space: nowrap;
}
.tool:hover { color: var(--text); box-shadow: inset 0 0 0 2px var(--edge-lg); }
.tool.on { background: var(--acc); color: var(--acc-on); box-shadow: none; font-weight: 700; }
/* The swatch in the tool row has to be the colour that actually lands on the
   ground, so it reads from the same tokens the diagram does. */
.sw { width: 10px; height: 10px; display: inline-block; box-shadow: inset 0 0 0 1px rgba(32,30,29,.4); }
.sw-a { background: var(--team-a); }
.sw-b { background: var(--team-b); }
.sw-c { background: var(--team-c); }
.editor-right { display: flex; align-items: center; gap: .4rem; flex-wrap: wrap; }
.stage {
  box-shadow: inset 0 0 0 2px var(--line); overflow: hidden;
  touch-action: none; background: var(--turf-dark);
}
.stage .pitch { cursor: crosshair; }
.stage .pitch.tool-select { cursor: default; }
.inspector { min-height: 2.6rem; padding-top: .5rem; }
.insp-row { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; }

/* Expanded: the ground takes the whole screen. This is how you draw a drill
   on a phone, and how you show one to the group on an iPad. */
body.editor-full { overflow: hidden; }
.editor.is-full {
  position: fixed; inset: 0; z-index: 60;
  display: flex; flex-direction: column;
  background: var(--bg);
  padding: calc(.6rem + env(safe-area-inset-top)) .6rem calc(.6rem + env(safe-area-inset-bottom));
}
.editor.is-full .editor-bar { margin-bottom: .5rem; }
.editor.is-full .stage { flex: 1 1 auto; min-height: 0; max-width: none; }
.editor.is-full .stage .pitch { aspect-ratio: auto; height: 100%; }
.editor.is-full .act-full { background: var(--acc); color: var(--acc-on); box-shadow: none; font-weight: 700; }

/* ---------- auth ---------- */

.auth-wrap { min-height: 100vh; min-height: 100dvh; display: grid; place-items: center; padding: 1.5rem; }
.auth-card {
  width: 100%; max-width: 380px; background: var(--panel);
  box-shadow: inset 0 0 0 2px var(--line); padding: 1.7rem;
}
.auth-card h1 { font-size: 1.4rem; letter-spacing: -.045em; }
.auth-card .sub { color: var(--muted); font-size: .88rem; }
.tabs { display: flex; gap: 2px; margin: 1rem 0 1.1rem; }
.tab {
  flex: 1; padding: .5rem; cursor: pointer;
  background: transparent; box-shadow: inset 0 0 0 2px var(--edge); border: 0;
  color: var(--muted); font: inherit; font-size: .84rem; font-weight: 600;
}
.tab.on { box-shadow: inset 0 0 0 2px var(--edge-lg); color: var(--text); font-weight: 700; }
.auth-card .btn.ghost { margin-top: .5rem; }

/* The mark leads the sign-in screen: three words flush left, hard-stacked at
   0.88, TRAIN the only coloured one (brandbook §02, §19).
   TRAIN takes --mark: #228B22 itself, in both themes. A logotype is exempt
   from the contrast rules, and it reads wherever it lands - so the mark keeps
   one colour rather than shifting with the surface like the interface does. */
.auth-mark {
  display: flex; flex-direction: column; align-items: flex-start;
  font-weight: 800; font-size: 2.15rem; line-height: .88;
  letter-spacing: -.05em; text-transform: uppercase; color: var(--text);
  margin: 0 0 .1rem;
}
.auth-mark i { font-style: normal; color: var(--mark); }
.auth-rule { height: 4px; background: var(--line); margin: .85rem 0 1.15rem; width: 100%; }
.auth-card h1 { font-size: 1rem; font-weight: 600; letter-spacing: .18em; text-transform: uppercase; color: var(--muted); }
.auth-card form { margin-top: 1.2rem; }
.auth-alt { display: flex; flex-direction: column; gap: .5rem; margin-top: 1rem; text-align: center; }
.auth-alt .linkish { font-size: .84rem; }
/* The coach link on a player's screen: reachable, not advertised. */
.linkish.quiet { color: var(--muted); font-weight: 400; }
.linkish.quiet:hover { color: var(--text); }

/* What a player is signing up to, said plainly, before they sign in. */
.auth-note {
  margin: .85rem 0 0; font-size: .78rem; line-height: 1.45;
  color: var(--muted); text-align: center;
}

/* The code is read off a screen or a group chat - make it unmistakable. */
.codebox {
  text-transform: uppercase; letter-spacing: .14em; font-weight: 800;
  text-align: center; font-size: 1.15rem !important;
}
.notice {
  background: var(--tint-acc); border-left: 6px solid var(--acc-text);
  color: var(--text); padding: .55rem .7rem;
  font-size: .86rem; margin: .85rem 0 0;
}
.demo-hint {
  margin-top: 1.2rem; padding-top: .9rem; border-top: 1px solid var(--line-soft);
  display: flex; flex-direction: column; gap: .3rem;
  font-size: .8rem; color: var(--muted); text-align: center;
}
.demo-hint strong {
  color: var(--muted); font-size: .64rem; font-weight: 600;
  text-transform: uppercase; letter-spacing: .16em;
}
.demo-hint code {
  background: var(--panel-2); padding: 2px 6px; font-family: var(--font);
  color: var(--text); letter-spacing: .1em; font-weight: 800;
}

/* Team code panel in settings. */
.code-row { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; }
.bigcode {
  flex: 1 1 180px; padding: .6rem .8rem;
  background: var(--bg); box-shadow: inset 0 0 0 2px var(--acc-text); color: var(--text);
  font-size: 1.2rem; font-weight: 800; letter-spacing: .12em; text-align: center;
}

/* Required fields. Brandbook §12: the asterisk is red, the reason is a line
   under the field, and both appear before a save is attempted. */
.req { color: var(--bad); font-weight: 800; }
.req-note { font-size: .82rem; color: var(--muted); margin: 0 0 1rem; }
.fld.invalid input, .fld.invalid select, .fld.invalid textarea { box-shadow: inset 0 0 0 2px var(--bad); }
.fld.invalid #f-focus { outline: 2px solid var(--bad); outline-offset: 4px; }
.field-err { display: block; margin-top: .3rem; font-size: .8rem; font-weight: 600; color: var(--bad); }

/* ---------- toast ---------- */
/* Brandbook §17: a toast confirms, it never informs. Ink, so it reads as the
   app speaking rather than as another panel. */

.toast {
  position: fixed; left: 50%; transform: translateX(-50%);
  bottom: calc(1.4rem + var(--navh) + env(safe-area-inset-bottom));
  background: #201E1D; color: #F3F2F2;
  padding: .6rem 1.1rem; font-size: .88rem; font-weight: 600;
  box-shadow: var(--shadow-lg); z-index: 90; transition: opacity .4s, transform .4s;
  max-width: calc(100vw - 2rem); text-align: center;
}
.toast.bad { box-shadow: inset 0 0 0 2px var(--bad), var(--shadow-lg); color: #F08A80; }
.toast.gone { opacity: 0; transform: translateX(-50%) translateY(8px); }

@media (min-width: 900px) {
  .toast { bottom: calc(1.4rem + env(safe-area-inset-bottom)); }
}

/* ---------- figures ---------- */
/*
 * Brandbook §15: "Tabular numerals on every figure, without exception."
 *
 * One rule rather than the dozen that had grown up around the file, because
 * "without exception" is not a promise you keep by remembering to add a line
 * each time somebody renders a number. A run sheet is a column of times read
 * in a single downward pass, and proportional figures make that column wobble.
 *
 * IT HAS TO COME AFTER THE COMPONENTS. `font: inherit` is the font SHORTHAND
 * and it silently resets font-variant-numeric to normal, so .chip, .btn, .pick
 * and five others were quietly undoing this from further down the file. Equal
 * specificity, later wins - hence the position rather than a specificity war.
 *
 * Deliberately not on <p> at large: these are the classes that carry counts,
 * durations, costs, dates and totals in this app. Running prose keeps its
 * proportional figures, which is what prose wants.
 */
.right, .num, .cnt, .cnt b, .mins, .muted, .sub,
.pill, .chip, .tally b, .hist-date, .hist-drills .mins,
.run-time, .run-mins input, .meta .mins, .row-link .date,
.away-line.cost strong, .clock, #count, .bigcode, .kv dd {
  font-variant-numeric: tabular-nums;
}

/* =====================================================================
   Tablet and phone. Below 900px the rail becomes the floating pill from
   brandbook §16 - ink, thumb-reachable, one item in an accent fill.
   ===================================================================== */

@media (max-width: 899px) {
  .body { display: block; }

  main {
    padding: 1rem 1rem calc(var(--navh) + 2rem + env(safe-area-inset-bottom));
    max-width: none;
  }

  /* The pill stays ink. Only the item you are on is green, which is the whole
     job of the thing - one lit capsule in a dark bar. */
  /* The bar spans the screen rather than hugging its content, because that is
     what lets every destination have the SAME width. Sized to content, the lit
     capsule was as wide as whatever word it happened to be under - "Sessions"
     nearly twice "Week" - so moving between tabs made the green block jump
     about. An equal column is the whole fix; the bar is wider to pay for it. */
  .nav {
    position: fixed; left: 10px; right: 10px;
    bottom: calc(10px + env(safe-area-inset-bottom));
    width: auto; max-width: none; flex: none; z-index: 45;
    flex-direction: row; align-items: stretch; gap: 2px;
    /* 14px, not a stadium. The bar is 57 tall, so a full pill curves at 28.5 -
       this is half of that: clearly soft, clearly not a lozenge. The lit item
       carries the SAME 14px, so the fill sits inside the bar as a smaller copy
       of its shape rather than a different one; colour and the heavier icon
       stroke carry the selection, per brandbook §16.

       Padding is even on all four sides on purpose. It used to be wider on the
       ends, left over from when the bar was a stadium whose curve pinched the
       outer two items - but that put the first and last selections further from
       the edge than from the top, and those are the two gaps you see at once. */
    padding: 6px; border-radius: 14px;
    background: #201E1D; box-shadow: var(--shadow-lg);
    top: auto;
  }
  /* flex-basis 0 rather than auto: the label no longer gets a say in how much
     room its destination is given. Six equal columns, four equal columns. */
  .nav a {
    flex: 1 1 0; min-width: 0;
    flex-direction: column; align-items: center; justify-content: center; gap: 3px;
    padding: 7px 2px; border-radius: 14px;
    color: #9B9797; font-size: 9px; font-weight: 600;
    letter-spacing: .05em; text-transform: uppercase; text-align: center;
    transition: background-color .15s ease, color .15s ease;
  }
  .nav a svg { width: 19px; height: 19px; }
  .nav .nav-full { display: none; }
  .nav .nav-short {
    display: block; width: 100%;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .nav a:hover { background: transparent; color: #F3F2F2; }
  /* The selected item is the same forest green as every button, with white on
     it. Against the ink pill the capsule is 3.05 - over the 3:1 line - and the
     white label against its grey neighbours is what really carries the state. */
  .nav a.on { background: var(--acc); color: var(--acc-on); font-weight: 800; }
  .nav a.on svg { color: var(--acc-on); stroke-width: 2.6; }

  h1 { font-size: 1.45rem; }
  .page-head { align-items: flex-start; gap: .6rem; }
  .page-head > div:first-child { flex: 1 1 100%; }
  .row-actions { flex: 1 1 100%; }
  .row-actions .btn { flex: 1 1 auto; }

  .kv { grid-template-columns: 1fr; gap: 0 .5rem; }
  .kv dt { margin-top: .6rem; }

  .picker { max-height: 60vh; }

  /* Filter tags scroll sideways instead of eating four rows of height. */
  .chips.small {
    flex-wrap: nowrap; overflow-x: auto; padding-bottom: .3rem;
    scrollbar-width: none; -webkit-overflow-scrolling: touch;
  }
  .chips.small::-webkit-scrollbar { display: none; }
  #f-focus.chips.small { flex-wrap: wrap; overflow: visible; }
}

/* Phones. */
@media (max-width: 560px) {
  .grid { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: .6rem; }
  .grid.blocks { grid-template-columns: 1fr; }
  .card-body { padding: .55rem .6rem .65rem; }
  .card-body h3 { font-size: .88rem; }

  .brand span { display: none; }
  .who .pill { display: none; }
  .who .pill.warn { display: inline-block; }

  /* Six equal columns on a 360px screen leaves about 55px each, and SESSIONS
     is the word that has to survive it. Buy the room back from the bar's own
     edges rather than from the type, which is already at the brandbook's 9px. */
  .nav { left: 6px; right: 6px; gap: 1px; padding: 5px; }
  .nav a { padding: 6px 1px; letter-spacing: .02em; }
  .nav a svg { width: 18px; height: 18px; }

  .run-item {
    grid-template-columns: 3rem 1fr auto;
    grid-template-areas: "time main main" ". note note" ". mins ctl";
  }
  .run-ctl { justify-content: flex-end; }

  .row-link .date { width: 100%; }
  .row-link .ttl { flex: 1 1 auto; }

  /* Save is the thing you reach for; Print/Copy/Delete share the row below it
     rather than pushing the form off the screen. */
  .row-actions {
    display: grid; grid-template-columns: repeat(auto-fit, minmax(96px, 1fr)); gap: .45rem;
  }
  .row-actions .btn { padding-left: .4rem; padding-right: .4rem; font-size: .82rem; }
  .row-actions .btn.primary { grid-column: 1 / -1; order: -1; }

  .tbl.run td:nth-child(2) { display: none; }  /* phase pill - the name says it */
  .tbl td, .tbl th { padding: .5rem .35rem; }

  .auth-mark { font-size: 1.9rem; }
}

/* The oldest phones still in a coach's pocket - an SE at 320. Six equal columns
   leave about 47px each, and SETTINGS in the active weight does not fit that at
   9px. The tracking goes first, because it is the part carrying the least.

   ONLY the nav belongs in here. This block was originally opened by splitting
   the 560 one above, which swallowed everything that followed it - the run
   sheet's phone grid, the session row wrap, the action row, the table padding -
   and quietly moved all of it from "every phone" to "almost no phone". */
@media (max-width: 340px) {
  .nav a { font-size: 8px; letter-spacing: 0; }
  .nav a svg { width: 17px; height: 17px; }
}

/* =====================================================================
   Touch. Fingers are not mice, and in July they are cold fingers.
   Everything tappable gets to 44px, and inputs get 16px type so iOS
   stops zooming the page every time you tap a field.
   ===================================================================== */

@media (pointer: coarse) {
  html { font-size: 16px; }

  .btn { min-height: 44px; padding: .55rem 1rem; }
  /* §08: every control clears 44px on a touch screen, and "small" is a type
     size rather than a licence to be harder to hit. The button stays visually
     small - .8rem type, tight padding - it just gets a thumb's worth of it. */
  .btn.sm { min-height: 44px; }
  .chip { min-height: 38px; display: inline-flex; align-items: center; padding: .35rem .85rem; }
  .chips { gap: .4rem; }
  .tool { min-height: 42px; padding: .4rem .75rem; font-size: .82rem; }
  .icon { width: 42px; height: 42px; font-size: .85rem; }
  .icon.danger { font-size: 1.3rem; }
  .theme-btn { width: 44px; height: 44px; }
  .tab { min-height: 44px; }
  .pick { min-height: 58px; padding: .5rem; }
  .pick-thumb { width: 72px; }
  .row-link { padding: .8rem .2rem; }
  .nav a { min-height: 44px; }

  /* Back links and the titles inside a history row are real navigation, and at
     20-25px tall they were the smallest targets in the app. The padding buys
     the tap area without moving the type, and the negative margin gives the
     space back to the layout so nothing drifts apart. */
  .back {
    min-height: 44px; display: inline-flex; align-items: center;
    padding-right: .6rem; margin-bottom: 0;
  }
  .hist-title { display: inline-flex; align-items: center; min-height: 44px; }

  /* "Full history", "See it by week", "view" - small by design, but they are
     still the only way to those screens, so they get the same thumb's worth.
     Inline-flex rather than a block, so they stay inside the sentence. */
  .linkish.sm {
    min-height: 44px; display: inline-flex; align-items: center;
    padding-right: .4rem;
  }

  /* 16px or iOS zooms the page the moment a field takes focus, and 44px is
     the smallest thing a thumb hits reliably. */
  input, select, textarea, .search {
    font-size: 16px; min-height: 44px;
  }
  input[type="checkbox"], input[type="radio"] { min-height: 0; }
  .run-note, .run-mins input { font-size: 16px; min-height: 44px; }
  .fld textarea { min-height: 88px; }
  .run-mins input { width: 4.2rem; }
  .fld.check input { width: 24px; height: 24px; }
  .filter-check { min-height: 44px; }
  .filter-check input { width: 22px; height: 22px; }

  /* No hover states to chase, but do give a clear press. */
  .card:hover, .btn:hover, .pick:hover { transform: none; }
  .btn:active, .chip:active, .tool:active, .icon:active, .pick:active { filter: brightness(1.15); }

  /* Bigger grab areas on the diagram - a cone is a small target with gloves on. */
  .pitch .ob .hit { stroke-width: 40; }
  .pitch .ob-player .hit { r: 26; }
}

/* ---------- print ---------- */

#printarea { display: none; }

@media print {
  body { background: #fff; color: #201E1D; }
  #app { display: none !important; }
  #printarea { display: block; padding: 0; font-size: 11pt; }
  #printarea h1 { font-size: 17pt; margin-bottom: .2em; letter-spacing: -.03em; }
  #printarea table { width: 100%; border-collapse: collapse; margin-top: .8em; }
  #printarea th, #printarea td {
    border: 1px solid #9B9797; padding: 5px 7px; text-align: left; vertical-align: top;
  }
  #printarea th {
    background: #EAE9E9; font-size: 9pt; font-weight: 600;
    text-transform: uppercase; letter-spacing: .12em;
  }
  #printarea small { color: #605D5D; }
}

/* ---------- account switching and the paywall ---------- */

/* The team name doubles as the way between accounts when there is more than
   one. It has to look identical to the plain version until you go near it -
   a coach with a single account should never see a control they cannot use. */
.brand.as-button {
  background: none; border: 0; padding: 0; margin: 0; font: inherit;
  color: inherit; text-align: left; cursor: pointer;
}
.brand .swap { color: var(--muted); font-size: .7em; vertical-align: middle; }
.brand.as-button:hover .swap { color: var(--acc-text); }
.brand.as-button:focus-visible { outline: 3px solid var(--acc-text); outline-offset: 2px; }

.paywall { margin: 1rem 0 .4rem; }
.ticks { list-style: none; margin: 0 0 1rem; padding: 0; text-align: left; }
.ticks li { padding: .3rem 0 .3rem 1.5rem; position: relative; color: var(--muted-2); }
.ticks li::before {
  content: ''; position: absolute; left: 0; top: .72em;
  width: 9px; height: 5px; border-left: 2px solid var(--acc-text);
  border-bottom: 2px solid var(--acc-text); transform: rotate(-45deg);
}

/* Inviting an assistant sits under the staff list in Settings. */
.invite { margin-top: 1rem; padding-top: 1rem; border-top: 1px solid var(--line-soft); }
.invite .fld { margin-bottom: .6rem; }

/* ---------- signing up, step two ---------- */

/* Two steps, and which one you are on. Only ever shown during sign-up: a coach
   coming back to a lapsed subscription is not partway through anything. */
.steps-of {
  display: flex; gap: .5rem; list-style: none; margin: 0 0 1rem; padding: 0;
  font-size: .78rem; font-weight: 600; letter-spacing: .04em;
  text-transform: uppercase; color: var(--muted);
}
.steps-of li { display: flex; align-items: center; gap: .5rem; }
.steps-of li + li::before {
  content: ''; width: 18px; height: 2px; background: var(--line-soft);
}
.steps-of .on { color: var(--acc-text); }
.steps-of .done { color: var(--muted-2); }
.steps-of .done::after { content: ' \2713'; }

/* The two plans. A price is a decision, not a button, so they are wide enough
   to read at a glance and sit side by side to be compared. */
.plans { display: flex; gap: .7rem; flex-wrap: wrap; margin-bottom: 1.1rem; }
.plan {
  flex: 1 1 10rem; position: relative; cursor: pointer;
  display: flex; flex-direction: column; align-items: center; gap: .15rem;
  padding: 1.1rem .8rem .9rem;
  background: var(--panel); color: var(--text);
  box-shadow: inset 0 0 0 2px var(--edge); border: 0;
  font-family: inherit; text-align: center;
}
/* A price card has to read as something you press. The old version said so
   only on hover, which is no help on a touch screen and no help at all until
   you are already on top of it - so the pressable part is now drawn, and hover
   and active only confirm what the card already looked like. */
.plan { transition: transform .12s ease, box-shadow .12s ease; }
.plan:hover:not(:disabled) {
  box-shadow: inset 0 0 0 2px var(--acc-text), 0 4px 12px rgba(32, 30, 29, .11);
  transform: translateY(-2px);
}
.plan:hover:not(:disabled) .go { background: var(--acc); color: var(--acc-on); }
.plan:active:not(:disabled) { transform: translateY(0); }
.plan:focus-visible { outline: 3px solid var(--acc-text); outline-offset: 2px; }
.plan:disabled { opacity: .55; cursor: default; }
.plan.picked { box-shadow: inset 0 0 0 3px var(--acc-text); opacity: 1; }
.plan.picked .go { background: var(--acc); color: var(--acc-on); }

/* Drawn as a button inside the card, so the affordance exists at rest. */
.plan .go {
  margin-top: .7rem; width: 100%;
  display: flex; align-items: center; justify-content: center; gap: .35rem;
  min-height: 34px; padding: .3rem .6rem;
  font-size: .82rem; font-weight: 700;
  color: var(--acc-text); box-shadow: inset 0 0 0 2px var(--acc-text);
}

/* Where the next click sends them, before it sends them. */
.leaving {
  margin: 0 0 1.1rem; font-size: .84rem; color: var(--muted-2); text-align: center;
}

@media (prefers-reduced-motion: reduce) {
  .plan { transition: none; }
  .plan:hover:not(:disabled) { transform: none; }
}
.plan strong { font-size: 1.7rem; font-weight: 800; letter-spacing: -.02em; }
.plan .per { font-size: .84rem; color: var(--muted); }
.plan .gst { font-size: .72rem; color: var(--muted); letter-spacing: .02em; }
.plan .save { font-size: .78rem; color: var(--acc-text); font-weight: 600; min-height: 1.1em; }
.plan.best { box-shadow: inset 0 0 0 2px var(--acc-text); }
.plan .flag {
  position: absolute; top: -.6rem; left: 50%; transform: translateX(-50%);
  background: var(--acc); color: var(--acc-on); font-size: .66rem; font-weight: 700;
  letter-spacing: .1em; text-transform: uppercase; padding: .15rem .5rem; white-space: nowrap;
}

/* Status and renewal date in Settings. */
.kv { display: grid; grid-template-columns: auto 1fr; gap: .35rem .9rem; margin: 0 0 1rem; }
.kv dt { color: var(--muted); font-size: .84rem; align-self: center; }
.kv dd { margin: 0; align-self: center; }
