/* Real Madrid Foundation attendance - one stylesheet, mobile first.
 *
 * White and gold, following the Foundation's own mark. The palette is sampled
 * from the official artwork, not invented:
 *
 *   LOGO FRM 2022 Oro.png            one flat colour, #af8543
 *   09_logo_FRM_solido_Clinic_color  #c0843c gold over #060606 black
 *
 * There is no blue anywhere in RMF's marks. The navy this app first shipped
 * with came from Craig's spreadsheet, which is his own styling. Do not
 * reintroduce it.
 *
 * Gold is a mid-tone: it never carries white text at body size (3.4:1) and
 * never states anything on its own. It is structure and emphasis; meaning is
 * carried by text and by the three mark colours, all of which clear 4.5:1.
 */

:root {
  --gold: #af8543;          /* official */
  --gold-bright: #c9a15a;   /* hover and hairlines only */
  --gold-deep: #8c6a35;     /* gold text on white: 4.6:1 */
  --gold-wash: #faf5ec;     /* tinted surface */
  --gold-edge: #ecdfc7;

  --present: #177a41;
  --absent: #c0392b;
  /* Deliberately not the brand gold. A mark that reads as chrome is a mark
   * nobody trusts. */
  --excused: #b45309;

  --paper: #ffffff;
  --surface: #faf8f5;
  --line: #e8e3da;
  --ink: #14110d;
  --ink-soft: #6b655c;

  --radius: 16px;
  --radius-sm: 11px;
  --tap: 46px;

  --shadow-sm: 0 1px 2px rgba(20, 17, 13, .05);
  --shadow: 0 2px 4px rgba(20, 17, 13, .04), 0 8px 20px -12px rgba(20, 17, 13, .14);
  --shadow-lift: 0 4px 10px rgba(20, 17, 13, .07), 0 14px 32px -16px rgba(20, 17, 13, .2);
}

@media (prefers-color-scheme: dark) {
  :root {
    --gold: #c9a15a;
    --gold-bright: #dcb977;
    --gold-deep: #e0be80;
    --gold-wash: #201c15;
    --gold-edge: #3a3226;

    --present: #35b06a;
    --absent: #ef6a5c;
    --excused: #e0913a;

    --paper: #1a1815;
    --surface: #121110;
    --line: #302b24;
    --ink: #f6f3ee;
    --ink-soft: #a8a196;

    --shadow-sm: 0 1px 2px rgba(0, 0, 0, .4);
    --shadow: 0 2px 4px rgba(0, 0, 0, .3), 0 8px 20px -12px rgba(0, 0, 0, .7);
    --shadow-lift: 0 4px 10px rgba(0, 0, 0, .4), 0 14px 32px -16px rgba(0, 0, 0, .8);
  }
}

* { box-sizing: border-box; }

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

body {
  margin: 0;
  background: var(--surface);
  color: var(--ink);
  font: 16px/1.55 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  /* Every control has its own pressed state; the default grey flash makes a
   * fast double-tap look broken. */
  -webkit-tap-highlight-color: transparent;
}

/* Chrome on Android raises "Tap to see search results" on any tap in
 * selectable text, which fires constantly on a roster. Opt back in only where
 * a coach would genuinely want to copy something. */
@media (pointer: coarse) {
  body { -webkit-user-select: none; user-select: none; }
  input, textarea, .selectable { -webkit-user-select: text; user-select: text; }
}

a { color: inherit; }

h1, h2, h3 { letter-spacing: -.012em; }

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

.topbar {
  background: var(--paper);
  color: var(--ink);
  padding: 10px 14px;
  padding-top: calc(10px + env(safe-area-inset-top));
  padding-left: calc(14px + env(safe-area-inset-left));
  padding-right: calc(14px + env(safe-area-inset-right));
  position: sticky;
  top: 0;
  z-index: 20;
  border-bottom: 1px solid var(--line);
  box-shadow: var(--shadow-sm);
}

/* The bar's background spans the window; its contents share .wrap's centred
 * column.  The max-width is the wrap's own minus the bar's padding, because
 * that padding sits outside this element - so the two content edges land on the
 * same pixel.  Keep these three numbers in step with .wrap at every
 * breakpoint. */
.topbar-inner {
  max-width: 592px;
  margin: 0 auto;
  display: flex;
  align-items: center;
  gap: 11px;
}

/* A hairline of gold under the bar. Thin enough to read as a rule rather than
 * a band, which is how the mark itself uses the colour. */
.topbar::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: -1px;
  height: 2px;
  background: linear-gradient(90deg, var(--gold), var(--gold-bright) 55%, transparent);
}

.topbar img { height: 32px; width: auto; flex: none; }

.topbar h1 {
  font-size: 15.5px;
  margin: 0;
  font-weight: 650;
  line-height: 1.2;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.topbar h1 small {
  display: block;
  font-weight: 500;
  font-size: 12px;
  letter-spacing: .01em;
  color: var(--gold-deep);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.topbar .spacer { margin-left: auto; }

/* 44px is the floor for a touch target, and this is the only way back on a
 * home-screen install.  The negative margin keeps the icon optically aligned
 * with the content edge below it despite the larger hit box. */
.backlink {
  width: 44px;
  height: 44px;
  margin-left: -10px;
  flex: none;
  display: grid;
  place-items: center;
  border-radius: var(--radius-sm);
  color: var(--gold-deep);
}
.backlink svg { width: 23px; height: 23px; }
.backlink:active { background: var(--gold-wash); }

.topbtn {
  min-height: 38px;
  padding: 0 12px;
  gap: 7px;
  font-size: 13.5px;
  font-weight: 600;
  color: var(--gold-deep);
  background: var(--gold-wash);
  border: 1px solid var(--gold-edge);
  max-width: 46vw;
  display: inline-flex;
}
/* The name truncates; the icon never does, so the control still reads as an
 * exit even for a coach whose name is too long for the bar. */
.topbtn .who {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}
.topbtn .outicon { width: 15px; height: 15px; flex: none; opacity: .8; }

.wrap {
  padding: 16px 14px 20px;
  padding-left: calc(14px + env(safe-area-inset-left));
  padding-right: calc(14px + env(safe-area-inset-right));
  max-width: 620px;
  margin: 0 auto;
}

/* ---- cards ---------------------------------------------------------- */

.card {
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 16px;
  margin-bottom: 14px;
  box-shadow: var(--shadow);
}

.card h2 {
  margin: 0 0 3px;
  font-size: 17.5px;
  font-weight: 650;
}
.meta { color: var(--ink-soft); font-size: 13.5px; margin: 0; }
/* A second, quieter meta line, so two facts can stack without competing. */
.meta.faint { font-size: 12.5px; opacity: .82; margin-top: 1px; }

.sectionlabel {
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--gold-deep);
  margin: 22px 4px 9px;
}
.sectionlabel:first-child { margin-top: 4px; }

/* ---- today ---------------------------------------------------------- */

.sessions { display: grid; gap: 12px; }

.sessioncard {
  display: flex;
  align-items: center;
  gap: 13px;
  text-decoration: none;
  margin-bottom: 0;
  transition: transform .12s ease, box-shadow .12s ease, border-color .12s ease;
}
.sessioncard:hover { border-color: var(--gold-edge); box-shadow: var(--shadow-lift); }
.sessioncard:active { transform: scale(.99); }

.sessioncard .age {
  /* Deeper than --gold on purpose: this carries white text at 17px bold,
   * which is just under WCAG's large-text threshold and so gets no allowance.
   * White on --gold is 3.4:1; on this it is 5:1. */
  background: linear-gradient(160deg, #8c6a35, #7a5c2d);
  color: #fff;
  font-weight: 750;
  font-size: 17px;
  letter-spacing: -.02em;
  border-radius: var(--radius-sm);
  padding: 13px 10px;
  min-width: 62px;
  text-align: center;
  flex: none;
}
.sessioncard .grow { flex: 1; min-width: 0; }
.sessioncard .chev { color: var(--gold); flex: none; width: 20px; height: 20px; }

/* Finished sessions.  Green rather than gold: gold is structure in this app and
 * never states anything by itself, so a gold glow would read as "selected"
 * rather than "done". */
.sessioncard .donetick {
  flex: none;
  width: 22px;
  height: 22px;
  color: var(--present);
}
.sessioncard.done { border-color: var(--present); }
.sessioncard.done .progress i {
  background: linear-gradient(90deg, var(--present), var(--present));
}

/* ---- add to home screen ---------------------------------------------- */

/* Revealed by app.js only when Chrome says the site is installable, so it is
 * hidden on browsers that cannot install and on an app already installed. */
.installbar {
  display: flex;
  align-items: center;
  gap: 11px;
  max-width: 592px;
  margin: 0 auto;
  padding: 11px 14px;
  border-bottom: 1px solid var(--gold-edge);
  background: var(--gold-wash);
}
.installbar[hidden] { display: none; }
.installbar .grow { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.installbar strong { font-size: 14px; font-weight: 650; }
.installbar .meta { font-size: 12.5px; }
.installbar .btn {
  flex: none;
  min-height: 38px;
  padding: 0 16px;
  font-size: 14px;
}

/* The dismiss. Quiet enough not to compete with Add, big enough to hit. */
.iconbtn {
  flex: none;
  width: 38px;
  height: 38px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--ink-soft);
  cursor: pointer;
}
.iconbtn svg { width: 17px; height: 17px; }
.iconbtn:hover { background: var(--paper); color: var(--ink); }

@media (min-width: 680px) {
  .installbar { max-width: 892px; padding: 12px 24px; }
}
@media (min-width: 1040px) {
  .installbar { max-width: 992px; }
}

/* ---- weather --------------------------------------------------------- */

/* Sessions are outdoors on the Highveld at 16:00, so the useful forecast is the
 * session window rather than "now".  Hidden until app.js has something real to
 * put in it - an empty box on a phone with no signal reads as broken. */
.weather {
  display: flex;
  align-items: center;
  gap: 13px;
  padding: 12px 15px;
  margin-bottom: 12px;
  border: 1px solid var(--gold-edge);
  border-radius: var(--radius);
  background: var(--gold-wash);
}
.weather[hidden] { display: none; }

.wx-temp {
  font-size: 25px;
  font-weight: 700;
  letter-spacing: -.03em;
  color: var(--gold-deep);
  flex: none;
  line-height: 1;
}
.wx-body { display: flex; flex-direction: column; min-width: 0; }
.wx-head { font-size: 14.5px; font-weight: 650; }
.wx-meta { font-size: 12.5px; color: var(--ink-soft); }

/* ---- who to call ------------------------------------------------------ */

.contacts { display: flex; flex-direction: column; gap: 11px; margin-top: 11px; }

.contact {
  display: flex;
  align-items: center;
  gap: 11px;
}
.contact .grow { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.contact .name { font-weight: 650; font-size: 15.5px; }

/* A real target, because this gets pressed one-handed by somebody who is
 * already dealing with a hurt child. */
.callbtn {
  flex: none;
  min-height: var(--tap);
  padding: 0 16px;
  font-size: 14.5px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.callbtn svg { width: 17px; height: 17px; flex: none; }

/* A real target, not a line of text that happens to be a link.  It is tapped
 * with a thumb, and 44px is the floor. */
.contactmail {
  display: flex;
  align-items: center;
  min-height: var(--tap);
  font-size: 13.5px;
  color: var(--gold-deep);
  word-break: break-all;
  text-decoration: none;
  border-top: 1px solid var(--line);
  margin-top: -4px;
}

@media (max-width: 419px) {
  /* The number and the name compete for a narrow row, and the number must not
     be the one that shrinks. */
  .contact { flex-wrap: wrap; }
  .callbtn { width: 100%; margin-top: 2px; }
}

/* ---- the weather drawing ---------------------------------------------- */

/* One SVG holding every part.  A class on .weather decides which parts are
 * shown, so there is no second request and nothing to load. */
.wx-icon {
  margin-left: auto;
  flex: none;
  width: 42px;
  height: 42px;
  color: var(--gold-deep);
}
.wx-icon svg { width: 100%; height: 100%; display: block; }

/* Hidden by default; each condition switches its own parts back on. */
.wx-icon .wx-rays,
.wx-icon .wx-orb,
.wx-icon .wx-cloud,
.wx-icon .wx-drops,
.wx-icon .wx-bolt,
.wx-icon .wx-fog { display: none; }

.weather.wx-clear  .wx-rays,
.weather.wx-clear  .wx-orb    { display: block; }
.weather.wx-partly .wx-orb,
.weather.wx-partly .wx-cloud  { display: block; }
.weather.wx-cloud  .wx-cloud  { display: block; }
.weather.wx-rain   .wx-cloud,
.weather.wx-rain   .wx-drops  { display: block; }
.weather.wx-storm  .wx-cloud,
.weather.wx-storm  .wx-bolt   { display: block; }
.weather.wx-fog    .wx-fog    { display: block; }

/* Partly cloudy: the sun peeks out from behind, so it needs shifting up and
 * left and shrinking, or the cloud swallows it. */
.weather.wx-partly .wx-orb {
  transform: translate(-5px, -6px) scale(.62);
  transform-origin: 24px 24px;
}

/* Motion is slow on purpose.  This sits above a register a coach is trying to
 * read on a bright touchline, and anything quick would pull the eye off the
 * names.  All of it stops under prefers-reduced-motion, which is handled
 * globally further up this file. */
.wx-rays  { animation: wx-spin 26s linear infinite; transform-origin: 24px 24px; }
.wx-cloud { animation: wx-drift 7s ease-in-out infinite alternate; }
.wx-fog   { animation: wx-drift 5s ease-in-out infinite alternate; }
.wx-bolt  { animation: wx-flash 3.4s ease-in-out infinite; }

.wx-drops path { animation: wx-fall 1.6s linear infinite; }
.wx-drops path:nth-child(2) { animation-delay: .45s; }
.wx-drops path:nth-child(3) { animation-delay: .9s; }

@keyframes wx-spin  { to { transform: rotate(360deg); } }
@keyframes wx-drift { from { transform: translateX(-1.5px); }
                      to   { transform: translateX(1.5px); } }
@keyframes wx-fall  { 0%   { opacity: 0; transform: translateY(-4px); }
                      35%  { opacity: 1; }
                      100% { opacity: 0; transform: translateY(5px); } }
@keyframes wx-flash { 0%, 88%, 100% { opacity: .45; }
                      92%           { opacity: 1; }
                      95%           { opacity: .5; }
                      97%           { opacity: 1; } }

/* Thunderstorms and heavy rain call a session off, so they do not get to look
 * like the same information as a mild afternoon. */
.weather.severe {
  border-color: var(--absent);
  background: #fdecea;
}
.weather.severe .wx-temp,
.weather.severe .wx-icon { color: #a02a1c; }
@media (prefers-color-scheme: dark) {
  .weather.severe { background: #2a1512; }
  .weather.severe .wx-temp,
  .weather.severe .wx-icon { color: #ffb4a8; }
}

/* Shown only when a page came out of the worker's cache with no signal.  The
 * whole objection to caching a roster was that a coach cannot tell it is stale;
 * this is the answer to that, so it is deliberately loud. */
.stalebar {
  border: 1px solid var(--gold-edge);
  background: var(--gold-wash);
  color: var(--gold-deep);
  border-radius: var(--radius-sm);
  padding: 10px 13px;
  margin-bottom: 12px;
  font-size: 13.5px;
  font-weight: 600;
}
/* Not from today at all: that is a different order of wrong. */
.stalebar.old {
  border-color: var(--absent);
  background: #fdecea;
  color: #a02a1c;
}
@media (prefers-color-scheme: dark) {
  .stalebar.old { background: #2a1512; color: #ffb4a8; }
}

/* ---- coming up ------------------------------------------------------- */

/* Every team trains on the same two or three days, so a row per session was
 * six tall rows all reading "31/7" - and on a laptop the chevron ended up 900px
 * from the age group it belonged to.  One block per day, age groups as chips
 * that are themselves the targets. */
.upday { padding: 3px 0 13px; }
.upday.ruled { border-bottom: 1px solid var(--line); margin-bottom: 12px; }
.upday:last-child { padding-bottom: 2px; }

.daylabel {
  margin: 0 0 9px;
  font-size: 14px;
  font-weight: 650;
  color: var(--ink);
}

.chiprow { display: flex; flex-wrap: wrap; gap: 8px; }

.teamchip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 58px;
  min-height: 44px;
  padding: 0 14px;
  border-radius: var(--radius-sm);
  border: 1.5px solid var(--gold-edge);
  background: var(--gold-wash);
  color: var(--gold-deep);
  font-weight: 700;
  font-size: 15px;
  letter-spacing: -.01em;
  text-decoration: none;
  transition: border-color .12s ease, background-color .12s ease,
              transform .07s ease;
}
.teamchip:hover { border-color: var(--gold); background: var(--paper); }
.teamchip:active { transform: scale(.96); }

.progress {
  /* display:block matters - this is a <span>, and an inline box ignores
   * height, so the bar silently collapsed to nothing. */
  display: block;
  height: 7px;
  background: var(--line);
  border-radius: 99px;
  overflow: hidden;
  margin-top: 9px;
}
.progress i {
  display: block;
  height: 100%;
  border-radius: 99px;
  background: linear-gradient(90deg, var(--gold), var(--gold-bright));
  transition: width .3s ease;
}

/* ---- roster --------------------------------------------------------- */

.rosterhead {
  display: flex;
  align-items: center;
  gap: 11px;
  padding-bottom: 13px;
  margin-bottom: 3px;
  border-bottom: 1px solid var(--line);
}
.rosterhead .grow { flex: 1; min-width: 0; }
.rosterhead h2 { margin: 0; }
.rosterhead .btn { flex: none; font-size: 13.5px; padding: 0 14px; min-height: 44px; }

/* The progress bar reads as a measure, not a rule across the whole card. */
.progress.capped { max-width: 180px; }

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

/* A grid, not a flex row, for one reason: the safety flags need the width of
 * the whole row.  As a child of the name they had the ~105px the name column
 * gets on a 375px phone, so "Medical" and "Medication" each took a line and the
 * row grew to 130px.  Here they sit on their own line, aligned with the name. */
#roster .player {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  grid-template-areas:
    "avatar name  marks"
    ".      flags flags";
  align-items: center;
  /* Pin the content to the top of the row.  Two columns make sibling rows share
     a height, and a row whose neighbour carries a flag chip gets stretched - so
     centring the whole grid dropped a flagless player 12px below the one beside
     it.  align-items still centres the avatar, name and buttons within the
     first row, which is the alignment that matters. */
  align-content: start;
  column-gap: 10px;
  row-gap: 0;
}
#roster .player > .avatar { grid-area: avatar; }
#roster .player > .name { grid-area: name; }
#roster .player > .marks { grid-area: marks; }
#roster .player > .flags { grid-area: flags; }

.player {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 11px 0;
  border-bottom: 1px solid var(--line);
}
.player:last-child { border-bottom: 0; padding-bottom: 2px; }
.player .grow { flex: 1; min-width: 0; }
.player .name {
  font-weight: 600;
  font-size: 15.5px;
  /* Wrap to two lines rather than truncate. At 390px the name column is only
   * ~150px once the avatar and three 46px buttons have taken theirs, and
   * "Amahle Mahlangu" does not fit on one line. Shrinking the buttons to make
   * room would be the wrong trade - a coach has to be able to read the whole
   * name, and the row can afford a second line. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  overflow-wrap: break-word;
  line-height: 1.28;
}
/* The name is only ~24px tall, a poor target beside a 46px button. Padding
 * plus an equal negative margin grows the hit box to the 44px floor without
 * moving anything on screen. */
.player a.name { text-decoration: none; display: block; padding: 13px 0; margin: -13px 0; }

.avatar {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  flex: none;
  display: grid;
  place-items: center;
  font-size: 13.5px;
  font-weight: 700;
  letter-spacing: .01em;
  color: #fff;
  background: linear-gradient(160deg, #8c6a35, #7a5c2d) center/cover no-repeat;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .22), var(--shadow-sm);
  text-decoration: none;
}
.avatar.haspic { color: transparent; }
.avatar.big { width: 62px; height: 62px; font-size: 19px; }

/* This had no layout rule at all, so it fell back to block and the 62px avatar
 * sat on its own line above the child's name.  Every other pairing of .avatar
 * with a .grow sibling in this app is a flex row, and .grow means nothing
 * outside a flex container. */
.profilehead {
  display: flex;
  align-items: center;
  gap: 15px;
}
.profilehead .grow { flex: 1; min-width: 0; }
.profilehead h2 { margin: 0 0 1px; }

/* A fixed slot, whether or not there is anything in it.
 *
 * Rows used to be whatever height their flags made them, so a child with two
 * conditions sat 30px taller than the one above and the list read as ragged.
 * Reserving the space costs a little height on rows with no flags and buys a
 * roster where every row is the same shape, which is what makes a list of
 * twenty scannable while standing up. */
.flags {
  display: flex;
  align-items: center;
  gap: 5px;
  height: 24px;
  margin-top: 3px;
  overflow: hidden;
}
/* Uniform chips.  Fixed padding and no wrapping, so one long condition name
 * cannot make its chip twice the size of its neighbour. */
.flag { white-space: nowrap; flex: none; }
/* A note is information, not a warning, so it does not borrow the red. */
.flag.note {
  background: var(--gold-wash);
  color: var(--gold-deep);
  border-color: var(--gold-edge);
}
@media (prefers-color-scheme: dark) {
  .flag.note { background: var(--gold-wash); color: var(--gold-deep); border-color: var(--gold-edge); }
}
.flag {
  font-size: 11px;
  padding: 2.5px 8px;
  border-radius: 99px;
  background: #fdecea;
  color: #a02a1c;
  border: 1px solid #f3c3bd;
  font-weight: 650;
  letter-spacing: .01em;
}
.flag.consent {
  background: var(--gold-wash);
  color: var(--gold-deep);
  border-color: var(--gold-edge);
}
@media (prefers-color-scheme: dark) {
  .flag { background: #3a1a16; color: #ffb4a8; border-color: #5c2a23; }
}

/* The mark buttons are the whole product: large, all three always visible, and
 * unmistakable at arm's length in sunlight. */
.marks { display: flex; gap: 6px; flex: none; }
.marks button {
  width: var(--tap);
  height: var(--tap);
  border-radius: 13px;
  border: 1.5px solid var(--line);
  background: var(--paper);
  color: var(--ink-soft);
  font-weight: 750;
  font-size: 17px;
  cursor: pointer;
  touch-action: manipulation;
  transition: transform .07s ease, background-color .14s ease,
              border-color .14s ease, color .14s ease, box-shadow .14s ease;
}
.marks button:hover { border-color: var(--gold-edge); color: var(--ink); }
.marks button:active { transform: scale(.93); }
.marks button[aria-pressed="true"] {
  color: #fff;
  border-color: transparent;
  box-shadow: var(--shadow-sm);
}
.marks button[data-mark="P"][aria-pressed="true"] { background: var(--present); }
.marks button[data-mark="A"][aria-pressed="true"] { background: var(--absent); }
.marks button[data-mark="E"][aria-pressed="true"] { background: var(--excused); }
.marks button:disabled { opacity: .5; }

@media (prefers-color-scheme: dark) {
  .marks button[aria-pressed="true"] { color: #12100e; }
  /* The chip and avatar are hardcoded deep gold for contrast in light mode;
   * against a dark surface they can use the lighter brand gold with a dark
   * label instead. */
  .sessioncard .age,
  .avatar { background: linear-gradient(160deg, var(--gold-bright), var(--gold)); color: #17130c; }
}

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
  .marks button:active, .sessioncard:active { transform: none; }
}

/* ---- forms and buttons ---------------------------------------------- */

.input, textarea.input {
  width: 100%;
  min-width: 0;
  min-height: var(--tap);
  padding: 11px 13px;
  border: 1.5px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--paper);
  color: var(--ink);
  font: inherit;
  transition: border-color .14s ease, box-shadow .14s ease;
}
.input:focus, textarea.input:focus {
  outline: none;
  border-color: var(--gold);
  box-shadow: 0 0 0 3px rgba(175, 133, 67, .16);
}
textarea.input { min-height: 100px; resize: vertical; line-height: 1.5; }

label {
  display: block;
  font-size: 12.5px;
  font-weight: 600;
  letter-spacing: .02em;
  color: var(--ink-soft);
  margin: 13px 0 5px;
}

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: var(--tap);
  padding: 0 20px;
  border: 1.5px solid transparent;
  border-radius: var(--radius-sm);
  background: var(--ink);
  color: var(--paper);
  font: inherit;
  font-weight: 650;
  cursor: pointer;
  text-decoration: none;
  transition: filter .14s ease, transform .07s ease, box-shadow .14s ease;
}
.btn:active { transform: scale(.985); filter: brightness(.94); }

/* Gold carries a near-black label, never white: white on #af8543 is 3.4:1 and
 * this is 6:1. It also looks more like the mark. */
.btn.gold {
  background: linear-gradient(160deg, var(--gold-bright), var(--gold));
  color: #1a1206;
  box-shadow: var(--shadow-sm);
}
.btn.gold:hover { box-shadow: var(--shadow-lift); }
.btn.ghost {
  background: var(--paper);
  color: var(--gold-deep);
  border-color: var(--gold-edge);
}
.btn.ghost:hover { background: var(--gold-wash); }
.btn.block { width: 100%; }
.btn:disabled { opacity: .55; cursor: default; box-shadow: none; }
.btn:disabled:active { transform: none; }

/* Replaces the empty <p></p> that four templates were using to push a button
 * clear of the field above it. */
.spaced { margin-top: 15px; }

/* A button and its status text, or two buttons, on one line that wraps rather
 * than overflowing a narrow phone. */
.formrow {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

.nowrap { white-space: nowrap; }

/* One visible focus style for everything, in the brand gold.  The UA default
 * ring is present but reads as a foreign element on this palette, and on the
 * gold buttons it has very little to contrast against. */
:where(a, button, input, textarea, select, [tabindex]):focus-visible {
  outline: 2.5px solid var(--gold-deep);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}
.marks button:focus-visible { outline-offset: 3px; }
.avatar:focus-visible { border-radius: 50%; }

/* ---- misc ----------------------------------------------------------- */

.flash {
  border-radius: var(--radius-sm);
  padding: 11px 13px;
  margin-bottom: 13px;
  background: #fdecea;
  color: #a02a1c;
  font-size: 14px;
  border: 1px solid #f3c3bd;
}
.flash.ok { background: #e9f6ed; color: #14603a; border-color: #bfe3cc; }
@media (prefers-color-scheme: dark) {
  .flash { background: #3a1a16; color: #ffb4a8; border-color: #5c2a23; }
  .flash.ok { background: #14301f; color: #a8e0bf; border-color: #23543a; }
}

.pill {
  display: inline-block;
  font-size: 11.5px;
  font-weight: 700;
  padding: 3px 10px;
  border-radius: 99px;
  background: var(--gold-wash);
  color: var(--gold-deep);
  border: 1px solid var(--gold-edge);
  vertical-align: middle;
}

/* The label column sizes to the longest label and no wider, so the value gets
 * everything else.  A fixed fraction has to be wrong in one direction or the
 * other: .85fr left long values like the sync result running to five lines on a
 * phone, and .55fr was narrow enough to wrap "Attendance marks" instead. */
.kv {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 9px 12px;
  font-size: 14.5px;
  /* The label is 13px and the value 14.5px, so their line boxes are different
   * heights and the value sat a couple of pixels low against its label -
   * "Medical" and "Asthma" not quite on the same line.  Aligning first
   * baselines rather than box tops is what actually makes them read as a pair. */
  align-items: baseline;
}
.kv dt { color: var(--ink-soft); font-size: 13px; white-space: nowrap; }
.kv dd { margin: 0; font-weight: 550; }
.kv > * { min-width: 0; overflow-wrap: break-word; }

/* The time and date of a log entry, held together against the right edge so the
 * column of them reads as one. */
.stampcol { flex: none; text-align: right; line-height: 1.35; }

.grid-history { display: flex; flex-wrap: wrap; gap: 7px; }
.grid-history .cell {
  width: 48px;
  text-align: center;
  font-size: 11px;
  color: var(--ink-soft);
}
.grid-history .cell b {
  display: block;
  margin-top: 3px;
  border-radius: 9px;
  padding: 6px 0;
  background: var(--surface);
  border: 1px solid var(--line);
  color: var(--ink-soft);
  font-size: 13px;
}
.grid-history .cell b.P { background: var(--present); border-color: transparent; color: #fff; }
.grid-history .cell b.A { background: var(--absent); border-color: transparent; color: #fff; }
.grid-history .cell b.E { background: var(--excused); border-color: transparent; color: #fff; }

/* A coach needs to know their marks are held safely, not wonder whether the
 * tap registered. */
.syncbar {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  background: var(--ink);
  color: var(--paper);
  padding: 12px 16px;
  padding-bottom: calc(12px + env(safe-area-inset-bottom));
  font-size: 13.5px;
  font-weight: 550;
  display: none;
  align-items: center;
  gap: 10px;
  z-index: 30;
  box-shadow: 0 -6px 20px -10px rgba(20, 17, 13, .35);
}
.syncbar.show { display: flex; }
.syncbar .dot {
  width: 9px; height: 9px; border-radius: 99px;
  background: var(--gold-bright); flex: none;
  box-shadow: 0 0 0 0 rgba(201, 161, 90, .6);
  animation: pulse 1.8s infinite;
}
@keyframes pulse {
  70% { box-shadow: 0 0 0 7px rgba(201, 161, 90, 0); }
  100% { box-shadow: 0 0 0 0 rgba(201, 161, 90, 0); }
}
/* Reserve room for the bar only while it is actually up.  It is hidden in the
 * ordinary case - app.js only shows it when marks are queued or the phone is
 * offline - so reserving always left 84px of dead white under the last card on
 * every page.  The growth is at the very bottom, so nothing the coach is
 * looking at moves when it appears. */
body.has-syncbar .wrap { padding-bottom: calc(22px + env(safe-area-inset-bottom)); }
body.syncbar-on .wrap { padding-bottom: calc(84px + env(safe-area-inset-bottom)); }

.accessalert {
  border-color: var(--absent);
  border-left-width: 4px;
  background: #fdecea;
}
.accessalert h2 { color: #a02a1c; }
@media (prefers-color-scheme: dark) {
  .accessalert { background: #2a1512; }
  .accessalert h2 { color: #ffb4a8; }
}

.empty { text-align: center; color: var(--ink-soft); padding: 30px 14px; }
.empty strong { color: var(--ink); }

/* Sandbox marker. Deliberately loud: mistaking demo players for the real
 * register is the worst confusion this app could cause. */
.demobar {
  background: linear-gradient(90deg, var(--gold-bright), var(--gold));
  color: #1a1206;
  font-size: 11px;
  font-weight: 750;
  letter-spacing: .09em;
  text-transform: uppercase;
  text-align: center;
  padding: 7px 12px;
}

/* ---- sign in -------------------------------------------------------- */

.signin { max-width: 400px; margin: 0 auto; }
.signin .hero { text-align: center; padding: 26px 0 4px; }
.signin .hero img { width: 96px; height: auto; }
.signin .hero h2 { margin: 16px 0 2px; font-size: 21px; font-weight: 700; }
/* Balanced rather than ragged: centred text that breaks late leaves "your PIN."
 * alone on a second line. */
.signin .hero p {
  margin: 0 auto;
  max-width: 30ch;
  color: var(--ink-soft);
  font-size: 14px;
  text-wrap: balance;
}

/* ---- narrow phones --------------------------------------------------- */

/* The roster row is avatar + name + three 46px buttons: 200px of fixed width
 * before the name gets any. Below 360px the marks take their own line rather
 * than crushing the name to nothing. */
@media (max-width: 359px) {
  /* The roster row is a grid, so the marks move to their own line by being
     given one, rather than by wrapping. */
  #roster .player {
    grid-template-areas:
      "avatar name"
      ".      flags"
      "marks  marks";
    grid-template-columns: auto minmax(0, 1fr);
  }
  #roster .player > .marks { justify-content: flex-end; margin-top: 9px; }
  /* Inert on the roster's grid rows; this is for the flex rows elsewhere. */
  .player { flex-wrap: wrap; }
  .rosterhead { flex-wrap: wrap; }
  .rosterhead .btn { width: 100%; }

  /* The coach's name costs ~90px of a 320px bar, which was enough to truncate
     the page title to "U11 regi...".  The icon alone still says "way out", and
     the button keeps its aria-label, so nothing is lost but the width. */
  .topbtn .who { display: none; }
  .topbtn { padding: 0 11px; }
  .topbtn .outicon { width: 17px; height: 17px; opacity: 1; }
}

/* Between 360 and 400 the bar is short by a handful of pixels, which was enough
 * to put an ellipsis on the subtitle - "Monday 3 Augu..." on a 375px phone.
 * The date is worth more than the padding around the sign-out button. */
@media (min-width: 360px) and (max-width: 399px) {
  .topbar-inner { gap: 8px; }
  .topbtn { padding: 0 9px; gap: 5px; }
}

/* ---- wide screens ---------------------------------------------------- */

/* Built for a phone, but opened on a laptop by whoever runs the programme.
 * Give that a considered layout rather than a phone column stranded in the
 * middle of a monitor. */
@media (min-width: 680px) {
  .wrap { max-width: 940px; padding: 24px 24px 32px; }
  .topbar { padding: 12px 24px; }
  /* 940 less the bar's 24px of padding each side, so the header content and the
     cards below it share one left edge. */
  .topbar-inner { max-width: 892px; }
  .topbar img { height: 36px; }
  .topbar h1 { font-size: 17px; }
  .topbar h1 small { font-size: 12.5px; }

  .card { padding: 22px 24px; border-radius: 18px; }
  .card h2 { font-size: 19px; }

  .sessions { grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }
  .sessioncard .age { min-width: 68px; font-size: 18px; }

  .player { padding: 13px 0; }
  .player .name { font-size: 16px; }

  .kv { column-gap: 18px; font-size: 15px; }

  .signin { margin-top: 20px; }
  .signin .hero img { width: 118px; }
  .signin .hero h2 { font-size: 24px; }

  .syncbar {
    left: auto;
    right: 24px;
    bottom: 24px;
    border-radius: 99px;
    padding: 12px 20px;
    box-shadow: var(--shadow-lift);
  }
  /* The bar is a floating pill out of the flow here, so no reserve is needed
     in either state. */
  body.has-syncbar .wrap,
  body.syncbar-on .wrap { padding-bottom: 32px; }
}

/* Two roster columns as soon as a single column would strand the P/A/E buttons
 * away from the name.  At 900px one column put 660px of blank card between a
 * child's name and their own buttons; two columns halve the scroll as well. */
@media (min-width: 900px) {
  .rosterlist {
    display: grid;
    grid-template-columns: 1fr 1fr;
    column-gap: 32px;
  }
  /* In a two-column row-major grid the last two items are always the bottom of
     each column, whether the count is odd or even.  So this is the whole of the
     "no rule under the final row" rule. */
  .rosterlist .player:nth-last-child(-n + 2) {
    border-bottom: 0;
    padding-bottom: 2px;
  }
}

@media (min-width: 1040px) {
  .wrap { max-width: 1040px; }
  .topbar-inner { max-width: 992px; }
  .profilegrid {
    display: grid;
    grid-template-columns: 1.15fr .85fr;
    gap: 0 18px;
    align-items: start;
  }
  .profilegrid > .full { grid-column: 1 / -1; }
  /* Explicit stacks.  Left to fill itself the grid put 770px of cards in the
     first column against 253px in the second. */
  .profilegrid > .colmain { grid-column: 1; }
  .profilegrid > .colside { grid-column: 2; }
}

/* ---------------------------------------------------------- register checks --
   Two levels only, and the distinction is real rather than decorative: a
   "problem" is something actively wrong on the register now - a team training
   twice on one date, a child with no phone number - and a "gap" is information
   that has not been filled in.  Three levels would invite a middle one that
   nobody acts on. */
.sectionhead {
  font-size: 12px;
  font-weight: 750;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--gold-deep);
  margin: 22px 0 10px;
}
.sectionhead:first-child { margin-top: 4px; }

.card.check { border-left: 4px solid var(--line); }
.card.check.problem { border-left-color: var(--absent); }
.card.check.gap { border-left-color: var(--excused); }
.card.warn { border-left: 4px solid var(--excused); }

.checklist {
  margin: 10px 0 0;
  padding-left: 18px;
  color: var(--ink-soft);
  font-size: 14px;
  line-height: 1.55;
}
.checklist li { margin-bottom: 2px; }
.check .more { margin: 6px 0 0; font-style: italic; }

/* ------------------------------------------------------------- equipment --
   A short numeric form a coach fills in once a week, so the number field is
   wide enough to tap and the label does the work.  Not a table: at 375px a
   table of five rows wraps into something nobody can read one-handed. */
.kitlist { margin-top: 6px; }
.kitrow {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 9px 0;
  border-bottom: 1px solid var(--line);
}
.kitrow:last-child { border-bottom: 0; }
.kitrow label { flex: 1; margin: 0; font-weight: 600; }
.kitqty {
  width: 96px;
  flex: none;
  text-align: center;
  font-variant-numeric: tabular-nums;
}

.checkline {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 12px 0 0;
  font-weight: 500;
}
.checkline input { width: 20px; height: 20px; flex: none; }

/* Admin only, and desktop-shaped: a week-by-coach grid is the one thing here
   that genuinely wants width.  It scrolls inside its own box rather than
   pushing the page sideways. */
.tablewrap { overflow-x: auto; margin-top: 8px; }
.grid { border-collapse: collapse; width: 100%; font-size: 14px; }
.grid th, .grid td {
  padding: 8px 10px;
  border-bottom: 1px solid var(--line);
  text-align: left;
  white-space: nowrap;
}
.grid th { font-size: 12px; text-transform: uppercase; letter-spacing: .05em;
           color: var(--gold-deep); }
.grid td.mid { text-align: center; }
.tick { color: var(--present); font-weight: 800; }

/* The admin console.
 *
 * This is the one part of the app that is genuinely a desktop screen: Craig or
 * Emmanuel at a desk, not a coach on a touchline.  So the tiles go to a grid
 * with the width available rather than staying a single column, while still
 * collapsing cleanly on a phone - an admin does open it from one.
 *
 * `auto-fit` with a minimum rather than fixed breakpoints, so the count per row
 * follows the container and there is no width at which a lone tile sits
 * stranded on its own line. */
.tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 12px;
  margin-bottom: 16px;
}
/* Three across once there is room, rather than however many auto-fit decides.
   There are six tiles, so auto-fit at this width lands on five and leaves one
   stranded on a row of its own, which reads as a mistake rather than a grid. */
@media (min-width: 760px) {
  .tiles { grid-template-columns: repeat(3, 1fr); }
}
.tile {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 16px 18px;
  background: var(--paper);
  border: 1px solid var(--line);
  border-left: 4px solid var(--line);
  border-radius: var(--radius-sm);
  text-decoration: none;
  color: inherit;
  box-shadow: var(--shadow-sm);
  min-height: 96px;
}
.tile:hover { border-color: var(--gold); box-shadow: var(--shadow); }
.tile.good { border-left-color: var(--present); }
.tile.warn { border-left-color: var(--excused); }
.tile.bad  { border-left-color: var(--absent); }
.tilevalue { font-size: 30px; font-weight: 800; line-height: 1.1; }
.tilelabel { font-size: 14px; font-weight: 700; }
.tilenote  { font-size: 12.5px; color: var(--ink-soft); line-height: 1.4; }

/* Settings.  A label, its control, and why it matters - the help text is not
 * decoration here, because most of these values fail in ways that are invisible
 * from the screen that sets them. */
.field { margin-bottom: 18px; }
.field:last-child { margin-bottom: 0; }
.field > label {
  display: block;
  font-size: 14px;
  font-weight: 700;
  margin-bottom: 6px;
}
.fieldhelp {
  margin: 6px 0 0;
  font-size: 13px;
  line-height: 1.5;
  color: var(--ink-soft);
}
.tag {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 8px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .03em;
  text-transform: uppercase;
  vertical-align: middle;
}
/* Which layer a value came from, and the two read differently on purpose: one
 * is somebody's decision, the other is the deployment's. */
.tag.here { background: var(--gold-wash); color: var(--gold-deep);
            border: 1px solid var(--gold-edge); }
.tag.env  { background: var(--surface); color: var(--ink-soft);
            border: 1px solid var(--line); font-weight: 600; }

/* The public coach guide.  Read once, on a phone, by somebody who has not
   signed in - so it is wider-spaced and larger than the app itself, which is
   built for a thumb in a hurry rather than for reading. */
.lede { font-size: 17px; line-height: 1.55; margin: 0 0 18px; }
.card.guide { padding-bottom: 18px; }
.chapternum {
  margin: 0 0 2px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--gold-deep);
}
.card.guide video {
  display: block;
  width: 100%;
  margin: 14px 0 4px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--line);
  background: var(--ink-black, #060606);
  /* The recordings are 390x844.  Without this the poster and the first frame
     size the box differently and the card jumps as the video loads. */
  aspect-ratio: 390 / 844;
  max-height: 70vh;
  object-fit: contain;
}
.steps { margin: 14px 0 0; padding-left: 22px; }
.steps li { margin-bottom: 9px; line-height: 1.55; }
.steps b { font-weight: 700; }
.warn-inline {
  display: block;
  margin-top: 5px;
  padding: 7px 10px;
  border-left: 3px solid var(--excused);
  background: var(--gold-wash);
  font-size: 14px;
  line-height: 1.45;
}

/* Reports.  Same scroll-inside-its-own-box rule as .grid, and it matters more
   here: the attendance table is seven columns, so on a 375px phone letting the
   page scroll sideways instead would carry the header off screen with it.
   `white-space: normal` on the last column, because "what to do about it" is a
   sentence and nowrap would make that one cell as wide as a paragraph. */
.report { border-collapse: collapse; width: 100%; font-size: 14px; }
.report th, .report td {
  padding: 8px 10px;
  border-bottom: 1px solid var(--line);
  text-align: left;
  white-space: nowrap;
  vertical-align: top;
}
.report th {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: .05em;
  color: var(--gold-deep);
  white-space: nowrap;
}
.report td:first-child { font-weight: 600; }
.report td:last-child { white-space: normal; min-width: 200px; }
.report tbody tr:nth-child(even) { background: var(--surface); }

.meta.purpose { margin-top: 10px; padding-top: 8px; border-top: 1px solid var(--line); }
.meta.caveat { margin-top: 6px; font-style: italic; }

.itemlist { list-style: none; margin: 10px 0 0; padding: 0; }
.itemlist li {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 0;
  border-bottom: 1px solid var(--line);
}
.itemlist li.retired { color: var(--ink-soft); }
/* `.grow` is scoped per component in this file rather than defined globally, so
   it has to be declared here too - without it the Retire button sits against
   the item name instead of at the right edge. */
.itemlist .grow { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.btn.small { padding: 5px 11px; font-size: 13px; }
