/* Elite Soccer League fundraising site.
 *
 * One stylesheet, no framework: the pages are forms, lists and one meter,
 * and a framework would be more bytes than the whole site.
 *
 * The palette is not this site's own. It is the league's, the same tokens
 * the uniform store already serves to the same parents, so a family that
 * bought a jersey last week recognises this page as the same organisation.
 * A brand change is an edit to the :root block and nothing else.
 *
 * Montserrat is served from this app. The store and the league's landing
 * pages self-host the same two files, so nothing here asks a font CDN for
 * a round trip or hands a third party a log line for every parent who
 * opens their child's page.
 *
 * Order of the file: fonts, tokens, reset, layout, type, cards, forms,
 * buttons, tables, the meter and stat tiles, page-specific bits, admin,
 * and the two media queries at the end.
 */

/* The two font URLs carry a ?v= stamp because that is what makes the app
 * serve them "immutable" for a year rather than for five minutes (see
 * static_assets.py). A stylesheet cannot call static_url, so this file is
 * the fonts' manifest: replacing a .woff2 means bumping the stamp here,
 * and that edit changes this file's own hash, so every browser refetches
 * the stylesheet and reads the new font URL. Never point one of these at a
 * bare /static path; tests/test_static_cache.py fails the build if you do.
 */

@font-face {
  font-family: "Montserrat";
  src: url("/static/fonts/Montserrat-Regular.woff2?v=montserrat-1") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Montserrat";
  src: url("/static/fonts/Montserrat-SemiBold.woff2?v=montserrat-1") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}

/* ---------------------------------------------------------------- tokens */

/* --brand is the league's light blue and it leads: it is the fill under a
 * primary button, the meter's colour and the accent rule on a card. It is
 * bright enough that white type on it measures 2.1:1, so it is never a
 * background for white text and never text itself (2.1:1 on white). A
 * button in --brand carries --ink, which measures 8.4:1 - the same pattern
 * the yellow accent already uses.
 *
 * --brand-deep is the step that has to be read: links, focus rings, the
 * secondary button's word, the primary button's edge. It has been lightened
 * as far as it can go and still clear 4.5:1 on every surface it lands on -
 * white (5.1:1), --page (4.7:1) and --brand-wash (4.6:1). One step lighter
 * (#0d7ab6) is 4.3:1 on the wash and would be an illegible link there.
 *
 * --accent is the league's yellow. On white it measures about 1.7:1, so it
 * appears only as a fill or a rule with --accent-ink on it, never as text.
 */
:root {
  color-scheme: light dark;

  --brand: #4dbbff;
  --brand-deep: #0c74ae;
  --brand-wash: #eaf6ff;
  --accent: #facc15;
  --accent-ink: #4a3600;
  --accent-wash: #fff8dd;

  --ink: #14181f;
  --ink-soft: #556070;
  --paper: #ffffff;
  --page: #f4f6f9;
  --rule: #e6eaef;
  --rule-strong: #c2cbd6;

  --good: #10633c;
  --good-wash: #e9f6ef;
  --bad: #b02420;
  --bad-wash: #fdf0ef;

  /* Derived roles. Everything below is expressed in the tokens above, so a
   * dark-mode re-pick only has to restate the roles it actually changes. */
  --link: var(--brand-deep);
  --focus: var(--brand-deep);

  /* Light theme: the masthead is paper, not navy. The brand blue does the
     work as a rule under it rather than as a slab behind the logo. */
  --masthead: var(--paper);
  --masthead-ink: var(--ink);
  --masthead-ink-soft: #556070;
  --masthead-rule: var(--brand);

  /* The primary button is the light blue under dark ink. The fill itself
   * is only 2.0:1 against the page, which is under the 3:1 a control's
   * boundary needs, so the button also carries a --btn-border in
   * --brand-deep (4.7:1 against the page) and is never a bare fill. */
  --btn-ink: var(--ink);
  --btn-bg: var(--brand);
  --btn-bg-hover: #35b2ff;
  --btn-bg-active: #1ea6fa;
  --btn-border: var(--brand-deep);
  --btn-quiet-ink: var(--ink-soft);
  --btn-danger-ink: var(--bad);
  --btn-danger-wash: var(--bad-wash);

  --field-bg: #ffffff;
  --meter-track: #e3edf5;
  --meter-fill: var(--brand);
  --meter-marker: var(--rule-strong);
  --zebra: #fafbfd;

  /* The type scale. 400 for prose, 600 for anything that names something.
   * Nothing on the site uses a size that is not one of these. */
  --text-xs: 0.75rem;
  --text-sm: 0.875rem;
  --text-base: 1rem;
  --text-lg: 1.125rem;
  --text-xl: 1.5rem;
  --text-hero: 2.25rem;

  --radius: 14px;
  --radius-control: 10px;
  --radius-sm: 6px;

  /* Depth is three layers, not one blur: a hairline contact shadow that
   * separates the card from the page, a short ambient one, and a long soft
   * one that does the lifting. A single large blur reads as fog. */
  --shadow: 0 1px 1px rgba(20, 24, 31, 0.05),
            0 2px 4px rgba(20, 24, 31, 0.04),
            0 8px 20px rgba(20, 24, 31, 0.05);
  --shadow-lift: 0 1px 2px rgba(20, 24, 31, 0.06),
                 0 6px 12px rgba(20, 24, 31, 0.06),
                 0 18px 38px rgba(20, 24, 31, 0.10);
  --shadow-button: 0 1px 1px rgba(20, 24, 31, 0.10),
                   0 2px 6px rgba(12, 116, 174, 0.18);

  --gutter: 1rem;
  --measure: 34rem;
}

/* Dark mode is chosen, not inverted. Every step below was re-picked against
 * the dark surface: the blue is lifted to stay a light blue rather than
 * becoming navy-on-navy, the washes are deep tints of their own hue rather
 * than grey, and the status inks are the light ends of their ramps because
 * #10633c and #b02420 are unreadable on a dark card. */
@media (prefers-color-scheme: dark) {
  :root {
    --brand: #4dbbff;
    --brand-deep: #a9e0ff;
    --brand-wash: #16344a;
    --accent: #facc15;
    --accent-ink: #ffe08a;
    --accent-wash: #332907;

    --ink: #e7edf5;
    --ink-soft: #b3c0cf;
    --paper: #1b222d;
    --page: #11161d;
    --rule: #2d3846;
    --rule-strong: #4a596b;

    --good: #7ce3ae;
    --good-wash: #163526;
    --bad: #ffada6;
    --bad-wash: #3a1c19;

    --masthead: #142438;
    --masthead-ink: #f2f6fb;
    --masthead-ink-soft: #b0c3d6;
    --masthead-rule: #5a7d9e;

    /* The fill is already 8.4:1 against the dark page, so the button needs
     * no edge to be found; the border token goes transparent rather than
     * drawing a line that says nothing. */
    --btn-ink: #06202e;
    --btn-bg: #4dbbff;
    --btn-bg-hover: #74caff;
    --btn-bg-active: #2ea6f2;
    --btn-border: transparent;
    --btn-danger-ink: #ff9c94;
    --btn-danger-wash: #2d1513;

    --field-bg: #161d26;
    --meter-track: #2a3a4c;
    --meter-fill: #4dbbff;
    --meter-marker: #5f7288;
    --zebra: #202938;

    --shadow: 0 1px 1px rgba(0, 0, 0, 0.5),
              0 2px 6px rgba(0, 0, 0, 0.35),
              0 10px 24px rgba(0, 0, 0, 0.35);
    --shadow-lift: 0 2px 6px rgba(0, 0, 0, 0.45),
                   0 18px 38px rgba(0, 0, 0, 0.5);
    --shadow-button: 0 1px 2px rgba(0, 0, 0, 0.5);
  }
}

/* ----------------------------------------------------------------- reset */

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  -webkit-text-size-adjust: 100%;
}

/* overflow-wrap is inherited, and it is declared here once so that every
 * surface printing something a family typed -- a statement, a donor's
 * message, a name, an address -- wraps it inside its container, including
 * the next surface somebody writes. A statement with one long unbroken
 * string in it (a pasted tracking link, a run-on hashtag) used to render as
 * a single line past the viewport and scroll the whole public page
 * sideways.
 *
 * ``anywhere`` rather than ``break-word``: both break a word that will not
 * fit, but only ``anywhere`` also shrinks an element's intrinsic minimum
 * width, which is what lets a flex or grid item -- the donor's message on
 * the dashboard, a table cell -- narrow below that word instead of widening
 * its whole row. Ordinary prose is untouched either way: a break happens
 * only where there is no other wrap opportunity. */
body {
  margin: 0;
  background-color: var(--page);
  color: var(--ink);
  font-family: "Montserrat", system-ui, -apple-system, "Segoe UI", Arial, sans-serif;
  font-size: var(--text-base);
  line-height: 1.55;
  overflow-wrap: anywhere;
}

img {
  max-width: 100%;
  height: auto;
}

a {
  color: var(--link);
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
}

a:hover {
  text-decoration-thickness: 2px;
}

/* Focus is never removed, only made worth looking at. Two rings, the inner
 * one in the page colour, so the outline reads on a card, on the masthead
 * and on a button fill alike. */
:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* --------------------------------------------------------------- masthead */

header.site {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 0.75rem 1rem;
  padding: 0.75rem var(--gutter);
  background: var(--masthead);
  color: var(--masthead-ink);
  /* 3px so the brand blue reads as a deliberate rule on a white masthead;
     at 1px on paper it is nearly invisible. */
  border-bottom: 3px solid var(--masthead-rule);
}

header.site a {
  color: var(--masthead-ink);
  text-decoration: none;
}

header.site a:hover {
  text-decoration: underline;
}

header.site .brand {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  font-weight: 600;
  font-size: var(--text-lg);
  letter-spacing: -0.01em;
}

/* The combined ESL + CFA lockup is landscape, 669x380 (about 1.76:1). The
 * markup carries those intrinsic numbers so the browser reserves the right
 * box before the file arrives; the drawn size is set here, by height, and
 * the width follows the ratio. At 40px tall the lockup is about 70px wide,
 * which leaves the wordmark and the nav room at 360px. */
header.site .brand img {
  display: block;
  width: auto;
  height: 40px;
  object-fit: contain;
}

header.site nav {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.35rem 1rem;
  font-size: var(--text-sm);
}

header.site nav a {
  color: var(--masthead-ink-soft);
  padding: 0.3rem 0;
}

header.site nav a:hover {
  color: var(--masthead-ink);
}

header.site nav span {
  color: var(--masthead-ink-soft);
}

header.site nav :focus-visible {
  outline-color: var(--brand);
}

/* The sign-out control lives in the masthead, so it is a quiet outline
 * rather than a filled button competing with the page's own call to
 * action. base.html still writes two inline properties on it; both of them
 * are widths this rule also sets, so the result is the same either way. */
header.site nav button {
  margin: 0;
  padding: 0.35rem 0.8rem;
  font-size: var(--text-sm);
  background: transparent;
  color: var(--masthead-ink);
  border: 1px solid var(--masthead-rule);
  box-shadow: none;
}

header.site nav button:hover {
  background: rgba(255, 255, 255, 0.1);
  color: var(--masthead-ink);
}

header.site nav button:active {
  background: rgba(255, 255, 255, 0.16);
}

/* ----------------------------------------------------------------- layout */

main {
  max-width: 62rem;
  margin: 0 auto;
  padding: 1.75rem var(--gutter) 4rem;
}

main.narrow {
  max-width: var(--measure);
}

/* A few pages open a second <main> inside the first for their own width.
 * Keep the padding off it so the gutter is not doubled. */
main main {
  padding: 0;
  max-width: none;
}

section + section {
  margin-top: 2rem;
}

footer.site {
  border-top: 1px solid var(--rule);
  color: var(--ink-soft);
  font-size: var(--text-sm);
  padding: 1.5rem var(--gutter) 3rem;
  text-align: center;
}

/* ------------------------------------------------------------------- type */

h1,
h2,
h3,
h4 {
  font-weight: 600;
  letter-spacing: -0.01em;
  line-height: 1.25;
}

h1 {
  font-size: var(--text-xl);
  margin: 0 0 0.35rem;
}

h2 {
  font-size: var(--text-lg);
  margin: 0 0 0.6rem;
}

h3 {
  font-size: var(--text-base);
  margin: 0 0 0.5rem;
}

p {
  margin: 0 0 1rem;
}

p.lede {
  color: var(--ink-soft);
  font-size: var(--text-lg);
  margin: 0 0 1.5rem;
}

.muted,
p.muted,
.hint,
.help,
.note {
  color: var(--ink-soft);
  font-size: var(--text-sm);
}

.note {
  max-width: 44rem;
}

.error {
  color: var(--bad);
}

.warn {
  background: var(--accent-wash);
  border: 1px solid var(--accent);
  border-radius: var(--radius-control);
  color: var(--ink);
  padding: 0.7rem 0.9rem;
}

.badge,
.tag {
  display: inline-block;
  padding: 0.1rem 0.5rem;
  border-radius: 999px;
  background: var(--brand-wash);
  color: var(--brand-deep);
  border: 1px solid var(--rule);
  font-size: var(--text-xs);
  font-weight: 600;
  white-space: nowrap;
}

.flash {
  border-radius: var(--radius-control);
  padding: 0.7rem 0.9rem;
  margin: 0 0 1.25rem;
  border: 1px solid var(--rule);
  background: var(--paper);
  font-size: var(--text-sm);
}

.flash.error,
.flash-error {
  background: var(--bad-wash);
  border-color: var(--bad);
  color: var(--bad);
}

.flash.notice {
  background: var(--good-wash);
  border-color: var(--good);
  color: var(--good);
}

/* ------------------------------------------------------------------ cards */

.card,
.panel {
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 1.25rem;
}

.card > :last-child,
.panel > :last-child {
  margin-bottom: 0;
}

.card-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 1rem;
  grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
}

.card-head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.25rem 0.6rem;
  margin-bottom: 0.5rem;
}

.card-head h2 {
  margin: 0;
}

/* The child's portrait on a dashboard card. Small and round, centred
 * against a row that is otherwise aligned on its baseline, and never
 * wider than its own 44px: the name is still the thing you read first. */
.card-thumb {
  flex: none;
  align-self: center;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  object-fit: cover;
  background: var(--brand-wash);
  border: 1px solid var(--rule);
}

.card-links,
.actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 0.75rem;
  margin: 1rem 0 0;
}

.actions form {
  margin: 0;
}

/* ------------------------------------------------------------------ forms */

label {
  display: block;
  font-weight: 600;
  font-size: var(--text-sm);
  margin: 1rem 0 0.35rem;
}

label.checkbox,
label.variant,
label.claim-match {
  font-weight: 400;
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
}

label.variant,
label.claim-match {
  border: 1px solid var(--rule);
  border-radius: var(--radius-control);
  padding: 0.7rem 0.85rem;
  background: var(--paper);
  flex-wrap: wrap;
}

label.variant:hover,
label.claim-match:hover {
  border-color: var(--brand);
}

label.variant span {
  flex-basis: 100%;
  color: var(--ink-soft);
  font-size: var(--text-sm);
}

input[type="email"],
input[type="password"],
input[type="text"],
input[type="search"],
input[type="date"],
input[type="number"],
input[type="file"],
input:not([type]),
select,
textarea {
  width: 100%;
  max-width: 100%;
  min-height: 2.75rem;
  padding: 0.6rem 0.7rem;
  border: 1px solid var(--rule-strong);
  border-radius: var(--radius-control);
  background: var(--field-bg);
  color: var(--ink);
  font: inherit;
}

textarea {
  min-height: 6rem;
  resize: vertical;
}

select {
  appearance: none;
  padding-right: 2rem;
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
                    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: calc(100% - 1.05rem) calc(50% + 2px),
                       calc(100% - 0.75rem) calc(50% + 2px);
  background-size: 6px 6px, 6px 6px;
  background-repeat: no-repeat;
}

input[type="checkbox"],
input[type="radio"] {
  width: auto;
  min-height: 0;
  margin: 0.25rem 0 0;
  accent-color: var(--brand-deep);
  flex: none;
}

input:focus-visible,
select:focus-visible,
textarea:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 1px;
  border-color: var(--focus);
}

fieldset {
  margin: 0 0 1.25rem;
  padding: 1rem;
  border: 1px solid var(--rule);
  border-radius: var(--radius-control);
}

legend {
  font-weight: 600;
  font-size: var(--text-sm);
  padding: 0 0.4rem;
}

/* The filter row above an admin table: labels side by side, each control
 * only as wide as it needs to be. */
form.filters {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 0.75rem;
  margin: 0 0 1.25rem;
  padding: 1rem;
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}

form.filters label {
  margin: 0;
  flex: 1 1 11rem;
}

form.filters input,
form.filters select {
  margin-top: 0.3rem;
}

form.filters button {
  margin: 0;
}

.field {
  margin-bottom: 1rem;
}

/* ---------------------------------------------------------------- buttons */

button,
.button,
input[type="submit"] {
  display: inline-block;
  margin-top: 1rem;
  padding: 0.7rem 1.25rem;
  min-height: 2.75rem;
  border: 1px solid var(--btn-border);
  border-radius: var(--radius-control);
  background: var(--btn-bg);
  color: var(--btn-ink);
  font: inherit;
  font-weight: 600;
  line-height: 1.2;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  box-shadow: var(--shadow-button);
  transition: background-color 120ms ease, box-shadow 120ms ease,
              transform 80ms ease;
}

button:hover,
.button:hover,
input[type="submit"]:hover {
  background: var(--btn-bg-hover);
  color: var(--btn-ink);
  text-decoration: none;
}

button:active,
.button:active,
input[type="submit"]:active {
  background: var(--btn-bg-active);
  transform: translateY(1px);
  box-shadow: none;
}

button:focus-visible,
.button:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

button[disabled],
button[disabled]:hover {
  opacity: 0.55;
  cursor: not-allowed;
  transform: none;
}

/* Secondary: the same shape in outline, for the action beside the action.
 * ``.preset`` is the donate form's amount row, which is a rank of equal
 * choices and so must not be a rank of primary buttons. */
button.secondary,
.button.secondary,
button.preset,
#preview-button {
  background: var(--paper);
  color: var(--brand-deep);
  border-color: var(--rule-strong);
  box-shadow: var(--shadow);
}

button.secondary:hover,
.button.secondary:hover,
button.preset:hover,
#preview-button:hover {
  background: var(--brand-wash);
  border-color: var(--brand);
  color: var(--brand-deep);
}

button.secondary:active,
.button.secondary:active,
button.preset:active,
#preview-button:active {
  background: var(--brand-wash);
  box-shadow: none;
}

/* The chosen amount. Only ever set by js/amount-presets.js, which is also
 * what makes the click a choice rather than a checkout, so with the script
 * blocked there is no selected state to draw and nothing to draw it on. */
button.preset[aria-pressed="true"] {
  background: var(--brand-wash);
  border-color: var(--brand);
  color: var(--brand-deep);
  box-shadow: inset 0 0 0 1px var(--brand);
}

/* Quiet: a link that has to sit in a row of buttons, and the destructive
 * verbs. Destructive is not red-by-default - it turns red under the cursor,
 * where the intent is real, and always says what it does in words. */
button.quiet,
.button.quiet {
  background: transparent;
  color: var(--btn-quiet-ink);
  border-color: var(--rule-strong);
  box-shadow: none;
}

button.quiet:hover,
.button.quiet:hover {
  background: var(--page);
  color: var(--ink);
}

button.danger,
.button.danger {
  background: transparent;
  color: var(--btn-danger-ink);
  border-color: var(--btn-danger-ink);
  box-shadow: none;
}

button.danger:hover,
.button.danger:hover {
  background: var(--btn-danger-wash);
  color: var(--btn-danger-ink);
}

/* A copy button beside the string it copies. Quiet and small, because the
 * string is the thing being read and the button is the thing being pressed:
 * it keeps the full touch height and loses the block margin buttons carry,
 * so it sits on the line rather than under it. The fallback box and the
 * spoken confirmation wrap onto their own line on a phone. */
.copy-field {
  display: inline-flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-left: 0.5rem;
}

.copy-field button {
  margin-top: 0;
  padding: 0.4rem 0.9rem;
  font-size: 0.9rem;
  font-weight: 600;
}

.copy-field .copy-fallback {
  flex: 1 1 14rem;
  min-width: 0;
  margin: 0;
}

.copy-field .copy-status {
  margin: 0;
}

/* ----------------------------------------------------------------- tables */

/* Wide tables scroll inside their own box. The page never scrolls
 * sideways: a horizontal scrollbar on <body> loses people the nav. */
.table-scroll {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  margin-bottom: 1.25rem;
}

.table-scroll > table {
  margin: 0;
  border: 0;
  box-shadow: none;
  border-radius: 0;
  min-width: 100%;
}

table {
  width: 100%;
  border-collapse: collapse;
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  margin-bottom: 1.25rem;
  font-size: var(--text-sm);
  overflow: hidden;
}

th,
td {
  text-align: left;
  padding: 0.6rem 0.75rem;
  border-bottom: 1px solid var(--rule);
  vertical-align: top;
}

thead th {
  position: sticky;
  top: 0;
  background: var(--paper);
  color: var(--ink-soft);
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  white-space: nowrap;
  border-bottom: 1px solid var(--rule-strong);
}

tbody tr:nth-child(even) {
  background: var(--zebra);
}

tbody tr:last-child td {
  border-bottom: 0;
}

tfoot th {
  background: var(--brand-wash);
  border-top: 1px solid var(--rule-strong);
  border-bottom: 0;
}

.num {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

td form,
td button {
  margin: 0;
}

td button {
  padding: 0.35rem 0.7rem;
  min-height: 0;
  font-size: var(--text-xs);
}

td .actions,
td.actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  align-items: center;
  margin: 0;
}

tr.highlight {
  background: var(--accent-wash);
}

pre.rendered {
  max-width: 100%;
  overflow-x: auto;
  white-space: pre-wrap;
  word-break: break-word;
  background: var(--page);
  border: 1px solid var(--rule);
  border-radius: var(--radius-control);
  padding: 0.75rem;
  font-size: var(--text-xs);
}

dl.stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
  gap: 0.75rem;
  margin: 0 0 1.5rem;
  padding: 0;
}

dl.stat-grid dt {
  color: var(--ink-soft);
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

dl.stat-grid dd {
  margin: 0 0 0.5rem;
  font-size: var(--text-lg);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

/* ------------------------------------------------- the meter and the tiles
 *
 * The rule for this component: the money is the hero and the bar is
 * furniture. The amount raised is set large, in ink, never in the bar's
 * colour, because a number tinted with the data colour reads as decoration
 * and is harder to read at a glance. The meter underneath is one hue -
 * magnitude, not category - and it is recessive by design.
 *
 * Colour is never the only signal. Every meter carries its own sentence
 * ("$1,240 of $25,000 - 5%") and the ARIA attributes that say the same
 * thing to a screen reader, so the bar can be invisible and nothing is
 * lost.
 */

.meter {
  margin: 0 0 1.25rem;
}

.meter__hero {
  display: block;
  font-size: var(--text-hero);
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: -0.02em;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

.meter__hero-note {
  display: block;
  color: var(--ink-soft);
  font-size: var(--text-sm);
  margin-top: 0.15rem;
}

/* The track carries the goal: it is the whole width, so the empty part of
 * it is the distance left. The goal marker is a hairline on the same
 * surface rather than a second colour block competing with the fill. */
.meter__track {
  position: relative;
  height: 14px;
  margin: 0.75rem 0 0.4rem;
  border-radius: 999px;
  background: var(--meter-track);
  overflow: hidden;
}

/* Anchored to the start of the track, square at the anchor and 4px rounded
 * at the data end, so the fill reads as a measurement from zero rather than
 * a floating capsule. The 2px right margin is the surface gap that keeps
 * the fill from touching the goal marker at 100%. */
.meter__fill {
  position: absolute;
  inset: 0 auto 0 0;
  min-width: 3px;
  border-radius: 0 4px 4px 0;
  background: var(--meter-fill);
  transition: width 400ms ease;
}

.meter__fill--empty {
  background: transparent;
}

.meter__marker {
  position: absolute;
  top: 0;
  bottom: 0;
  right: 0;
  width: 2px;
  background: var(--meter-marker);
  border-radius: 1px;
}

.meter__label {
  display: block;
  color: var(--ink-soft);
  font-size: var(--text-sm);
  font-variant-numeric: tabular-nums;
}

.meter__label strong {
  color: var(--ink);
  font-weight: 600;
}

/* Over the goal. The bar cannot say more than "full", so the words do. */
.meter__over {
  display: inline-block;
  margin-left: 0.4rem;
  padding: 0.05rem 0.45rem;
  border-radius: 999px;
  background: var(--good-wash);
  color: var(--good);
  font-size: var(--text-xs);
  font-weight: 600;
}

/* Day one. A zero total is the normal state of a new page and has to look
 * deliberate: the hero still reads as money, and the sentence under it is
 * an instruction rather than an apology. */
.meter--zero .meter__hero {
  color: var(--ink);
}

.meter--zero .meter__track {
  border: 1px dashed var(--rule-strong);
  background: transparent;
}

.stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9.5rem, 1fr));
  gap: 0.75rem;
  margin: 0 0 1.5rem;
  padding: 0;
  list-style: none;
}

.stat {
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 0.85rem 1rem;
}

.stat__label {
  display: block;
  color: var(--ink-soft);
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.stat__value {
  display: block;
  margin-top: 0.15rem;
  font-size: var(--text-xl);
  font-weight: 600;
  line-height: 1.2;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}

.stat__context {
  display: block;
  margin-top: 0.15rem;
  color: var(--ink-soft);
  font-size: var(--text-xs);
}

/* The inline drawings: a ball, a goal and a ring. Two colours at most, both
 * inherited, and decorative - every one of them is aria-hidden and says
 * nothing the text does not already say. */
.deco {
  display: block;
  color: var(--brand);
}

.deco--ink {
  color: var(--ink-soft);
}

/* The club crest is a raster file rather than inherited paths, so it needs
 * its own two lines: contain rather than the site-wide cover, because a
 * badge cropped to fill a box is a badge with its edges cut off, and an
 * explicit aspect so the row does not reflow while the file is loading. */
.deco--crest {
  aspect-ratio: 1 / 1;
  object-fit: contain;
}

.hero-figure {
  display: flex;
  align-items: center;
  gap: 1rem;
}

.hero-figure .deco {
  flex: none;
  width: 56px;
  height: 56px;
}

/* Whatever sits beside the mark takes the rest of the row, and may shrink:
 * without min-width a flex item refuses to go below its content and the
 * meter pushes the page sideways on a phone. */
.hero-figure > :not(.deco) {
  flex: 1 1 auto;
  min-width: 0;
}

.hero-figure h1,
.hero-figure h2 {
  margin-bottom: 0;
}

.hero-figure .meter {
  margin-bottom: 0;
}

/* ------------------------------------------------------------ page pieces */

/* The donate box is the one place on a public page that should look like an
 * object you act on, so it is a card with a brand rule across its top. */
form.donate {
  background: var(--paper);
  border: 1px solid var(--rule);
  border-top: 3px solid var(--brand);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lift);
  padding: 1.25rem;
  margin: 1.5rem 0;
}

form.donate fieldset.amounts {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

form.donate fieldset.amounts legend,
form.donate fieldset.designation legend {
  font-size: var(--text-base);
}

form.donate button.preset {
  margin-top: 0;
  flex: 1 1 5.5rem;
}

form.donate label.custom {
  flex: 1 1 100%;
  margin-top: 0.5rem;
}

form.donate fieldset.designation label {
  font-weight: 400;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 0.4rem 0;
}

form.donate #minimum-note {
  flex-basis: 100%;
  margin: 0.4rem 0 0;
}

/* What the card will be charged. It sits directly above the button that
 * charges it and is the most consequential line on the form, so it reads as a
 * statement rather than as another hint: full size, on the ink colour, with
 * the brand wash behind it to separate it from the fields.
 *
 * js/donation-total.js unhides it once there is an amount to total. */
.donate-total {
  margin: 1.25rem 0 0;
  padding: 0.7rem 0.9rem;
  border: 1px solid var(--rule);
  border-radius: var(--radius-control);
  background: var(--brand-wash);
  color: var(--ink);
  font-weight: 600;
}

form.donate button.primary {
  width: 100%;
  font-size: var(--text-lg);
  padding: 0.85rem 1.25rem;
}

/* The composer's preview. It is an iframe on purpose - the message is
 * rendered by the same code that sends it, and letting this stylesheet into
 * it would show the sender something no mail client will produce - so it is
 * framed like a card and left white inside, whatever the page's theme. */
.preview-frame {
  display: block;
  width: 100%;
  min-height: 60vh;
  background: #ffffff;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}

/* A child's photo slot is 3:2 whatever arrived in it.
 *
 * Families upload group shots, screenshots, portraits held sideways and, once,
 * a 3000x500 panorama. Sizing the box from the image meant the page's shape
 * changed per child and the panorama came out as a 6:1 band with no face in
 * it. So the slot states its own shape and the photo covers it, centred: the
 * stored file is still the whole photograph the parent chose, and a later
 * design change is one number here rather than a re-upload from every family.
 *
 * object-fit only does something because the height is pinned. Without the
 * ratio the box is already the image's own shape and there is nothing to
 * cover, which is how this rule looked before and why it had no effect.
 *
 * The same class is the edit page's saved photo and its just-chosen preview,
 * so a parent is shown the shape the public page will use. */
.player .photo,
.profile-photo-preview {
  width: 100%;
  max-width: 28rem;
  aspect-ratio: 3 / 2;
  border-radius: var(--radius);
  border: 1px solid var(--rule);
  object-fit: cover;
  object-position: center;
  background: var(--brand-wash);
}

/* The moderation table's thumbnail. Square, because the column is, and
 * cropped for the same reason as the slot above: an admin deciding a takedown
 * has to be able to recognise the photo. */
.photo-thumb {
  width: 64px;
  height: 64px;
  border-radius: var(--radius-control);
  border: 1px solid var(--rule);
  object-fit: cover;
  object-position: center;
  background: var(--brand-wash);
}

.player .team {
  color: var(--ink-soft);
  font-weight: 600;
  margin-top: 0;
}

.player .statement {
  font-size: var(--text-lg);
  max-width: 40rem;
}

.share .qr {
  background: #ffffff;
  padding: 0.5rem;
  border: 1px solid var(--rule);
  border-radius: var(--radius-control);
}

.share .url {
  color: var(--ink-soft);
  font-size: var(--text-sm);
  word-break: break-all;
}

.roster-list,
.donation-list,
.claim-list,
.year-list {
  list-style: none;
  margin: 0 0 1.25rem;
  padding: 0;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--paper);
  box-shadow: var(--shadow);
  overflow: hidden;
}

.roster-list li,
.donation-list li,
.claim-list li,
.year-list li {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.35rem 0.75rem;
  padding: 0.7rem 1rem;
  border-bottom: 1px solid var(--rule);
}

.roster-list li:last-child,
.donation-list li:last-child,
.claim-list li:last-child,
.year-list li:last-child {
  border-bottom: 0;
}

.roster-list li a,
.claim-name {
  font-weight: 600;
}

.roster-list li span,
.donation-amount {
  margin-left: auto;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}

.donation-message {
  flex-basis: 100%;
  margin: 0.25rem 0 0;
  color: var(--ink-soft);
  font-style: italic;
}

.claim-row form,
.claim-row button,
.year-list a {
  margin: 0;
}

.claim-row {
  gap: 0.5rem 0.75rem;
}

.claim-action,
.year-list a {
  margin-left: auto;
}

.claim-row-taken {
  background: var(--page);
}

/* ------------------------------------------------------------------ admin */

.admin-head {
  margin-bottom: 1.5rem;
}

.admin-head h1 {
  margin-bottom: 0.75rem;
}

.admin-nav {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  padding: 0.5rem;
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}

.admin-nav a {
  padding: 0.35rem 0.7rem;
  border-radius: 999px;
  font-size: var(--text-sm);
  text-decoration: none;
  color: var(--ink-soft);
}

.admin-nav a:hover {
  background: var(--brand-wash);
  color: var(--brand-deep);
}

.admin-body > h2:first-child {
  margin-top: 0;
}

.claim-columns {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
  gap: 1rem;
}

.claim-side dl {
  margin: 0;
}

.claim-side dt {
  color: var(--ink-soft);
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin-top: 0.6rem;
}

.claim-side dd {
  margin: 0.1rem 0 0;
}

.claim-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 0.75rem;
  margin-top: 1rem;
  padding-top: 1rem;
  border-top: 1px solid var(--rule);
}

.claim-actions form {
  display: flex;
  align-items: flex-end;
  gap: 0.5rem;
  margin: 0;
}

.claim-actions input {
  width: auto;
  min-width: 14rem;
  max-width: 100%;
}

.claim-actions button {
  margin: 0;
}

.todo {
  list-style: none;
  padding: 0;
  display: grid;
  gap: 0.5rem;
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
}

.todo li a {
  display: block;
  padding: 0.75rem 1rem;
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: var(--radius-control);
  box-shadow: var(--shadow);
  text-decoration: none;
  font-weight: 600;
}

.todo li a:hover {
  border-color: var(--brand);
}

.statement {
  max-width: 24rem;
}

/* --------------------------------------------------------------- motion */

/* Everything above that moves, stops moving here. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ------------------------------------------------------------ wider than a
 * phone. The layout above is the narrow one; these two steps only add
 * room. */

@media (min-width: 40em) {
  :root {
    --gutter: 1.5rem;
  }

  main {
    padding-top: 2.25rem;
  }

  h1 {
    font-size: 1.85rem;
  }

  .meter__hero {
    font-size: 2.5rem;
  }
}

@media (min-width: 60em) {
  :root {
    --gutter: 2rem;
  }
}

/* Very narrow phones. Nothing may overflow at 360px: the amount buttons
 * stack, the hero number steps down, and tables keep their own scroller. */
@media (max-width: 24em) {
  .meter__hero {
    font-size: 1.85rem;
  }

  form.donate button.preset {
    flex-basis: 100%;
  }

  header.site {
    gap: 0.5rem;
  }

  header.site .brand {
    font-size: var(--text-base);
    gap: 0.45rem;
  }

  header.site .brand img {
    height: 32px;
  }
}
