/* ==========================================================================
   Gnosall St Lawrence PSFA
   Palette carried over from the printed newsletter: school-jumper maroon,
   dusky rose, teal and mustard, on blush paper. The arch — shelter, roofline,
   the years a child passes through — is the one structural idea, used for the
   logo, the hero, and every section marker.
   ========================================================================== */

:root {
  --maroon: #6b1e33;
  --maroon-deep: #4c1424;
  --rose: #c98490;
  --rose-soft: #ecc9ce;
  --rose-wash: #f7e7e7;
  --teal: #2e6f73;
  --teal-deep: #204d50;
  --teal-wash: #e2eeed;
  --mustard: #d9a227;
  --mustard-soft: #f4e2b4;
  --mustard-wash: #fbf2dd;

  /* First, second and third. A flat wash of any of these reads as pastel rather
     than as metal, so each place is a diagonal sheen instead: a bright edge, a
     saturated core, a second highlight three-quarters of the way down. That
     light-dark-light run across the face is the thing the eye actually reads as
     gold, silver and bronze, and it costs nothing but a gradient.

     The solid token beside each gradient is the same metal at full strength,
     for borders and for the winners list under the result. The ink is dark
     enough to clear 4.5:1 against the *darkest* stop of its own gradient, since
     a holder's name can land anywhere across the face of the square. */
  --gold: #b8860b;
  --gold-ink: #3d2b04;
  --gold-metal: linear-gradient(145deg, #fbeaa8 0%, #e6b422 30%, #c8920f 55%, #f2d878 76%, #d9a520 100%);
  --silver: #8b959e;
  --silver-ink: #262d34;
  --silver-metal: linear-gradient(145deg, #fafbfc 0%, #ccd3d9 30%, #9aa4ad 55%, #eef1f4 76%, #b4bec6 100%);
  --bronze: #9c5f28;
  --bronze-ink: #40230c;
  --bronze-metal: linear-gradient(145deg, #f6ddc4 0%, #dda877 30%, #c08a58 55%, #efd0ae 76%, #cf9a6b 100%);

  --paper: #fbf6f5;
  --paper-2: #f4e9e7;
  --card: #ffffff;
  --ink: #2a1f22;
  --ink-soft: #6b5c60;
  --line: #e6d5d2;

  --focus: var(--teal);

  --display: "Fraunces", "Iowan Old Style", Georgia, serif;
  --body: "Karla", ui-sans-serif, system-ui, "Segoe UI", Helvetica, Arial, sans-serif;
  /* Lucky Squares stickers. The colour emoji fonts have to be named ahead of
     anything else, because Karla and most of what follows it carry text-form
     glyphs for a good part of the set and will render a clover as a flat black
     outline given the chance. Every platform's own colour font is here, then
     Twemoji for a Firefox install that has it, then Segoe UI Symbol as the last
     stop before the body face. */
  --emoji: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", "Twemoji Mozilla",
    "Android Emoji", "EmojiOne Color", "Segoe UI Symbol", sans-serif;

  --wrap: 68rem;
  --wrap-narrow: 44rem;
  --radius: 14px;
  --radius-sm: 9px;

  --shadow: 0 1px 2px rgba(76, 20, 36, .06), 0 8px 24px -12px rgba(76, 20, 36, .22);
  --shadow-lg: 0 2px 4px rgba(76, 20, 36, .07), 0 22px 48px -20px rgba(76, 20, 36, .3);
}

*, *::before, *::after { box-sizing: border-box; }

/* `hidden` means hidden, whatever else is on the element. The browser's own
   rule for it is weaker than any author rule that sets display, so a class
   saying `display: block` quietly wins and the thing stays on screen — which is
   how the Lucky Squares sticky bar once got stuck to the bottom of a desktop
   window with nothing in it. Anything the JavaScript hides by setting `hidden`
   depends on this. */
[hidden] { display: none !important; }

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

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--body);
  font-size: 1.0625rem;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
}

img { max-width: 100%; height: auto; display: block; }

h1, h2, h3, h4 {
  font-family: var(--display);
  font-optical-sizing: auto;
  font-weight: 600;
  line-height: 1.12;
  letter-spacing: -.015em;
  color: var(--maroon);
  margin: 0 0 .5em;
  text-wrap: balance;
}

h1 { font-size: clamp(2.25rem, 6vw, 3.6rem); line-height: 1.03; letter-spacing: -.025em; }
h2 { font-size: clamp(1.65rem, 3.2vw, 2.4rem); }
h3 { font-size: clamp(1.15rem, 1.8vw, 1.4rem); }
h4 { font-size: 1.0625rem; font-family: var(--body); font-weight: 700; letter-spacing: 0; }

p, ul, ol { margin: 0 0 1.1em; }
ul, ol { padding-left: 1.15em; }
li { margin-bottom: .4em; }
li::marker { color: var(--rose); }

a { color: var(--teal-deep); text-decoration-thickness: 1px; text-underline-offset: .18em; }
a:hover { color: var(--maroon); }

strong { font-weight: 700; }

:focus-visible {
  outline: 3px solid var(--focus);
  outline-offset: 2px;
  border-radius: 3px;
}

.skip {
  position: absolute; left: -9999px; top: 0;
  background: var(--maroon); color: #fff; padding: .8rem 1.2rem; z-index: 100;
  border-radius: 0 0 var(--radius-sm) 0;
}
.skip:focus { left: 0; color: #fff; }

.wrap { width: 100%; max-width: var(--wrap); margin-inline: auto; padding-inline: clamp(1.1rem, 4vw, 2rem); }
.wrap--narrow { max-width: var(--wrap-narrow); }

.visually-hidden {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

/* ---------- the arch mark ---------- */

.mark { display: block; flex: none; }
.mark__a { fill: var(--rose); }
.mark__b { fill: var(--teal); }
.mark__c { fill: var(--mustard); }
.mark__ground { fill: var(--maroon); }
/* On the maroon header and footer the mark's own maroon baseline disappears,
   which leaves the arches floating. Swap it for something that reads. */
.site-header .mark__ground,
.admin-header .mark__ground { fill: var(--mark-ground-light, #f0dcdf); }
.site-footer .mark__ground { fill: var(--rose); }
.mark--eyebrow { width: 20px; height: 12px; color: inherit; margin-top: .32em; }
.mark--brand { width: 39px; height: 39px; }
.mark--footer { width: 40px; height: 40px; margin-bottom: .7rem; }

.eyebrow {
  /* flex-start keeps the arch on the first line when the label wraps; the
     margin below re-centres it against that line. */
  display: flex; align-items: flex-start; gap: .55rem;
  font-size: .74rem; font-weight: 700; letter-spacing: .17em;
  text-transform: uppercase; color: var(--teal-deep);
  margin: 0 0 1rem;
}

/* ---------- header ---------- */

.site-header { background: var(--maroon); color: #fff; --focus: var(--mustard); }

.site-header__inner {
  display: flex; align-items: center; flex-wrap: wrap; justify-content: flex-end;
  gap: clamp(.6rem, 1.4vw, 1.2rem);
  min-height: 72px; padding-block: .7rem;
}

.brand {
  display: flex; align-items: center; gap: .65rem; text-decoration: none;
  color: #fff; margin-right: auto;
  flex: none;           /* never shrink to make room for the nav */
  white-space: nowrap;  /* "St Lawrence" must never break across two lines */
}
.brand__text { display: flex; flex-direction: column; line-height: 1; }
/* PSFA is what the site is actually for, so it is the one that carries the
   weight. The village and the school name sit above it as quiet context, and
   the whole block stays about as wide as "St Lawrence" so the header still
   fits a phone. */
.brand__place {
  font-size: .58rem; font-weight: 700; letter-spacing: .28em;
  text-transform: uppercase; color: var(--rose-soft); margin-bottom: .22rem;
}
.brand__name {
  font-size: .78rem; font-weight: 600; letter-spacing: .04em;
  color: var(--rose-soft); margin-bottom: .18rem;
}
.brand__sub {
  font-family: var(--display); font-size: 1.6rem; font-weight: 700;
  letter-spacing: .04em; color: #fff;
}
.brand:hover { color: #fff; }
.brand:hover .brand__place, .brand:hover .brand__name { color: var(--mustard-soft); }

.site-nav { display: flex; gap: clamp(.5rem, 1vw, .85rem); }
.site-nav--wide { flex-wrap: nowrap; }
.site-nav a {
  color: var(--on-main, #f6e6e8); text-decoration: none; white-space: nowrap;
  font-size: .78rem; font-weight: 600; letter-spacing: .06em; text-transform: uppercase;
  padding: .35rem 0; border-bottom: 2px solid transparent;
}
.site-nav a:hover { color: #fff; border-bottom-color: var(--rose); }
.site-nav a[aria-current="page"] { color: #fff; border-bottom-color: var(--mustard); }

.site-header__actions { display: flex; align-items: center; gap: .6rem; }

.basket-pill {
  display: inline-flex; align-items: center; gap: .4rem; flex: none;
  background: rgba(255, 255, 255, .13); color: #fff; text-decoration: none;
  font-size: .74rem; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  padding: .48rem .7rem; border-radius: 999px;
}
.basket-pill__icon { width: 15px; height: 15px; flex: none; }
/* The word costs ~79px of a header that has none to spare; the aria-label on
   the link says "Reserving N items" for anyone who cannot see the icon. */
.basket-pill__label { display: none; }
.basket-pill__count {
  background: var(--mustard); color: var(--maroon-deep);
  border-radius: 999px; padding: 0 .42rem; min-width: 1.35em; text-align: center;
}
.basket-pill:hover { background: rgba(255, 255, 255, .22); color: #fff; }


/* mobile menu, built on <details> so it works without JavaScript */
.nav-toggle { display: none; }
.nav-toggle > summary {
  list-style: none; cursor: pointer;
  font-size: .74rem; font-weight: 700; letter-spacing: .12em; text-transform: uppercase;
  color: #fff; padding: .55rem .85rem;
  border: 1px solid rgba(255, 255, 255, .35); border-radius: 999px;
}
.nav-toggle > summary::-webkit-details-marker { display: none; }
.nav-toggle > summary { display: flex; align-items: center; gap: .45rem; }
.nav-toggle__icon { display: none; width: 16px; height: 12px; fill: currentColor; }
.nav-toggle[open] > summary { background: rgba(255, 255, 255, .14); }
/* The drawer hangs from the header, not from the Menu button. Hung from the
   button's right edge it grew leftwards, and on a 320px phone it started off
   the screen before anything in it was even opened. Pinned to the header's
   content edge and capped at the width of the screen, it cannot leave it.

   It scrolls inside itself when a group or two are open and it is taller than
   the phone, so the last link is always reachable without losing the header. */
.site-header__inner { position: relative; }
.site-nav--drawer {
  position: absolute; right: clamp(1.1rem, 4vw, 2rem); top: calc(100% - .2rem); z-index: 40;
  flex-direction: column; gap: 0;
  width: min(18rem, calc(100% - 2 * clamp(1.1rem, 4vw, 2rem)));
  max-height: calc(100vh - 5.5rem); max-height: calc(100dvh - 5.5rem);
  overflow-y: auto; overscroll-behavior: contain;
  background: var(--maroon-deep); border-radius: var(--radius); padding: .5rem;
  box-shadow: var(--shadow-lg);
}
.site-nav--drawer a { padding: .7rem .8rem; border-bottom: 0; border-radius: var(--radius-sm); white-space: normal; }
.site-nav--drawer a:hover, .site-nav--drawer a[aria-current="page"] {
  background: rgba(255, 255, 255, .1); border-bottom: 0;
}

/* The full nav, a basket pill and Donate need roughly 1120px together. Below
   that, fall back to the drawer rather than let the header wrap.

   Grouping the nine links into six entries bought back real width, so the full
   bar now survives down to a small laptop instead of collapsing on one. */
@media (max-width: 64rem) {
  .site-nav--wide { display: none; }
  .nav-toggle { display: block; }
}

/* ---------- buttons ---------- */

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .45rem;
  font-family: var(--body); font-size: .9rem; font-weight: 700;
  letter-spacing: .02em; text-decoration: none; text-align: center;
  padding: .78rem 1.35rem; border-radius: 999px; border: 2px solid transparent;
  cursor: pointer; transition: transform .12s ease, background-color .15s ease, color .15s ease;
}
.btn:active { transform: translateY(1px); }
.btn__ext { font-size: .85em; opacity: .75; }

.btn--primary { background: var(--maroon); color: #fff; }
.btn--primary:hover { background: var(--maroon-deep); color: #fff; }

.btn--secondary { background: transparent; color: var(--maroon); border-color: var(--rose); }
.btn--secondary:hover { background: var(--rose-wash); color: var(--maroon); border-color: var(--maroon); }

.btn--donate { background: var(--mustard); color: var(--maroon-deep); }
.btn--donate:hover { background: var(--mustard-soft); color: var(--maroon-deep); }

.btn--teal { background: var(--teal); color: #fff; }
.btn--teal:hover { background: var(--teal-deep); color: #fff; }

.btn--ghost { background: transparent; color: var(--teal-deep); border-color: var(--line); padding: .55rem 1rem; font-size: .82rem; }
.btn--ghost:hover { background: var(--teal-wash); color: var(--teal-deep); }

.btn--small { padding: .5rem 1rem; font-size: .8rem; }
.btn--block { width: 100%; }
.btn[aria-disabled="true"], .btn:disabled { opacity: .45; pointer-events: none; }

.btn-row { display: flex; flex-wrap: wrap; gap: .7rem; }

/* ---------- the hero arch ---------- */
/* Nested padding builds the coloured bands, so they stay concentric at every
   width without any fixed sizes. */

.hero { padding: clamp(2rem, 5vw, 3.5rem) 0 clamp(2.5rem, 6vw, 4.5rem); }

.arch {
  --band: clamp(7px, 1.4vw, 15px);
  --curve: clamp(80px, 19vw, 200px);
  max-width: 50rem; margin-inline: auto;
  padding: var(--band) var(--band) 0; background: var(--rose);
  border-radius: 50% 50% 0 0 / var(--curve) var(--curve) 0 0;
}
.arch__2 {
  padding: var(--band) var(--band) 0; background: var(--teal);
  border-radius: 50% 50% 0 0 / calc(var(--curve) - var(--band)) calc(var(--curve) - var(--band)) 0 0;
}
.arch__3 {
  padding: var(--band) var(--band) 0; background: var(--mustard);
  border-radius: 50% 50% 0 0 / calc(var(--curve) - var(--band) * 2) calc(var(--curve) - var(--band) * 2) 0 0;
}
.arch__core {
  background: var(--card);
  border-radius: 50% 50% 0 0 / calc(var(--curve) - var(--band) * 3) calc(var(--curve) - var(--band) * 3) 0 0;
  padding: calc(var(--curve) * .66 + .5rem) clamp(1.25rem, 5vw, 3.25rem) clamp(2.25rem, 5vw, 3.25rem);
  text-align: center;
}
/* The archway stands on a line of ground, like the mark it comes from. */
.hero__ground {
  max-width: 50rem; margin: 0 auto;
  height: clamp(8px, 1.5vw, 14px); background: var(--maroon);
  border-radius: 0 0 6px 6px;
}

.hero .eyebrow { justify-content: center; }
.hero h1 { margin-bottom: .45em; }
.hero .lede { max-width: 34rem; margin-inline: auto; }
.hero .btn-row { justify-content: center; margin-top: 1.75rem; }

.lede { font-size: clamp(1.05rem, 1.9vw, 1.28rem); line-height: 1.6; color: var(--ink); }
.lede p:last-child { margin-bottom: 0; }

/* ---------- generic sections ---------- */

.section { padding-block: clamp(2.75rem, 6vw, 4.5rem); }
.section > .wrap > :last-child { margin-bottom: 0; }

.band { background: var(--paper-2); }
.band--teal { background: var(--teal); color: var(--on-second, #eaf4f3); --focus: var(--mustard); }
.band--teal h2, .band--teal h3 { color: #fff; }
/* Buttons carry their own colours — excluding them here stops the band's link
   colour landing mustard-on-mustard. */
.band--teal a:not(.btn) { color: var(--mustard-soft); }
.band--teal .eyebrow { color: var(--mustard-soft); }
.band--teal p, .band--teal li, .band--teal .lede, .band--teal .field__help { color: var(--on-second, #eaf4f3); }
.band--teal .prose ul li::before { background: var(--mustard-soft); }
.band--rose { background: var(--rose-wash); }
.band--mustard { background: var(--mustard-wash); }

.page-head { padding: clamp(2.25rem, 5vw, 3.75rem) 0 clamp(1.25rem, 3vw, 2rem); }
.page-head .lede { margin-top: .85rem; }

.prose > :last-child { margin-bottom: 0; }
.prose ul { list-style: none; padding-left: 0; }
.prose ul li { position: relative; padding-left: 1.5rem; }
.prose ul li::before {
  content: ""; position: absolute; left: 0; top: .62em;
  width: 12px; height: 7px; background: var(--rose);
  border-radius: 6px 6px 0 0;
}

.section-title { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }
.section-title h2 { margin-bottom: .6rem; }

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

.cards { display: grid; gap: clamp(1rem, 2vw, 1.5rem); grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); }

.card {
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  padding: clamp(1.25rem, 2.4vw, 1.75rem); box-shadow: var(--shadow);
  display: flex; flex-direction: column;
}
.card > :last-child { margin-bottom: 0; }
.card h3 { margin-bottom: .4rem; }
.card p { color: var(--ink-soft); }
.card__more { margin-top: auto; padding-top: .9rem; font-weight: 700; font-size: .85rem; }

/* The arch reappears as a card top-edge, tinted by position. */
.card--arch { position: relative; padding-top: calc(clamp(1.25rem, 2.4vw, 1.75rem) + .5rem); overflow: hidden; }
.card--arch::before {
  content: ""; position: absolute; inset: 0 0 auto; height: 5px; background: var(--rose);
}
.card--arch:nth-child(3n+2)::before { background: var(--teal); }
.card--arch:nth-child(3n+3)::before { background: var(--mustard); }

/* ---------- home ---------- */

.shelter { max-width: 42rem; }

.facebook-strip { display: grid; gap: 1.25rem; align-items: center; }
@media (min-width: 44rem) { .facebook-strip { grid-template-columns: 1fr auto; } }

/* ---------- news ---------- */

/* The latest post, under the hero. The same width as the arch, so it reads as
   standing on the same ground, and pulled up into the hero's bottom padding so
   it belongs to the top of the page rather than floating between sections. */
.news-strip { margin-top: calc(-1 * clamp(1rem, 3vw, 2rem)); padding-bottom: clamp(2rem, 5vw, 3.25rem); }
.news-strip__inner {
  position: relative; max-width: 50rem; margin-inline: auto;
  display: grid; gap: .2rem 1.5rem; align-items: center;
  background: var(--card); border: 1px solid var(--line); border-left: 5px solid var(--mustard);
  border-radius: var(--radius); padding: 1rem clamp(1.1rem, 2.5vw, 1.5rem);
  box-shadow: var(--shadow);
  transition: box-shadow .15s ease, transform .15s ease;
}
.news-strip__inner:hover { box-shadow: var(--shadow-lg); transform: translateY(-1px); }
@media (min-width: 44rem) { .news-strip__inner { grid-template-columns: 1fr auto; } }
.news-strip__label {
  grid-column: 1 / -1; margin: 0;
  font-weight: 700; font-size: .78rem; letter-spacing: .08em; text-transform: uppercase; color: var(--accent-ink, #85601a);
}
.news-strip__label time { font-weight: 500; letter-spacing: .02em; text-transform: none; color: var(--ink-soft); }
.news-strip__label time::before { content: "·"; margin: 0 .45rem 0 .3rem; color: var(--ink-soft); }
.news-strip__title { font-size: clamp(1.15rem, 2vw, 1.35rem); margin: 0; }
.news-strip__summary { margin: .2rem 0 0; color: var(--ink-soft); font-size: .98rem; line-height: 1.5; }
.news-strip__more { font-weight: 700; white-space: nowrap; text-decoration: none; padding-top: .35rem; }
@media (min-width: 44rem) { .news-strip__more { padding-top: 0; } }
/* The link covers the whole bar, and the focus ring is drawn round the bar it
   covers rather than round the two words. */
.news-strip__more::after { content: ""; position: absolute; inset: 0; border-radius: var(--radius); }
.news-strip__more:focus-visible { outline: none; }
.news-strip__more:focus-visible::after { outline: 3px solid var(--focus); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) {
  .news-strip__inner { transition: none; }
  .news-strip__inner:hover { transform: none; }
}

.news-post { padding-block: clamp(1.75rem, 4vw, 2.5rem); border-top: 1px solid var(--line); }
.news-list > .news-post:first-child { border-top: 0; padding-top: 0; }
.news-post__date {
  margin: 0 0 .35rem;
  font-weight: 700; font-size: .82rem; letter-spacing: .06em; text-transform: uppercase; color: var(--teal-deep);
}
.news-post__title { font-size: clamp(1.5rem, 3vw, 2rem); margin-bottom: .6em; }
.news-post__title a { color: inherit; text-decoration: none; }
.news-post__title a:hover { text-decoration: underline; text-decoration-thickness: 2px; }
.news-post__photo { margin: 0 0 1.35rem; }
.news-post__photo picture { display: block; }
.news-post__photo img { width: 100%; border-radius: var(--radius); box-shadow: var(--shadow); }
.news-post__actions { display: flex; flex-wrap: wrap; align-items: center; gap: .75rem 1.5rem; margin-top: 1.4rem; }

.news-share { display: inline-flex; align-items: center; gap: .4rem; font-weight: 700; font-size: .92rem; }
.news-share__icon {
  width: 1.6rem; height: 1.6rem; padding: .25rem; border-radius: 50%;
  background: var(--teal-wash); color: var(--teal-deep); flex: none;
}

.news-pager {
  display: flex; justify-content: space-between; align-items: center; gap: 1rem;
  border-top: 1px solid var(--line); padding-top: 1.5rem; font-weight: 700;
}
.news-pager__where { font-weight: 500; font-size: .9rem; color: var(--ink-soft); }
.news-back { margin: 2rem 0 0; }

/* ---------- donate ---------- */

.donate-block {
  background: var(--card); border: 2px solid var(--mustard); border-radius: var(--radius);
  padding: clamp(1.5rem, 3vw, 2.5rem); box-shadow: var(--shadow);
}
.donate-block > :last-child { margin-bottom: 0; }
.donate-pending {
  background: var(--mustard-wash); border-radius: var(--radius-sm);
  padding: 1rem 1.15rem; margin-bottom: 0;
}

/* ---------- events ---------- */

.event-list { list-style: none; padding: 0; margin: 0; display: grid; gap: 1rem; }
.event {
  display: grid; gap: .35rem 1.5rem; background: var(--card);
  border: 1px solid var(--line); border-left: 5px solid var(--rose);
  border-radius: var(--radius); padding: 1.15rem clamp(1.1rem, 2.5vw, 1.6rem);
}
/* A stack on a phone. From 40rem the date moves into its own column, and
   from 48rem there is room for a third one holding "Add to my calendar". */
@media (min-width: 40rem) { .event { grid-template-columns: 9.5rem 1fr; } }
@media (min-width: 48rem) { .event { grid-template-columns: 9.5rem 1fr auto; } }
.event:nth-child(3n+2) { border-left-color: var(--teal); }
.event:nth-child(3n+3) { border-left-color: var(--mustard); }
.event__when {
  font-weight: 700; font-size: .82rem; letter-spacing: .06em; text-transform: uppercase;
  color: var(--teal-deep); padding-top: .2rem;
}
.event__when span { display: block; font-weight: 500; letter-spacing: .02em; text-transform: none; color: var(--ink-soft); }
.event__body > :last-child { margin-bottom: 0; }
.event__body h3 { margin-bottom: .25rem; }
.event__meta { font-size: .88rem; color: var(--ink-soft); margin-bottom: .4rem; }
/* On a phone this is the last thing in the card and spans the full width, so
   it is an easy thumb target. On a middling screen it tucks under the details
   rather than dropping into the narrow date column. On a wide one it sits out
   to the right, level with the event title. */
.event__action { align-self: start; padding-top: .9rem; }
.event__action .btn { width: 100%; }
@media (min-width: 40rem) {
  .event__action { grid-column: 2; }
  .event__action .btn { width: auto; }
}
@media (min-width: 48rem) {
  .event__action { grid-column: 3; padding-top: .1rem; }
  .event__action .btn { white-space: nowrap; }
}

/* ---------- the diary: PSFA events and the school planner together ---------- */

/* On a list holding both, the left edge says which is which, so the rotating
   three-colour stripe above has to give way. These sit after it deliberately:
   both selectors weigh the same, so source order is what decides. */
.event-list--diary .event--psfa { border-left-color: var(--maroon); }
.event-list--diary .event--school { border-left-color: var(--teal); }

.event__tag {
  font-size: .68rem; font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
  color: var(--ink-soft); margin-bottom: .3rem;
}
.event--psfa .event__tag { color: var(--maroon); }
.event--school .event__tag { color: var(--teal-deep); }

/* The PSFA stamp, watermarked into the empty right-hand end of the card.

   The stacking is the fiddly part. An absolutely positioned element paints
   above ordinary in-flow content whatever the source order, so the stamp would
   sit on top of the words. Giving the body its own stacking context (position
   plus a z-index) lets the stamp go to -1 and drop behind the text while
   staying in front of the card's white background -- which is where a
   watermark belongs, and it saves marking up every other child to outrank it.

   It hangs off the right edge, so the list clips its cards. Scoped to the
   diary list rather than .event, because nothing else needs it. */
.event-list--diary .event { overflow: hidden; }

.event__body { position: relative; z-index: 0; }

/* The body reserves a column on its right for the stamp and wraps its text
   before reaching it. That is what lets the stamp be properly dark: it is
   never behind any words, so there is no reading cost to pay for making it
   visible. Reserved on every diary card, not just the stamped ones, so the two
   kinds keep the same measure and the list stays even.

   The min-height guarantees the stamp fits. It does not have to match the
   stamp: the card is taller than the body by its own vertical padding, which
   is empty either way, so the stamp is free to overhang into it. 5rem of body
   makes the shortest card -- a bake sale with no details -- about 7.3rem tall
   against a 6.25rem stamp, which clears it without inflating every row in a
   list that can run to a whole school year. */
.event-list--diary .event__body {
  min-height: 5rem;
  padding-right: 6.75rem;
}

.event__stamp {
  position: absolute; z-index: -1;
  top: 50%; right: 0;
  width: 6.25rem; height: 6.25rem;
  transform: translateY(-50%) rotate(-11deg);
  color: var(--maroon);
  opacity: .55;
  pointer-events: none;
}
.event__stamp text {
  text-anchor: middle; fill: currentColor;
  font-family: var(--display); font-weight: 700;
}
.event__stamp-name { font-size: 24px; letter-spacing: 1px; }
.event__stamp-word { font-size: 8.5px; letter-spacing: 3px; }

/* A phone stacks the card into one column, so the stamp loses the width it had
   beside the text -- but not the room. It sits to the right of the heading
   instead, a size down so the reserved column does not squeeze a long title
   into too narrow a measure. */
@media (max-width: 40rem) {
  .event-list--diary .event__body { min-height: 6rem; padding-right: 5.9rem; }
  .event__stamp { width: 5.625rem; height: 5.625rem; }
}

.diary-filter { margin-bottom: 1rem; }
.diary-lede { color: var(--ink-soft); margin-bottom: 1.25rem; max-width: 46rem; }

/* The tick box takes the slot the single "Add to my calendar" button used to
   have, so it inherits that column layout unchanged. Sized as a comfortable
   thumb target rather than a form control. */
.event__pick {
  display: flex; gap: .55rem; align-items: flex-start; cursor: pointer;
  font-size: .85rem; font-weight: 600; color: var(--teal-deep); line-height: 1.35;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: .6rem .75rem; background: var(--paper);
}
.event__pick:hover { border-color: var(--teal); }
.event__pick input { width: 1.15rem; height: 1.15rem; flex: none; margin: 0; accent-color: var(--teal); }
.event__pick:has(input:checked) { border-color: var(--teal); background: var(--teal-wash); }
.event__pick:focus-within { outline: 3px solid var(--teal-wash); outline-offset: 1px; }

/* Pinned to the bottom of the screen, so the way to act on what you have
   ticked follows you down a whole term of dates. Same arrangement as the
   basket bar on the uniform rail, down to the body padding that stops it
   sitting on top of the footer -- it is the same job, and a second style of
   bottom bar would only make the site look like two sites.

   Fixed rather than sticky: it stays put through the sections below the list
   as well, which is where somebody who has scrolled past the end and changed
   their mind will be looking for it. */
.diary-bar {
  position: fixed; inset: auto 0 0 0; z-index: 30;
  background: var(--maroon-deep); color: #fff;
  box-shadow: 0 -6px 24px -10px rgb(76 20 36 / 55%);
  padding: .7rem 0 calc(.7rem + env(safe-area-inset-bottom));
}
.diary-bar__inner {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
}
.diary-bar__note { margin: 0; font-size: .8rem; line-height: 1.35; opacity: .78; }
.diary-bar .btn { flex: none; }
body.has-diary-bar { padding-bottom: 5.5rem; }

@media (max-width: 34rem) {
  .diary-bar__note { display: none; }
  .diary-bar .btn { width: 100%; }
}

/* ---------- what we fund ---------- */

.fund-total {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: .6rem 1.25rem;
  padding: clamp(1.25rem, 3vw, 2rem); background: var(--card);
  border: 1px solid var(--line); border-radius: var(--radius); box-shadow: var(--shadow);
  margin-bottom: 1.75rem;
}
.fund-total__figure { font-family: var(--display); font-size: clamp(2.4rem, 6vw, 3.6rem); font-weight: 700; color: var(--maroon); line-height: 1; }
.fund-total__label { font-size: .95rem; color: var(--ink-soft); }

.fund-columns { display: grid; gap: clamp(1.5rem, 4vw, 3rem); }
@media (min-width: 52rem) { .fund-columns { grid-template-columns: 1.4fr 1fr; align-items: start; } }

.fund-group { margin-bottom: 1.75rem; }
.fund-group:last-child { margin-bottom: 0; }
.fund-group h3 {
  font-family: var(--body); font-size: .78rem; font-weight: 700;
  letter-spacing: .15em; text-transform: uppercase; color: var(--teal-deep);
  padding-bottom: .5rem; border-bottom: 2px solid var(--rose-soft); margin-bottom: .9rem;
}
.fund-list { list-style: none; padding: 0; margin: 0; }
.fund-list li { display: flex; gap: .75rem; padding: .55rem 0; border-bottom: 1px solid var(--line); }
.fund-list li:last-child { border-bottom: 0; }
.fund-list li::before {
  content: ""; flex: none; width: 14px; height: 8px; margin-top: .5em;
  background: var(--mustard); border-radius: 7px 7px 0 0;
}
.fund-list strong { display: block; }
.fund-list span { color: var(--ink-soft); font-size: .92rem; }

.source-list { list-style: none; padding: 0; margin: 0; }
.source-list li { display: flex; justify-content: space-between; gap: 1rem; padding: .6rem 0; border-bottom: 1px solid var(--line); }
.source-list li:last-child { border-bottom: 0; }
.source-list b { font-variant-numeric: tabular-nums; color: var(--maroon); font-weight: 700; }
.source-list em { display: block; font-style: normal; font-size: .85rem; color: var(--ink-soft); }

.year-nav { display: flex; flex-wrap: wrap; gap: .5rem; margin-bottom: 2rem; }

/* ---------- uniform shop ---------- */

.filters { display: flex; flex-wrap: wrap; gap: 1.25rem; align-items: flex-end; margin-bottom: 1.75rem; }
.filter-group { display: flex; flex-direction: column; gap: .45rem; }
.filter-group__label { font-size: .74rem; font-weight: 700; letter-spacing: .14em; text-transform: uppercase; color: var(--ink-soft); }
.chips { display: flex; flex-wrap: wrap; gap: .4rem; }
.chip {
  display: inline-block; text-decoration: none; font-size: .82rem; font-weight: 600;
  padding: .42rem .85rem; border-radius: 999px;
  border: 1px solid var(--line); background: var(--card); color: var(--ink);
}
.chip:hover { border-color: var(--rose); color: var(--maroon); }
/* aria-pressed is the dashboard's filter chips; aria-current is the diary's.
   A link cannot honestly be "pressed" -- aria-pressed belongs to a button, and
   role=link does not support it -- so the public ones say which is current
   instead. Both look the same, which is the point of them sharing this rule. */
.chip[aria-pressed="true"],
.chip[aria-current] { background: var(--maroon); border-color: var(--maroon); color: #fff; }
.chip__count { opacity: .6; font-weight: 500; margin-left: .3rem; }

.stock-grid { display: grid; gap: clamp(1rem, 2vw, 1.5rem); grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr)); }

.stock-card {
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  overflow: hidden; box-shadow: var(--shadow); display: flex; flex-direction: column;
}
.stock-card__photo { aspect-ratio: 4 / 5; background: var(--paper-2); position: relative; }
.stock-card__photo img { width: 100%; height: 100%; object-fit: cover; }
/* Without a photo the 4:5 frame is a lot of empty space, so shrink it. */
.stock-card__photo--empty { aspect-ratio: 5 / 2; }
.stock-card__placeholder {
  display: grid; place-items: center; align-content: center; gap: .5rem; height: 100%;
}
.stock-card__placeholder .mark { width: 34px; height: 34px; opacity: .45; }
.stock-card__placeholder span {
  font-size: .72rem; font-weight: 700; letter-spacing: .12em;
  text-transform: uppercase; color: var(--ink-soft); opacity: .75;
}

/* The size, set like the label sewn into a jumper collar. */
.stock-card__size {
  background: var(--maroon); color: #fff;
  font-size: .72rem; font-weight: 700; letter-spacing: .19em; text-transform: uppercase;
  padding: .45rem .95rem;
}
.stock-card__body { padding: .95rem 1.05rem 1.15rem; display: flex; flex-direction: column; flex: 1; gap: .3rem; }
.stock-card__name { font-family: var(--display); font-size: 1.1rem; font-weight: 600; color: var(--maroon); margin: 0; }
.stock-card__price { font-size: .92rem; color: var(--ink-soft); margin: 0; }
.stock-card__price b { color: var(--ink); font-weight: 700; }
.stock-card__stock {
  display: inline-flex; align-items: center; gap: .45rem;
  font-size: .84rem; font-weight: 600; color: var(--teal-deep); margin: .15rem 0 .75rem;
}
.stock-card__stock::before { content: ""; width: .55rem; height: .55rem; border-radius: 50%; background: var(--teal); }
.stock-card__stock--low { color: var(--accent-ink, #8a5a12); }
.stock-card__stock--low::before { background: var(--mustard); }
.stock-card__stock--none { color: var(--ink-soft); }
.stock-card__stock--none::before { background: var(--line); }
.stock-card__notes { font-size: .86rem; color: var(--ink-soft); margin: 0 0 .75rem; }
.stock-card form { margin-top: auto; display: flex; gap: .5rem; align-items: center; }
.stock-card select { flex: none; width: 4.2rem; padding: .5rem; }

/* The card someone has just added from. The page reloads and lands on this
   card via its anchor, so the confirmation belongs here rather than in a strip
   at the top the eye has already scrolled past. */
.stock-card--added { border-color: var(--teal); box-shadow: 0 0 0 2px var(--teal-wash), var(--shadow); }
.stock-card__added {
  display: flex; align-items: center; gap: .4rem;
  font-size: .84rem; font-weight: 700; color: var(--teal-deep);
  background: var(--teal-wash); border-radius: var(--radius-sm);
  padding: .4rem .6rem; margin: 0 0 .75rem;
}
.stock-card__added::before { content: "✓"; font-size: .95rem; }



/* ---------- uniform stock alerts ---------- */

/* The sign-up under the rail. Bordered rather than banded, so it reads as a
   thing to fill in rather than another stripe of page. */
.alert-panel {
  background: var(--card); border: 2px solid var(--rose-soft); border-radius: var(--radius);
  padding: clamp(1.5rem, 3vw, 2.5rem); box-shadow: var(--shadow);
}
.alert-panel > h2 { margin-bottom: .6rem; }
.alert-form { margin-top: 1.25rem; }

.alert-sizes { border: 0; padding: 0; margin: 0; }
.alert-sizes legend {
  padding: 0; margin-bottom: .6rem;
  font-weight: 700; font-size: .95rem;
}
/* auto-fill rather than a fixed count: the ages come from whatever is on the
   rail, so the number of boxes is not known here. */
.alert-sizes__grid {
  display: grid; gap: .5rem;
  grid-template-columns: repeat(auto-fill, minmax(7.5rem, 1fr));
}

/* The whole tile is the hit area -- these get tapped on a phone, one-handed,
   usually while the child in question is asking for something. */
.alert-size {
  display: flex; align-items: center; gap: .55rem;
  padding: .6rem .75rem; cursor: pointer;
  background: var(--paper); border: 1px solid var(--line); border-radius: var(--radius-sm);
  font-size: .92rem; line-height: 1.2;
}
.alert-size:hover { border-color: var(--rose); background: var(--rose-wash); }
.alert-size input { width: 1.1rem; height: 1.1rem; margin: 0; accent-color: var(--maroon); flex: none; }
.alert-size:has(input:checked) {
  border-color: var(--maroon); background: var(--rose-wash); font-weight: 700;
}
/* :has() is recent enough to need a fallback that still shows the state. */
.alert-size input:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }

.alert-watches { list-style: none; padding: 0; margin: 1rem 0 0; }
.alert-watches li {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  padding: .75rem 0; border-bottom: 1px solid var(--line);
}
.alert-watches li:last-child { border-bottom: 0; }
.alert-watches form { margin: 0; }
/* ---------- the basket bar on the uniform rail ---------- */

/* Pinned so that the way out of browsing follows you down a long grid. */
.basket-bar {
  position: fixed; inset: auto 0 0 0; z-index: 30;
  background: var(--maroon-deep); color: #fff;
  box-shadow: 0 -6px 24px -10px rgba(76, 20, 36, .55);
  padding: .7rem 0 calc(.7rem + env(safe-area-inset-bottom));
}
.basket-bar__inner { display: flex; align-items: center; justify-content: space-between; gap: 1rem; }
.basket-bar__count { margin: 0; font-size: .95rem; line-height: 1.35; }
.basket-bar__count b { font-weight: 700; }
.basket-bar__note { display: block; font-size: .8rem; opacity: .78; }
.basket-bar .btn { flex: none; }
/* Clear the fixed bar so it never covers the last of the footer. */
body.has-basket-bar { padding-bottom: 5.5rem; }

@media (max-width: 30rem) {
  .basket-bar__inner { gap: .7rem; }
  .basket-bar__count { font-size: .85rem; }
  .basket-bar__note { display: none; }
}

.basket-table { width: 100%; border-collapse: collapse; margin-bottom: 1.5rem; }
.basket-table th {
  text-align: left; font-size: .74rem; font-weight: 700; letter-spacing: .13em;
  text-transform: uppercase; color: var(--ink-soft); padding: 0 .75rem .6rem 0;
  border-bottom: 2px solid var(--rose-soft);
}
.basket-table td { padding: .85rem .75rem .85rem 0; border-bottom: 1px solid var(--line); vertical-align: top; }
.basket-table tfoot td { border-bottom: 0; font-weight: 700; padding-top: 1rem; }
.basket-table .num { text-align: right; font-variant-numeric: tabular-nums; padding-right: 0; }

.reserve-layout { display: grid; gap: clamp(1.5rem, 4vw, 2.5rem); align-items: start; }
@media (min-width: 52rem) { .reserve-layout { grid-template-columns: 1.25fr .75fr; } }

.summary-box { background: var(--card); border: 1px solid var(--line); border-radius: var(--radius); padding: 1.35rem; box-shadow: var(--shadow); }
.summary-box h2 { font-size: 1.15rem; }
.summary-box > :last-child { margin-bottom: 0; }

.ref-badge {
  display: inline-block; font-family: var(--display); font-size: 1.5rem; font-weight: 700;
  background: var(--mustard-wash); border: 2px dashed var(--mustard);
  padding: .5rem 1.1rem; border-radius: var(--radius-sm); color: var(--maroon);
  letter-spacing: .04em;
}

/* ---------- committee, support, documents ---------- */

.people { list-style: none; padding: 0; margin: 0; display: grid; gap: .75rem; grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr)); }
.people li { background: var(--card); border: 1px solid var(--line); border-radius: var(--radius-sm); padding: .9rem 1.1rem; }
.people b { display: block; font-family: var(--display); font-size: 1.1rem; color: var(--maroon); }
.people span { font-size: .85rem; font-weight: 600; letter-spacing: .08em; text-transform: uppercase; color: var(--teal-deep); }
.people em { display: block; font-style: normal; font-size: .88rem; color: var(--ink-soft); margin-top: .25rem; }

.support-list { display: grid; gap: 1.25rem; }
.support-item {
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  padding: clamp(1.25rem, 2.5vw, 1.85rem); box-shadow: var(--shadow);
  display: grid; gap: .5rem 1.5rem;
}
@media (min-width: 44rem) { .support-item { grid-template-columns: 1fr auto; align-items: center; } }
.support-item > div > :last-child { margin-bottom: 0; }
.support-item h3 { margin-bottom: .3rem; }
.support-item__raised {
  display: inline-block; font-size: .78rem; font-weight: 700; letter-spacing: .06em;
  background: var(--teal-wash); color: var(--teal-deep);
  padding: .25rem .7rem; border-radius: 999px; margin-bottom: .5rem;
}

.doc-group { margin-bottom: 2.25rem; }
.doc-group:last-child { margin-bottom: 0; }
.doc-group > h2 {
  font-family: var(--body); font-size: .78rem; font-weight: 700;
  letter-spacing: .15em; text-transform: uppercase; color: var(--teal-deep);
  padding-bottom: .5rem; border-bottom: 2px solid var(--rose-soft); margin-bottom: 1rem;
}
.doc-list { list-style: none; padding: 0; margin: 0; }
.doc-list li { border-bottom: 1px solid var(--line); }
.doc-list li:last-child { border-bottom: 0; }
.doc-list a {
  display: flex; gap: 1rem; align-items: baseline; justify-content: space-between;
  flex-wrap: wrap; padding: .95rem 0; text-decoration: none; color: var(--ink);
}
.doc-list a:hover { color: var(--maroon); }
.doc-list a:hover .doc-list__title { text-decoration: underline; }
.doc-list__title { font-weight: 700; color: var(--maroon); }
.doc-list__desc { display: block; font-weight: 400; color: var(--ink-soft); font-size: .9rem; }
.doc-list__meta { font-size: .8rem; color: var(--ink-soft); letter-spacing: .04em; text-transform: uppercase; white-space: nowrap; }

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

.form-grid { display: grid; gap: 1.1rem; }
.form-row { display: grid; gap: 1.1rem; }
@media (min-width: 36rem) { .form-row--2 { grid-template-columns: 1fr 1fr; } }
@media (min-width: 44rem) { .form-row--3 { grid-template-columns: repeat(3, 1fr); } }

.field { display: flex; flex-direction: column; gap: .4rem; }
.field > label, .field__label { font-weight: 700; font-size: .92rem; color: var(--maroon); }
.field__help { font-size: .85rem; color: var(--ink-soft); margin: 0; }
.field__req { color: var(--rose); font-weight: 400; }

input[type="text"], input[type="email"], input[type="tel"], input[type="url"],
input[type="password"], input[type="number"], input[type="date"], input[type="search"],
input[type="file"], select, textarea {
  font-family: var(--body); font-size: 1rem; color: var(--ink);
  background: var(--card); border: 1.5px solid var(--line); border-radius: var(--radius-sm);
  padding: .7rem .85rem; width: 100%;
}
textarea { min-height: 8rem; resize: vertical; line-height: 1.6; }
input:hover, select:hover, textarea:hover { border-color: var(--rose); }
input:focus, select:focus, textarea:focus { border-color: var(--teal); outline: 3px solid var(--teal-wash); outline-offset: 0; }
select { appearance: none; background-image: linear-gradient(45deg, transparent 50%, var(--maroon) 50%), linear-gradient(135deg, var(--maroon) 50%, transparent 50%); background-position: calc(100% - 18px) 50%, calc(100% - 13px) 50%; background-size: 5px 5px, 5px 5px; background-repeat: no-repeat; padding-right: 2.25rem; }

.checkbox { display: flex; gap: .6rem; align-items: flex-start; font-weight: 500; }
.checkbox input { width: auto; margin-top: .28rem; }

/* A group of checkboxes that answer one question together — the roles on a
   website administrator. A fieldset rather than loose boxes so the question is asked
   once, and the browser's default border and padding are dropped because the
   panel around it is already a box. */
.field__set { border: 0; padding: 0; margin: 0; display: grid; gap: .75rem; }
.field__set legend { padding: 0; margin-bottom: .35rem; font-weight: 600; }
.field__set .field__help { margin: 0 0 .25rem; }
.field__set .checkbox span { font-weight: 500; }

/* A checkbox inside a table cell, under a control it qualifies. Smaller and
   tighter than .checkbox, which is sized for a form somebody is filling in
   rather than for a dense admin table. */
.table-check {
  display: flex;
  gap: .35rem;
  align-items: flex-start;
  margin-top: .3rem;
  font-size: .78rem;
  line-height: 1.25;
  color: var(--ink-soft);
  cursor: pointer;
}
.table-check input { width: auto; margin: .15rem 0 0; flex: none; }

.form-actions { display: flex; flex-wrap: wrap; gap: .75rem; align-items: center; margin-top: .5rem; }

/* honeypot */
.trap { position: absolute; left: -9999px; opacity: 0; height: 0; overflow: hidden; }

.notice { border-radius: var(--radius-sm); padding: .9rem 1.15rem; margin-bottom: 1.25rem; border-left: 4px solid; }
.notice > :last-child { margin-bottom: 0; }
.notice--info { background: var(--teal-wash); border-color: var(--teal); }
.notice--success { background: var(--teal-wash); border-color: var(--teal); }
.notice--warn { background: var(--mustard-wash); border-color: var(--mustard); }
.notice--error { background: var(--rose-wash); border-color: var(--maroon); }
.notice ul { margin: .5rem 0 0; }

/* ==========================================================================
   Admin dashboard — plainer on purpose. Committee members should feel like
   they are filling in a form, not operating a website.
   ========================================================================== */

.admin { background: var(--paper-2); }

.admin-header { background: var(--maroon-deep); color: #fff; --focus: var(--mustard); }
.admin-header__inner { display: flex; align-items: center; gap: 1rem; flex-wrap: wrap; min-height: 60px; padding-block: .7rem; }
.admin-header__title { display: flex; align-items: center; gap: .6rem; margin-right: auto; color: #fff; text-decoration: none; font-family: var(--display); font-size: 1.1rem; font-weight: 600; }
.admin-header__title .mark { width: 26px; height: 26px; }
.admin-header a { color: var(--on-main, #f3e2e5); }
.admin-header__links { display: flex; gap: 1rem; align-items: center; font-size: .85rem; }

/* The dashboard sections, grouped into menus.
   Seventeen links in one row had stopped being a menu and started being a
   paragraph -- on a phone it wrapped to four lines before the page began.

   There is no JavaScript here and there is not going to be: the site is
   script-src 'none' and a nav is not the thing to break that for. The menus
   open three ways instead, each covering an input the others miss -- :hover for
   a mouse, :focus-within for a keyboard, and <details>'s own open state for a
   tap on a phone, where hover does not exist at all. <summary> is focusable and
   toggles on Enter for free, which is why these are <details> and not divs. */
.admin-nav { background: var(--maroon); position: relative; z-index: 20; }
.admin-nav__inner { display: flex; flex-wrap: wrap; gap: .15rem; padding-block: 0; }

.admin-nav__link {
  display: inline-flex; align-items: center; gap: .4rem;
  color: var(--on-main, #f3dde1); text-decoration: none; font-size: .82rem; font-weight: 600;
  padding: .8rem .9rem; border-bottom: 3px solid transparent; cursor: pointer;
}
.admin-nav__link:hover { color: #fff; background: rgba(255, 255, 255, .07); }
.admin-nav a[aria-current="page"] { color: #fff; border-bottom-color: var(--mustard); }

.admin-nav__group { position: relative; }
/* Both lines are needed to lose the disclosure triangle: the standard property
   for browsers that support it, the -webkit- pseudo-element for Safari. */
.admin-nav__summary { list-style: none; }
.admin-nav__summary::-webkit-details-marker { display: none; }
/* The group holding the page you are on, marked without being held open. */
.admin-nav__summary[data-here] { color: #fff; border-bottom-color: var(--mustard); }
.admin-nav__caret {
  width: .38rem; height: .38rem; margin-top: -.2rem;
  border-right: 2px solid currentColor; border-bottom: 2px solid currentColor;
  transform: rotate(45deg); opacity: .7;
}

.admin-nav__menu {
  display: none;
  position: absolute; left: 0; top: 100%; min-width: 12.5rem;
  background: var(--card); border: 1px solid var(--line);
  border-radius: 0 0 var(--radius) var(--radius);
  box-shadow: var(--shadow-lg); padding: .35rem; z-index: 30;
}
.admin-nav__group:hover > .admin-nav__menu,
.admin-nav__group:focus-within > .admin-nav__menu,
.admin-nav__group[open] > .admin-nav__menu { display: block; }

.admin-nav__menu a {
  display: block; padding: .55rem .7rem; border-bottom: 0;
  border-radius: calc(var(--radius) / 2);
  color: var(--ink); font-size: .85rem; font-weight: 600;
  text-decoration: none; white-space: nowrap;
}
.admin-nav__menu a:hover { background: var(--paper-2); color: var(--maroon); }
.admin-nav__menu a[aria-current="page"] { background: var(--maroon); color: #fff; border-bottom: 0; }

/* On a wide screen the bar is simply on show and the hamburger does not exist.
   The drawer copy is inside a <details>, whose contents the browser hides on its
   own when closed -- that hiding is not a CSS rule we can override, which is why
   there are two copies rather than one clever one. */
.admin-nav__toggle { display: none; }
.admin-nav__inner--wide { display: flex; }
.admin-nav__burger { list-style: none; }
.admin-nav__burger::-webkit-details-marker { display: none; }

/* Phones and narrow tablets.
   Nine groups laid out as nine full-width rows is a worse menu than the flat
   list it replaced -- it filled the screen before the page began. So below this
   width the bar collapses to a hamburger, and opening it gives the same grouped
   list stacked, with each group expanding in place.

   Hover is switched off outright down here: a tap counts briefly as a hover, so
   the submenu would flash open under a finger on its way past. */
@media (max-width: 62rem) {
  .admin-nav__toggle { display: block; }
  .admin-nav__inner--wide { display: none; }

  .admin-nav__burger {
    display: flex; align-items: center; gap: .5rem; cursor: pointer;
    padding: .8rem clamp(1.1rem, 4vw, 2rem);
    color: var(--on-main, #f3dde1); font-size: .78rem; font-weight: 700;
    letter-spacing: .08em; text-transform: uppercase;
  }
  .admin-nav__toggle[open] > .admin-nav__burger { background: rgba(255, 255, 255, .14); color: #fff; }
  .admin-nav__burger-icon { width: 16px; height: 12px; fill: currentColor; }

  .admin-nav__inner { flex-direction: column; align-items: stretch; padding-bottom: .5rem; }

  .admin-nav__menu {
    position: static; padding: 0 0 .4rem 1.4rem;
    background: transparent; border: 0; box-shadow: none;
  }
  .admin-nav__menu a { color: var(--on-main, #f3dde1); white-space: normal; }
  .admin-nav__menu a:hover { background: rgba(255, 255, 255, .07); color: #fff; }
  .admin-nav__group:hover > .admin-nav__menu { display: none; }
  .admin-nav__group[open] > .admin-nav__menu { display: block; }
  /* The caret should point down when shut and up when open, since on a phone
     these expand in place rather than dropping a panel over the page. */
  .admin-nav__group[open] > summary .admin-nav__caret { transform: rotate(-135deg); }
}

.admin main { padding-block: clamp(1.5rem, 4vw, 2.5rem); }
.admin h1 { font-size: clamp(1.6rem, 3.5vw, 2.1rem); }
.admin h2 { font-size: clamp(1.2rem, 2.5vw, 1.5rem); }

.admin-panel {
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  padding: clamp(1.25rem, 3vw, 2rem); margin-bottom: 1.5rem; box-shadow: var(--shadow);
}
.admin-panel > :last-child { margin-bottom: 0; }
.admin-panel > h2:first-child { margin-top: 0; }

.admin-intro { color: var(--ink-soft); max-width: 44rem; }

/* Rehearsals, fenced off from the grids taking real money.
   A row for a test game and a row for a real one are near enough identical --
   the same columns, the same figures, a small badge between them -- so "10 of
   2000, £10" reads as money the PSFA has taken when it is nothing of the kind.
   The checkerboard is doing the work a badge could not: it says "not real" from
   across the room, before any of the words are read. Kept at a whisper of
   contrast (two neighbouring paper tones) so the rows on top stay legible --
   it has to be unmistakable as a signal without becoming noise as a texture.
   Scrolls within itself, so a wide grid table cannot push the page sideways. */
.test-zone {
  margin-top: 1.75rem;
  padding: 1.15rem clamp(.9rem, 2.5vw, 1.35rem) 1.25rem;
  border: 2px dashed var(--rose-soft);
  border-radius: var(--radius);
  background-color: var(--paper);
  background-image: repeating-conic-gradient(var(--paper-2) 0% 25%, transparent 0% 50%);
  background-size: 16px 16px;
  overflow-x: auto;
}
.test-zone__head {
  margin: 0; font-size: .78rem; font-weight: 700; letter-spacing: .12em;
  text-transform: uppercase; color: var(--ink-soft);
}
.test-zone__note { margin: .3rem 0 1rem; font-size: .85rem; color: var(--ink-soft); max-width: 44rem; }
.test-zone .table th { border-bottom-color: var(--rose); }
.test-zone .table td { border-bottom-color: var(--line); }

/* Dashboard figures.
   The dashboard used to be one tile per section, which is the nav written out a
   second time underneath the nav -- you had to open every one to find out
   whether it wanted anything. These are the numbers instead, and each is a link
   to the screen that acts on it. */
/* The whole page is meant to be taken in at once, so it is built to fit a
   desktop screen without scrolling: two columns of panels, tighter padding than
   the rest of the dashboard, and figures sized to be read rather than admired. */
.dash-head { display: flex; flex-wrap: wrap; gap: .2rem 1rem; align-items: baseline; margin-bottom: 1rem; }
.dash-head h1 { margin: 0; }
.dash-head p { margin: 0; color: var(--ink-soft); font-size: .88rem; }

.dash-grid { display: grid; gap: 1rem; grid-template-columns: repeat(auto-fit, minmax(21rem, 1fr)); }
.admin-panel--dash { padding: 1rem 1.15rem 1.1rem; margin-bottom: 0; }

.panel-head { display: flex; flex-wrap: wrap; gap: .3rem 1rem; align-items: baseline; justify-content: space-between; }
.panel-head h2 { margin: 0; font-size: 1.05rem; }
.panel-head__link { font-size: .8rem; font-weight: 600; white-space: nowrap; }

.stat-row { display: flex; flex-wrap: wrap; gap: .5rem 1.75rem; margin-top: .7rem; }
.stat { text-decoration: none; color: inherit; min-width: 5rem; }
.stat b {
  display: block; font-family: var(--display); font-size: 1.6rem;
  line-height: 1.15; color: var(--maroon); font-weight: 700;
}
.stat span { display: block; font-size: .75rem; color: var(--ink-soft); margin-top: .05rem; }
a.stat:hover b { color: var(--teal); }
a.stat:hover span { color: var(--ink); }

.dash-note { margin: .7rem 0 0; font-size: .78rem; color: var(--ink-soft); line-height: 1.45; }

/* Page visits. Both the day-by-day bars and the share column are drawn with a
   percentage and nothing else: the dashboard runs script-src 'none', so a
   charting library is not an option — and for "is this going up or down" a row
   of bars answers the question as well as one would. */
.visit-chart { display: flex; align-items: flex-end; gap: 2px; height: 7.5rem; margin-top: .5rem; }
.visit-bar { flex: 1 1 0; min-width: 2px; background: var(--teal); border-radius: 2px 2px 0 0; opacity: .85; }
.visit-bar:hover { opacity: 1; background: var(--teal-deep); }
.visit-share { width: 7rem; vertical-align: middle; }
.visit-share__bar { display: block; height: .45rem; border-radius: 999px; background: var(--rose); min-width: 2px; }

/* What is actually waiting on a person, at the top where it cannot be missed.
   Only non-zero rows are rendered at all: a standing list of "0 things to do"
   teaches people to stop reading it, and then the day it says 3 they miss that
   too. */
.admin-panel--jobs { border-left: 4px solid var(--mustard); padding: 1rem 1.15rem 1.1rem; margin-bottom: 1rem; }
.admin-panel--jobs h2 { margin: 0; font-size: 1.05rem; }
/* Two columns once there is room, so a busy week does not push the figures
   below the fold -- which is the one thing this page must not do. */
.jobs {
  list-style: none; margin: .55rem 0 0; padding: 0;
  display: grid; gap: .05rem; grid-template-columns: repeat(auto-fit, minmax(21rem, 1fr));
}
.jobs__item { padding: 0; }
.jobs__item a {
  display: block; padding: .4rem .6rem; border-radius: calc(var(--radius) / 2);
  text-decoration: none; color: var(--ink); font-weight: 600; font-size: .87rem;
}
.jobs__item a::before { content: "→"; color: var(--ink-soft); margin-right: .5rem; font-weight: 400; }
.jobs__item a:hover { background: var(--paper-2); color: var(--maroon); }
.jobs__item--urgent a { color: var(--maroon); }
.jobs__item--urgent a::before { content: "●"; color: var(--rose); font-size: .7em; vertical-align: .15em; }
.jobs__clear { margin: .5rem 0 0; color: var(--ink-soft); font-size: .88rem; }

.tiles { display: grid; gap: 1rem; grid-template-columns: repeat(auto-fill, minmax(13.5rem, 1fr)); margin-bottom: 2rem; }
.tile {
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 1.15rem 1.25rem; text-decoration: none; color: var(--ink);
  box-shadow: var(--shadow); border-top: 4px solid var(--rose);
}
.tile:nth-child(4n+2) { border-top-color: var(--teal); }
.tile:nth-child(4n+3) { border-top-color: var(--mustard); }
.tile:nth-child(4n+4) { border-top-color: var(--maroon); }
.tile:hover { box-shadow: var(--shadow-lg); color: var(--ink); }
.tile b { display: block; font-family: var(--display); font-size: 1.15rem; color: var(--maroon); margin-bottom: .2rem; }
.tile span { font-size: .88rem; color: var(--ink-soft); }
.tile em { display: block; font-style: normal; font-size: .8rem; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--teal-deep); margin-top: .5rem; }

.table-scroll { overflow-x: auto; margin-inline: calc(clamp(1.25rem, 3vw, 2rem) * -1); padding-inline: clamp(1.25rem, 3vw, 2rem); }
.table { width: 100%; border-collapse: collapse; font-size: .92rem; min-width: 34rem; }
.table th {
  text-align: left; font-size: .72rem; font-weight: 700; letter-spacing: .12em;
  text-transform: uppercase; color: var(--ink-soft);
  padding: 0 .8rem .6rem 0; border-bottom: 2px solid var(--rose-soft); white-space: nowrap;
}
.table td { padding: .7rem .8rem .7rem 0; border-bottom: 1px solid var(--line); vertical-align: top; }
.table tr:last-child td { border-bottom: 0; }
.table .num { text-align: right; font-variant-numeric: tabular-nums; }
.table .actions { white-space: nowrap; text-align: right; }
.table img { width: 44px; height: 55px; object-fit: cover; border-radius: 4px; }

/* A facts table -- one row per fact, its label on the left -- rather than a
   list with column headings. It needs the opposite of what .table gives a list:
   no 34rem floor to overflow the screen with (there is nothing to scroll
   sideways to, only two columns), and a label free to wrap onto a second line
   instead of holding the column open at the width of its longest phrase.
   Without both, "Where it is up to" runs off the right of a phone and squeezes
   its explanations into a ribbon. */
.table--facts { min-width: 0; }
.table--facts th { white-space: normal; width: 12rem; }

/* Narrower than that and the two columns stop being columns. A label and a
   sentence explaining a figure cannot share the width of a phone, so the label
   sits above its value and each fact gets the full width. */
@media (max-width: 34rem) {
  .table--facts,
  .table--facts tbody,
  .table--facts tr,
  .table--facts th,
  .table--facts td { display: block; width: auto; }
  .table--facts th { border-bottom: 0; padding: 0 0 .25rem; }
  .table--facts td { padding: 0 0 .9rem; border-bottom: 1px solid var(--line); }
  .table--facts tr:last-child td { border-bottom: 0; padding-bottom: 0; }
}

.badge {
  display: inline-block; font-size: .7rem; font-weight: 700; letter-spacing: .09em;
  text-transform: uppercase; padding: .22rem .6rem; border-radius: 999px;
}
.badge--new { background: var(--mustard-wash); color: var(--accent-ink, #85601a); }
.badge--ready { background: var(--teal-wash); color: var(--teal-deep); }
.badge--collected { background: #e6efe6; color: #35603a; }
.badge--cancelled { background: var(--paper-2); color: var(--ink-soft); }
.badge--draft { background: var(--rose-wash); color: var(--maroon); }

.inline-form { display: inline; }

.admin-actions { display: flex; flex-wrap: wrap; gap: .6rem; align-items: center; justify-content: space-between; margin-bottom: 1.25rem; }

.settings-group { margin-bottom: 2rem; }
.settings-group:last-of-type { margin-bottom: 1rem; }
.settings-group > h2 { margin-bottom: .3rem; }
.settings-group__blurb { color: var(--ink-soft); font-size: .92rem; margin-bottom: 1.25rem; }

.sticky-save {
  position: sticky; bottom: 0; background: var(--card);
  border-top: 1px solid var(--line); padding: 1rem 0; margin-top: 1rem;
  display: flex; gap: .75rem; align-items: center; flex-wrap: wrap;
}

.login-shell { display: grid; place-items: center; min-height: 78vh; padding: 2rem 1rem; }
.login-card { width: 100%; max-width: 24rem; background: var(--card); border: 1px solid var(--line); border-radius: var(--radius); padding: 2rem; box-shadow: var(--shadow-lg); }
.login-card .mark { width: 44px; height: 44px; margin-bottom: 1rem; }

/* The master password, folded away under the ordinary way in. It is the account
   most weeks need nothing from, and a login page that leads with it teaches
   everybody to keep using it. */
.login-alt { border-top: 1px solid var(--line); padding-top: 1rem; }
.login-alt > summary {
  cursor: pointer; font-size: .9rem; color: var(--ink-soft);
  padding: .25rem 0;
}
.login-alt > summary:hover { color: var(--ink); }
.login-alt > summary:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; border-radius: 4px; }
.login-alt[open] > summary { margin-bottom: .75rem; color: var(--ink); }

.photo-preview { display: flex; gap: 1rem; align-items: flex-start; }
.photo-preview img { width: 92px; height: 115px; object-fit: cover; border-radius: var(--radius-sm); border: 1px solid var(--line); }

/* Site photos: each preview is drawn at the shape its slot crops it to, so the
   dashboard shows what the page will show rather than the whole photograph. */
.slot-preview {
  display: block; width: 100%; object-fit: cover;
  border-radius: var(--radius-sm); border: 1px solid var(--line); background: var(--paper-2);
}
.slot-preview--empty { display: grid; place-items: center; aspect-ratio: 4 / 3; }
.slot-preview--empty .mark { width: 30px; height: 30px; opacity: .4; }

.empty-state { text-align: center; padding: clamp(2rem, 6vw, 3.5rem) 1rem; color: var(--ink-soft); }
.empty-state .mark { width: 52px; height: 52px; margin: 0 auto 1rem; opacity: .5; }

/* ---------- footer ---------- */

.site-footer { background: var(--maroon-deep); color: var(--on-main, #e8d6d9); --focus: var(--mustard); margin-top: clamp(3rem, 7vw, 5rem); }
.site-footer__inner { display: grid; gap: clamp(1.75rem, 4vw, 2.5rem); padding-block: clamp(2.5rem, 5vw, 3.5rem); }
@media (min-width: 46rem) { .site-footer__inner { grid-template-columns: 1.4fr 1fr 1fr; } }
@media (min-width: 64rem) { .site-footer__inner { grid-template-columns: 1.5fr 1fr 1.1fr 1fr; } }

.site-footer h2 {
  font-family: var(--body); font-size: .74rem; font-weight: 700; letter-spacing: .15em;
  text-transform: uppercase; color: var(--rose); margin-bottom: .9rem;
}
.site-footer a { color: var(--on-main, #f5e7e9); text-decoration: none; }
.site-footer a:hover { color: var(--mustard-soft); text-decoration: underline; }
.site-footer__name { font-family: var(--display); font-size: 1.25rem; color: #fff; margin-bottom: .5rem; }
.site-footer__note { font-size: .92rem; color: var(--on-main-soft, #d9c3c7); }
.site-footer__note p { margin-bottom: .5rem; }
.site-footer__charity { font-size: .85rem; color: var(--rose); margin: 0; }
.site-footer__nav, .site-footer__contact, .site-footer__school { display: flex; flex-direction: column; gap: .5rem; align-items: flex-start; }
.site-footer__nav a, .site-footer__contact a { font-size: .92rem; }
.site-footer__address, .site-footer__trust { font-size: .85rem; color: var(--on-main-soft, #c9b1b6); margin: .25rem 0 0; }
.site-footer__school img { background: #fff; border-radius: var(--radius-sm); padding: .5rem; width: 92px; height: auto; }
.site-footer__base {
  display: flex; flex-wrap: wrap; gap: .5rem 1.5rem; justify-content: space-between;
  padding-block: 1.1rem; border-top: 1px solid rgba(255, 255, 255, .13);
  font-size: .82rem; color: var(--on-main-soft, #bfa5aa);
}
.site-footer__base p { margin: 0; }

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

@media print {
  .site-header, .site-footer, .admin-nav, .btn, .filters { display: none !important; }
  body { background: #fff; color: #000; }
  .arch, .arch__2, .arch__3 { padding: 0; background: none; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: .001ms !important; transition-duration: .001ms !important; }
}

/* ---------- photographs ---------- */

picture { display: block; }

.photo-band { position: relative; }
.photo-band picture img {
  width: 100%; aspect-ratio: 16 / 7; object-fit: cover;
  border-radius: var(--radius);
}
@media (max-width: 40rem) { .photo-band picture img { aspect-ratio: 4 / 3; } }
.photo-band figcaption {
  font-size: .85rem; color: var(--ink-soft); margin-top: .7rem; text-align: center;
}
.photo-band figure { margin: 0; }

/* A photo framed by the arch — the mark doing real work. */
.photo-arch {
  --band: clamp(6px, 1.1vw, 12px);
  --curve: clamp(60px, 14vw, 130px);
  padding: var(--band); background: var(--rose);
  border-radius: 50% 50% 0 0 / var(--curve) var(--curve) 0 0;
}
.photo-arch__2 {
  padding: var(--band); background: var(--teal);
  border-radius: 50% 50% 0 0 / calc(var(--curve) - var(--band)) calc(var(--curve) - var(--band)) 0 0;
}
.photo-arch__3 {
  padding: var(--band); background: var(--mustard);
  border-radius: 50% 50% 0 0 / calc(var(--curve) - var(--band) * 2) calc(var(--curve) - var(--band) * 2) 0 0;
}
.photo-arch picture, .photo-arch img {
  display: block; width: 100%;
  border-radius: 50% 50% 0 0 / calc(var(--curve) - var(--band) * 3) calc(var(--curve) - var(--band) * 3) 0 0;
}
.photo-arch img { aspect-ratio: 4 / 3; object-fit: cover; }

.media { display: grid; gap: clamp(1.5rem, 4vw, 3rem); align-items: center; }
@media (min-width: 48rem) {
  .media { grid-template-columns: 1fr 1fr; }
  .media--flip .media__text { order: 2; }
}
.media__text > :last-child { margin-bottom: 0; }
.media picture img { width: 100%; border-radius: var(--radius); box-shadow: var(--shadow-lg); }
.media--portrait picture img { aspect-ratio: 3 / 4; object-fit: cover; }
.media--landscape picture img { aspect-ratio: 4 / 3; object-fit: cover; }

.photo-strip { display: grid; gap: 1rem; grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr)); }
.photo-strip figure { margin: 0; }
.photo-strip img { aspect-ratio: 1 / 1; object-fit: cover; border-radius: var(--radius); }
.photo-strip figcaption { font-size: .82rem; color: var(--ink-soft); margin-top: .5rem; }
.band--teal .photo-strip figcaption { color: var(--on-second, #cfe2e0); }

/* ---------- narrow phones ---------- */
/* The header is the widest fixed row on the site: brand, menu and Donate must
   all fit at 320px or the whole document overflows sideways. */
@media (max-width: 30rem) {
  .site-header__inner { gap: .5rem; min-height: 62px; }
  .mark--brand { width: 31px; height: 31px; }
  .brand { gap: .5rem; }
  .brand__place { font-size: .5rem; letter-spacing: .22em; margin-bottom: .18rem; }
  .brand__name { font-size: .68rem; letter-spacing: .03em; margin-bottom: .14rem; }
  .brand__sub { font-size: 1.3rem; }
  .nav-toggle > summary { padding: .48rem .62rem; font-size: .66rem; letter-spacing: .08em; }
  .nav-toggle__text { display: none; }
  .nav-toggle__icon { display: block; }
  .btn--donate { padding: .55rem .85rem; font-size: .8rem; }
  .basket-pill { padding: .42rem .6rem; }
}


/* Flex and grid children default to min-width:auto, which lets long words and
   wide children push a row past the viewport. */
.site-header__inner > *, .site-footer__inner > *, .media > *, .shelter > * { min-width: 0; }
.cards, .stock-grid, .photo-strip, .tiles { min-width: 0; }
h1, h2, h3, p, li, td, th, .lede { overflow-wrap: break-word; }

/* ---------- donation form ---------- */

.donate-form {
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  padding: clamp(1.35rem, 3vw, 2.25rem); box-shadow: var(--shadow);
}

.amount-set { border: 0; padding: 0; margin: 0 0 1.5rem; }
.amount-set legend {
  padding: 0; margin-bottom: .85rem;
  font-size: .74rem; font-weight: 700; letter-spacing: .14em;
  text-transform: uppercase; color: var(--ink-soft);
}

.amount-grid { display: grid; gap: .7rem; grid-template-columns: repeat(auto-fit, minmax(5.5rem, 1fr)); }

.amount {
  font-family: var(--display); font-size: clamp(1.25rem, 3vw, 1.6rem); font-weight: 600;
  color: var(--maroon); background: var(--card);
  border: 2px solid var(--line); border-radius: var(--radius-sm);
  padding: .85rem .5rem; cursor: pointer; text-align: center;
  transition: border-color .14s ease, background-color .14s ease, transform .1s ease;
}
.amount:hover { border-color: var(--rose); background: var(--rose-wash); }
.amount:active { transform: translateY(1px); }
.amount.is-selected {
  background: var(--maroon); border-color: var(--maroon); color: #fff;
}

/* £ sits inside the field so the number lines up with the presets above. */
.amount-input { position: relative; }
.amount-input__symbol {
  position: absolute; left: .85rem; top: 50%; transform: translateY(-50%);
  font-family: var(--display); font-size: 1.15rem; font-weight: 600;
  color: var(--ink-soft); pointer-events: none;
}
.amount-input input { padding-left: 2.1rem; font-size: 1.15rem; font-weight: 600; }

#donate-error { margin-bottom: 1.25rem; }

/* ---------- uniform fulfilment portal ---------- */
/* The screen a volunteer works from while making up bags, so the tick targets
   are deliberately large and the state of a bag readable at arm's length. */

.badge--picked  { background: var(--teal-wash); color: var(--teal-deep); }
.badge--paid    { background: #e6efe6; color: #35603a; }
.badge--unpaid  { background: var(--rose-wash); color: var(--maroon); }
.badge--failed  { background: #f8ddd8; color: #8d3018; }

.res-list { display: grid; gap: 1.25rem; }

.res-card {
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: var(--shadow); padding: 1.25rem; scroll-margin-top: 5rem;
}
/* Anything sitting past the stale threshold is holding stock nobody else can
   reserve, so it is meant to catch the eye. */
.res-card--stale { border-color: var(--rose); box-shadow: 0 0 0 1px var(--rose), var(--shadow); }

.res-card__head { display: flex; flex-wrap: wrap; gap: .75rem; justify-content: space-between; align-items: flex-start; }
.res-card__ref { font-family: var(--display); font-weight: 700; letter-spacing: .06em; color: var(--maroon); margin: 0; }
.res-card__name { font-size: 1.2rem; margin: .1rem 0 0; }
.res-card__child { color: var(--ink-soft); font-size: .9rem; margin: .15rem 0 0; }
.res-card__badges { display: flex; flex-wrap: wrap; gap: .4rem; align-items: center; justify-content: flex-end; }
.res-card__age { font-size: .8rem; color: var(--ink-soft); width: 100%; text-align: right; }
.res-card__age--stale { color: #b3271c; font-weight: 700; }

.res-card__contact { display: flex; flex-wrap: wrap; gap: .9rem; font-size: .88rem; color: var(--ink-soft); margin-top: .5rem; }
.res-card__notes { background: var(--paper); border-radius: var(--radius-sm); padding: .6rem .8rem; font-size: .9rem; margin: .75rem 0 0; }
.res-card__progress { margin-top: .9rem; font-size: .9rem; color: var(--ink-soft); }

.pick-list { list-style: none; margin: .5rem 0 0; padding: 0; border-top: 1px solid var(--line); }

.pick-row {
  display: grid; grid-template-columns: auto 1fr auto; gap: .5rem .8rem; align-items: center;
  padding: .7rem 0; border-bottom: 1px solid var(--line);
}
.pick-row--done .pick-row__what strong { color: var(--ink-soft); text-decoration: line-through; }
.pick-row__what { display: flex; flex-direction: column; gap: .1rem; min-width: 0; }
.pick-row__note { font-size: .82rem; color: var(--teal-deep); font-style: italic; }

/* How many should be in the box. Quiet as the price beside it until the count
   is no bigger than what was asked for, which is the line worth spotting
   before you set off rather than after. */
.pick-row__stock { font-size: .82rem; color: var(--ink-soft); font-variant-numeric: tabular-nums; }
.pick-row__stock--tight { color: var(--accent-ink, #85601a); font-weight: 700; }
.pick-row__qty { font-variant-numeric: tabular-nums; white-space: nowrap; }
.pick-row__more { grid-column: 1 / -1; }
.pick-row__more summary { cursor: pointer; font-size: .82rem; color: var(--ink-soft); }
.pick-row__form { display: grid; gap: .6rem; padding: .8rem 0 .3rem; max-width: 30rem; }
.pick-row__form .field { margin: 0; }

/* Big enough to hit accurately while standing over a box of jumpers, and a
   square rather than a circle so an empty one still reads as a checkbox.
   Sized on every axis -- a button with no text in it will otherwise collapse
   to a line wherever a parent tries to size it by its content. */
.tick {
  flex: none; box-sizing: border-box; padding: 0; margin: 0;
  width: 2.25rem; height: 2.25rem;
  min-width: 2.25rem; min-height: 2.25rem;
  aspect-ratio: 1 / 1;
  border: 2px solid var(--rose); border-radius: 7px;
  background: var(--card); cursor: pointer;
  font-size: 1.25rem; line-height: 1; color: var(--teal-deep);
  display: inline-grid; place-items: center;
}
.tick:hover { border-color: var(--teal); background: var(--teal-wash); }
.tick--on { background: var(--teal-wash); border-color: var(--teal); }
.pick-row__tick { display: flex; margin: 0; }

.checkline { display: flex; gap: .5rem; align-items: center; font-size: .9rem; }
.checkline input { width: auto; }

.res-card__money {
  display: flex; flex-wrap: wrap; gap: .8rem; justify-content: space-between; align-items: flex-end;
  margin-top: 1rem; padding-top: .9rem; border-top: 1px solid var(--line);
}
.res-card__money strong { display: block; font-size: 1.15rem; }
.pay-controls { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; }
.pay-form { display: flex; flex-wrap: wrap; gap: .4rem; align-items: center; }
.pay-form select { width: auto; }
.pay-done { text-align: right; }
.pay-done .field__help { margin: 0 0 .3rem; }
.amount-input--small { width: 7rem; }
.amount-input--small input { font-size: 1rem; padding-left: 1.7rem; }
.amount-input--small .amount-input__symbol { font-size: .95rem; }

.res-card__actions { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: 1rem; }

/* The reserve/pay pair on the public basket, so neither reads as the fallback. */
.form-actions--pay { flex-wrap: wrap; gap: .6rem; }

@media (max-width: 40rem) {
  .res-card__badges { justify-content: flex-start; }
  .res-card__age { text-align: left; }
  .pay-done { text-align: left; }
}

/* ---------- reservations: the list, and the panel it opens ---------- */

.res-table td { vertical-align: middle; }
.res-row__link { font-weight: 700; font-family: var(--display); letter-spacing: .04em; }
.res-row--stale td { background: var(--rose-wash); }
.res-row__age--stale { color: #b3271c; font-weight: 700; }

.res-dialog {
  width: min(46rem, calc(100vw - 2rem));
  max-height: calc(100vh - 4rem);
  padding: 1.25rem; border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--paper); color: var(--ink); box-shadow: var(--shadow-lg);
  overflow: auto;
}
.res-dialog::backdrop { background: rgba(42, 31, 34, .55); }
.res-dialog__close { display: flex; justify-content: flex-end; margin-bottom: .5rem; }
/* Inside a dialog the card is the whole panel, so it drops its own frame. */
.res-dialog .res-card { border: 0; box-shadow: none; padding: 0; background: transparent; }

.admin-back { margin-bottom: 1rem; font-size: .9rem; }

/* A line nobody ticked on a bag that has already been picked: the family is not
   getting it, so it should not look the same as one that is simply still to do. */
.pick-row--notsupplied { opacity: .75; }
.pick-row--notsupplied .pick-row__what strong { text-decoration: line-through; color: var(--ink-soft); }
.pick-row--unavailable .pick-row__what strong { text-decoration: line-through; }
.res-card__left { color: #b3271c; font-weight: 700; }

/* ---------- photo gallery ---------- */

.album-grid {
  display: grid; gap: 1.5rem;
  grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
}

.album-card {
  display: flex; flex-direction: column; text-decoration: none; color: inherit;
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  overflow: hidden; box-shadow: var(--shadow); transition: transform .15s ease, box-shadow .15s ease;
}
.album-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-lg); color: inherit; }
.album-card__photo { aspect-ratio: 4 / 3; background: var(--paper-2); }
.album-card__photo picture, .album-card__photo img { width: 100%; height: 100%; display: block; object-fit: cover; }
.album-card__placeholder { display: grid; place-items: center; height: 100%; opacity: .35; }
.album-card__body { padding: 1rem 1.15rem 1.25rem; }
.album-card__title { font-size: 1.15rem; margin: 0; }
.album-card__meta { font-size: .88rem; color: var(--ink-soft); margin: .3rem 0 0; }

/* The album itself. Thumbnails are cropped to a common shape: left at their own
   proportions, a short photo sitting in a tall grid row showed a band of bare
   card beneath it. The crop is only ever a thumbnail -- clicking one opens the
   whole photo, uncropped, in the lightbox. */
.shot-grid {
  display: grid; gap: 1rem; margin-top: 1.5rem;
  grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
}
.shot { margin: 0; background: var(--card); border: 1px solid var(--line); border-radius: var(--radius-sm); overflow: hidden; }
.shot__link { display: block; aspect-ratio: 4 / 3; background: var(--paper-2); overflow: hidden; }
.shot__link picture { display: block; width: 100%; height: 100%; }
.shot__link img { display: block; width: 100%; height: 100%; object-fit: cover; transition: transform .35s ease; }
.shot__link:hover img, .shot__link:focus-visible img { transform: scale(1.04); }
.shot figcaption { padding: .6rem .8rem; font-size: .86rem; color: var(--ink-soft); }

/* The lightbox.
   There is no JavaScript anywhere on this site (script-src 'none'), so the
   enlarged view is driven by :target -- each photo is an anchor, the arrows
   link to the photo either side, and close links back to the thumbnail. The
   arrows wrap around, so neither one is ever a dead end. */
.lightbox { display: none; }
.lightbox:target {
  display: grid; place-items: center;
  position: fixed; inset: 0; z-index: 200;
  padding: clamp(.5rem, 3vw, 2rem);
}
.lightbox__scrim { position: absolute; inset: 0; background: rgba(30, 8, 14, .9); }
.lightbox__figure { position: relative; z-index: 1; margin: 0; max-width: min(60rem, 92vw); }
.lightbox__figure picture { display: block; }
.lightbox__figure img {
  display: block; width: auto; height: auto; margin: 0 auto;
  max-width: 100%; max-height: 78vh;
  border-radius: var(--radius-sm); box-shadow: var(--shadow-lg);
}
.lightbox__caption {
  display: flex; flex-wrap: wrap; gap: .25rem .75rem;
  justify-content: center; align-items: baseline;
  margin-top: .8rem; color: #f4e9e7; font-size: .9rem; text-align: center;
}
.lightbox__count { color: #c4a9ad; font-variant-numeric: tabular-nums; }

.lightbox__nav, .lightbox__close {
  position: absolute; z-index: 2; display: grid; place-items: center;
  color: #fff; text-decoration: none; line-height: 1;
  background: rgba(255, 255, 255, .12); border: 1px solid rgba(255, 255, 255, .3);
  border-radius: 50%; --focus: var(--mustard);
}
.lightbox__nav:hover, .lightbox__close:hover { background: rgba(255, 255, 255, .28); color: #fff; }
.lightbox__nav { top: 50%; transform: translateY(-50%); width: 3rem; height: 3rem; font-size: 2.1rem; padding-bottom: .22rem; }
.lightbox__nav--prev { left: clamp(.4rem, 2vw, 2rem); }
.lightbox__nav--next { right: clamp(.4rem, 2vw, 2rem); }
.lightbox__close { top: clamp(.4rem, 2vw, 1.5rem); right: clamp(.4rem, 2vw, 1.5rem); width: 2.6rem; height: 2.6rem; font-size: 1.7rem; padding-bottom: .18rem; }

@media (max-width: 30rem) {
  .lightbox__figure img { max-height: 68vh; }
  .lightbox__nav { width: 2.6rem; height: 2.6rem; font-size: 1.8rem; }
}

/* ---------- gallery admin ---------- */

.gphoto-grid {
  list-style: none; margin: 0; padding: 0;
  display: grid; gap: 1rem;
  grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr));
}
.gphoto {
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: .6rem; background: var(--card);
  display: flex; flex-direction: column; gap: .5rem;
}
.gphoto--cover { border-color: var(--teal); box-shadow: 0 0 0 1px var(--teal); }
.gphoto__img { position: relative; aspect-ratio: 4 / 3; background: var(--paper-2); border-radius: 6px; overflow: hidden; }
.gphoto__img picture, .gphoto__img img { width: 100%; height: 100%; display: block; object-fit: cover; }
.gphoto__flag {
  position: absolute; left: .4rem; top: .4rem;
  background: var(--teal); color: #fff; font-size: .62rem; font-weight: 700;
  letter-spacing: .08em; text-transform: uppercase; padding: .18rem .45rem; border-radius: 999px;
}
.gphoto__form { display: flex; flex-wrap: wrap; gap: .4rem; }
.gphoto__form input[type="text"] { flex: 1 1 100%; font-size: .9rem; padding: .45rem .6rem; }
.gphoto__sort { width: 4.5rem; font-size: .9rem; padding: .45rem .5rem; }
.gphoto__actions { display: flex; flex-wrap: wrap; gap: .4rem; margin-top: auto; }

/* ==========================================================================
   Lucky Squares
   ========================================================================== */

.btn--danger { background: #8d3018; color: #fff; }
.btn--danger:hover { background: #6f2412; color: #fff; }

/* Two narrow fields side by side, but only in forms that ask for it -- the
   :has() guard leaves every existing single-column form exactly as it was, and
   a browser without :has() simply keeps them all full width, which is fine. */
@media (min-width: 40rem) {
  .form-grid:has(> .field--half) { grid-template-columns: 1fr 1fr; }
  .form-grid:has(> .field--half) > .field { grid-column: 1 / -1; }
  .form-grid:has(> .field--half) > .field--half { grid-column: span 1; }
  .form-grid:has(> .field--half) > .form-actions { grid-column: 1 / -1; }
}

.card-grid {
  display: grid;
  gap: 1.25rem;
  grid-template-columns: repeat(auto-fill, minmax(17rem, 1fr));
  margin-block: 1.25rem 2rem;
}

.progress {
  height: 10px;
  border-radius: 999px;
  background: var(--paper-2);
  overflow: hidden;
  border: 1px solid var(--line);
}
.progress__bar {
  height: 100%;
  background: var(--teal);
  transition: width .4s ease;
}

/* The grid itself.

   Two rules, and everything else falls out of them.

   ONE: the grid never scrolls sideways. Not on any screen, not at any size.
   A sideways scrollbar is a thing plenty of people simply do not notice, and a
   square nobody notices is a square nobody buys. So the number of columns is
   decided by what actually fits, and a grid too wide for the screen grows
   downwards instead. `auto-fill` does this on its own; there is deliberately no
   media query that pins the column count.

   TWO: a square is never smaller than the text it has to carry. --sq-min is
   sized to hold a full name on two lines, because names are wide rather than
   tall and wrapping one buys back far more room than shrinking the type would.

   The shape chosen in the dashboard is therefore a *ceiling*, not a promise:
   --cols caps how wide the grid may get on a large screen, via the max-width
   below, and narrower screens simply fit fewer. The numbers still read left to
   right and top to bottom, so square 1 is always first and the last square is
   always last, whatever the row length happens to be on a given phone. */
.club-full {
  width: 100%;
  max-width: 112rem;
  margin-inline: auto;
  padding-inline: clamp(1.1rem, 4vw, 2rem);
}

/* The whole Lucky Squares game page runs at one width. Stepping from a narrow
   reading column into a full-bleed grid and back looked like a mistake rather
   than a decision, so nothing steps. */
.club-grid-wrap { margin-block: 1.5rem; }

/* Width used rather than wasted: on a wide screen the explanation and the
   figures sit side by side, which keeps a line of prose a readable length
   without making the container narrower than everything around it. */
.club-head { display: grid; gap: 1.25rem; margin-block: 1.5rem; align-items: start; }
@media (min-width: 62rem) {
  /* Equal halves. They are two panels of the same standing sitting side by
     side, so anything other than equal widths reads as one being an
     afterthought of the other. */
  .club-head { grid-template-columns: 1fr 1fr; gap: 1.75rem; }
}

.club-grid {
  /* Room for "Charlotte" over "Braithwaite" at a readable size. */
  --sq-min: 6rem;
  --sq-max: 9rem;
  --sq-gap: 5px;

  list-style: none;
  margin: 0 auto;
  padding: 0;
  display: grid;
  gap: var(--sq-gap);
  grid-template-columns: repeat(auto-fill, minmax(var(--sq-min), 1fr));
  /* The column count as a hard ceiling. This is measured in --sq-min, the same
     size auto-fill packs at, so it can never license a column the shape did not
     ask for. Measuring it in --sq-max instead was a bug: it reserved room for
     five squares at their largest and then packed seven at their smallest.
     Above the width where the chosen shape genuinely fits, the per-game rule
     that routes/club.js writes into the page takes over and lets the squares
     grow -- see gridShapeStyle() there. */
  max-width: calc(var(--cols, 10) * (var(--sq-min) + var(--sq-gap)));
}

.sq { margin: 0; container-type: inline-size; }

/* Finding one square among hundreds. Only rendered on grids big enough for it
   to matter, because on a 5x5 it would be clutter. */
.club-find {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .6rem;
  margin-bottom: 1rem;
}
.club-find label { font-weight: 700; font-size: .92rem; color: var(--maroon); }
.club-find input { width: 7rem; }

.sq--found .sq__btn {
  outline: 3px solid var(--teal);
  outline-offset: 2px;
  animation: sq-found 1.6s ease-out;
}
@keyframes sq-found {
  0%, 60% { background: var(--teal-wash); }
}

/* The bar that follows a phone up the page.

   On a big grid the details form can be thousands of pixels below the square
   somebody just tapped. Asking them to find it by scrolling is asking them not
   to bother, so once anything is chosen the total and a way onwards come with
   them. Shown by club.js, so it never appears for somebody who cannot use it. */
.club-bar {
  position: sticky;
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 20;
  background: var(--card);
  border-top: 1px solid var(--line);
  box-shadow: 0 -6px 24px -12px rgba(76, 20, 36, .35);
  padding: .7rem clamp(1.1rem, 4vw, 2rem);
  padding-bottom: max(.7rem, env(safe-area-inset-bottom));
}
.club-bar__inner {
  max-width: var(--wrap);
  margin-inline: auto;
  display: flex;
  align-items: center;
  gap: 1rem;
}
.club-bar__count { margin: 0; font-weight: 700; flex: 1; line-height: 1.3; }
.club-bar__count span { display: block; font-weight: 400; font-size: .85rem; color: var(--ink-soft); }

/* Room for the bar so it never sits on top of the last thing on the page. */
body.has-club-bar { padding-bottom: 5.5rem; }

@media (min-width: 48rem) {
  /* On a desktop the form is usually already in view and a fixed bar is more
     intrusion than help, so it only appears once the form is genuinely off
     screen -- club.js adds this class. */
  .club-bar { display: none; }
  .club-bar.club-bar--needed { display: block; }
  body.has-club-bar { padding-bottom: 0; }
}

.sq__btn {
  width: 100%;
  aspect-ratio: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
  padding: 2px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--card);
  color: var(--ink);
  font-family: var(--body);
  cursor: pointer;
  overflow: hidden;
  transition: background .12s ease, border-color .12s ease, transform .12s ease;
}
.sq__btn:hover:not(:disabled) { border-color: var(--maroon); background: var(--rose-wash); }
.sq__btn:focus-visible { outline: 3px solid var(--focus); outline-offset: 2px; }

/* Each square sizes its own text. The type grows a little with the square, so a
   roomy 5x5 does not look like a 32x32 with the same tiny lettering, but both
   ends are clamped: never smaller than legible, never so large it pushes the
   name out. */
.sq__n {
  font-size: clamp(.8rem, 18cqw, 1.15rem);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  line-height: 1;
}
/* A name wraps onto a second line rather than being cut short on one. Names are
   wide rather than tall, so two short lines fit a full name in far less width
   than one long line needs -- which is what lets the square stay at 6rem
   instead of the 8rem or so a single line would demand, and so lets a third
   more squares fit across every screen.

   A name too long even for two lines is clipped, and the whole of it stays in
   the button's title and aria-label. The square is a label; the record is the
   purchases table. */
.sq__who {
  font-size: clamp(.62rem, 12cqw, .8rem);
  line-height: 1.15;
  color: var(--ink-soft);
  max-width: 100%;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  overflow-wrap: anywhere;
  hyphens: auto;
}

/* The sticker, sitting between the number and the name.

   Given the emoji stack rather than the body face for the reason set out beside
   --emoji above. Sized in cqw like everything else in a square, so it grows
   with a roomy 5x5 and stays modest on a 32x32, and clamped at both ends: never
   so small it is a smudge, never so large it pushes the holder's name out.

   Three lines in a 6rem square is the tightest this gets -- number, sticker and
   two lines of name come to around 64px of the 90 available, and a winning
   square's "★ 1st" fits in what is left. */
.sq__sticker {
  font-family: var(--emoji);
  font-size: clamp(.75rem, 20cqw, 1.35rem);
  line-height: 1.05;
}

/* One row's sticker control in the admin purchase table: the sticker as it
   stands, the list to change it to, and the button. Wraps rather than forcing
   the column wide, because this table already carries nine others. */
.sticker-cell {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .35rem;
}
.sticker-cell select { width: auto; max-width: 11rem; }

/* Beside a name in the "recently taken" feed and in the winners list. */
.sticker-inline {
  font-family: var(--emoji);
  font-size: 1.05em;
  line-height: 1;
}

/* A sold square is the good news on this page, so it is given the weight of it.
   It was previously a pale wash with a dashed border, which read as provisional
   -- and dashed is the right signal for a square somebody is *part way through*
   buying, not one that is bought and paid for.

   Teal because nothing else here uses it: available is white, reserved is a
   dashed mustard wash, a square in your own basket is solid maroon, and the
   winner is mustard. Five states, five treatments, none of them ambiguous.
   A filling grid should look like it is filling.

   Status is still never carried by colour alone: the holder's name is printed
   on the square and the button is disabled, so a screen reader says so too. */
.sq--sold .sq__btn {
  background: var(--teal);
  border-color: var(--teal-deep);
  color: #fff;
  cursor: default;
}
.sq--sold .sq__who { color: var(--on-second, #d8e8e7); }
/* Somebody is part way through paying for this one. Distinct from sold: no
   name, a lighter treatment, and wording that says what is actually true --
   because it may well come back if their payment does not finish. */
.sq--reserved .sq__btn {
  background: var(--mustard-wash);
  border-style: dashed;
  border-color: var(--mustard);
  color: var(--accent-ink, #85601a);
  cursor: default;
}
.sq--reserved .sq__who { color: var(--accent-ink, #85601a); font-style: italic; }

.sq--picked .sq__btn {
  background: var(--maroon);
  border-color: var(--maroon-deep);
  color: #fff;
  transform: scale(1.04);
}
.sq--picked .sq__who { color: var(--on-main, #f3e2e5); }
.sq--picked .sq__btn::after { content: "✓"; font-size: .6rem; line-height: 1; }

/* Three winners, and the grid says which is which. Gold, silver and bronze are
   the obvious reading of first, second and third -- but colour is never the only
   signal here, so each square also carries its place in words, in the ::after
   below and in the button's own aria-label. Somebody colour-blind, somebody
   reading with a screen reader and somebody looking at a black-and-white
   printout all get the same answer.

   The shared rule is only the shape -- the border weight and the edge
   highlight. Face, border colour and ink are set per place, ink included:
   a sold square renders its name pale for the dark teal behind it, which would
   be invisible on all three of these. */
.sq--winner .sq__btn {
  border-width: 2px;
  border-style: solid;
  /* A hairline of white along the top edge. Struck metal catches the light on
     the edge facing it, and one inset line is the whole of that effect. */
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .6);
}
.sq--winner .sq__who { font-weight: 700; }
.sq--winner .sq__btn::after {
  font-size: .58rem;
  font-weight: 700;
  letter-spacing: .04em;
  line-height: 1;
}

.sq--place1 .sq__btn { background: var(--gold-metal); border-color: var(--gold); color: var(--gold-ink); }
.sq--place1 .sq__who { color: var(--gold-ink); }
.sq--place1 .sq__btn::after { content: "★ 1st"; }

.sq--place2 .sq__btn { background: var(--silver-metal); border-color: var(--silver); color: var(--silver-ink); }
.sq--place2 .sq__who { color: var(--silver-ink); }
.sq--place2 .sq__btn::after { content: "★ 2nd"; }

.sq--place3 .sq__btn { background: var(--bronze-metal); border-color: var(--bronze); color: var(--bronze-ink); }
.sq--place3 .sq__who { color: var(--bronze-ink); }
.sq--place3 .sq__btn::after { content: "★ 3rd"; }

/* The three places as a list, under the result on the game page and on the card
   for a game already drawn. Numbered by the browser rather than by hand, and
   given the same medal colours as the squares they point at. */
.winners {
  list-style: none;
  margin: .5rem 0 .75rem;
  padding: 0;
  display: grid;
  gap: .3rem;
}
.winners li {
  border-left: 4px solid var(--line);
  padding: .1rem 0 .1rem .6rem;
}
/* The same three metals as the squares they point at, so the list and the grid
   are read as one thing rather than two colour schemes. */
.winners li:nth-child(1) { border-left-color: var(--gold); }
.winners li:nth-child(2) { border-left-color: var(--silver); }
.winners li:nth-child(3) { border-left-color: var(--bronze); }
.winners .field__help { display: block; }

/* The standing prizes on a game still selling. A row per place, the amount
   pushed to the right so the three of them line up as a column somebody can
   read down -- which is what makes a pot climbing by five pounds visible at a
   glance rather than something to be worked out of a sentence. */
.prizes__title {
  margin: .75rem 0 0;
  font-size: .78rem;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--teal-deep);
}
.winners--amounts li {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
}
.winners--amounts strong { font-variant-numeric: tabular-nums; }

/* ---------- the sticker picker, in the buy form ---------- */

/* Shut until somebody asks for it, and quiet while it is shut. Plenty of people
   buying a square have no interest in decorating it, and the one thing this
   must never do is read as a field they have left blank -- so there is no empty
   box, no red required mark, and the word "optional" sits in the summary in the
   soft ink the help text uses. A <details>, so none of this needs JavaScript. */
.sticker-pick > summary {
  display: flex;
  align-items: center;
  gap: .45rem;
  padding: .35rem 0;
  cursor: pointer;
  font-weight: 700;
  font-size: .95rem;
  color: var(--maroon);
  /* Both spellings: the pseudo-element below is the disclosure marker now, and
     a triangle sitting beside a plus sign would be two of them. */
  list-style: none;
}
.sticker-pick > summary::-webkit-details-marker { display: none; }
.sticker-pick > summary::before {
  content: "+";
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 1.3rem;
  height: 1.3rem;
  border: 1px solid currentColor;
  border-radius: 50%;
  font-size: .95rem;
  line-height: 1;
}
.sticker-pick[open] > summary::before { content: "2"; } /* a real minus, not a hyphen */
.sticker-pick > summary:focus-visible { outline: 3px solid var(--focus); outline-offset: 2px; }
.field__opt { font-weight: 400; font-size: .85rem; color: var(--ink-soft); }

.sticker-pick fieldset { border: 0; margin: 0; padding: 0; }

.sticker-grid { display: flex; flex-wrap: wrap; gap: .4rem; margin: .6rem 0 .5rem; }
.sticker { cursor: pointer; }

/* The radio is hidden, never removed: it still takes focus, still answers the
   arrow keys, and is still what a screen reader announces. Everything visible
   below is drawn from its :checked and :focus-visible states, so the tile and
   the control can never disagree about what is selected. */
.sticker input {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: 0;
  opacity: 0;
}
.sticker__face {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* 44px square. A row of emoji is exactly the sort of thing that ends up too
     small to hit on a phone, and this is the size that stops it. */
  min-width: 2.75rem;
  height: 2.75rem;
  padding: 0 .5rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--card);
  font-family: var(--emoji);
  font-size: 1.5rem;
  line-height: 1;
  transition: background .12s ease, border-color .12s ease, transform .12s ease;
}
/* "None" is a word, not a picture, so it goes back to the body face. */
.sticker--none .sticker__face {
  font-family: var(--body);
  font-size: .85rem;
  font-weight: 700;
  color: var(--ink-soft);
}
.sticker:hover .sticker__face { border-color: var(--maroon); background: var(--rose-wash); }
.sticker input:focus-visible + .sticker__face { outline: 3px solid var(--focus); outline-offset: 2px; }
/* Chosen is carried by the ring and the border as well as the wash, so it does
   not depend on telling two pale backgrounds apart. */
.sticker input:checked + .sticker__face {
  border-color: var(--maroon);
  background: var(--rose-wash);
  box-shadow: inset 0 0 0 2px var(--maroon);
  transform: scale(1.06);
}
.sticker--none input:checked + .sticker__face { color: var(--maroon); }

@media (prefers-reduced-motion: reduce) {
  .sq__btn, .progress__bar { transition: none; }
  .sq--picked .sq__btn { transform: none; }
  .sticker__face { transition: none; }
  .sticker input:checked + .sticker__face { transform: none; }
}

/* On a very small phone the gap tightens to win back a little width, but the
   square itself does not shrink -- the whole point of the minimum is that a
   name never disappears in order to make a shape fit. At this width a grid is
   usually two squares across, and growing downwards is the right answer. */
@media (max-width: 26rem) {
  .club-grid { --sq-gap: 4px; }
}

/* ============================================================
   The parents' forum
   ============================================================
   Server-rendered like everything else, and with no JavaScript of its own --
   the bell, the category chips and the first-time guide are all forms and
   links. What follows is only the parts the rest of the site had no class for
   already. */

.forum-bar {
  display: flex; flex-wrap: wrap; gap: .75rem 1rem;
  align-items: center; justify-content: space-between; margin-bottom: 1.25rem;
}
.forum-search { display: flex; gap: .5rem; flex: 1 1 18rem; }
.forum-search input {
  flex: 1 1 auto; min-width: 0;
  padding: .55rem .75rem; border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--card); font: inherit; color: var(--ink);
}
.forum-bar__actions { display: flex; gap: .6rem; align-items: center; flex-wrap: wrap; }
.forum-bar__me { font-size: .88rem; font-weight: 600; }
.forum-chips { margin-bottom: 1.25rem; }

/* The thread list. Deliberately plain rows rather than cards: a forum index is
   read by scanning titles, and a card puts furniture between each one. */
.thread-list { list-style: none; margin: 0; padding: 0; }
.thread-list__item { padding: .9rem 0; border-bottom: 1px solid var(--line); }
.thread-list__item:first-child { border-top: 1px solid var(--line); }
.thread-list__title { font-weight: 700; font-size: 1.02rem; text-decoration: none; }
.thread-list__title:hover { text-decoration: underline; }
/* Title left, thread's total likes hard right, so the badges line up down the
   page and a busy thread is obvious without reading a word. align-items:
   baseline keeps the badge sitting on the first line of a title that wraps,
   and flex-shrink: 0 stops a long title squeezing it. */
.thread-list__head { display: flex; align-items: baseline; justify-content: space-between; gap: .75rem; }
.thread-list__likes {
  flex-shrink: 0; display: inline-flex; align-items: center; gap: .3rem;
  padding: .1rem .55rem; border-radius: 999px;
  border: 1px solid var(--line); background: var(--card);
  color: var(--ink-soft); font-size: .75rem; font-weight: 600; white-space: nowrap;
}
.thread-list__excerpt { margin: .3rem 0 0; color: var(--ink-soft); font-size: .88rem; }
.thread-list__meta { margin: .25rem 0 0; font-size: .8rem; color: var(--ink-soft); }

.pill {
  display: inline-block; padding: .1rem .5rem; margin-right: .4rem;
  border-radius: 999px; background: var(--paper-2); color: var(--maroon);
  font-size: .72rem; font-weight: 700; letter-spacing: .02em;
}
.pill--muted { background: transparent; border: 1px solid var(--line); color: var(--ink-soft); }

/* Two states worth telling apart at a glance on the Monthly Draw subscriber
   list: a payment that has cleared and one that bounced. Built from the same
   two washes the notices use, so the dashboard keeps one vocabulary for good
   news and bad rather than inventing a third palette here. */
.pill--ok  { background: var(--teal-wash); color: var(--teal); }
.pill--bad { background: var(--rose-wash); color: var(--maroon); }

.crumb { font-size: .82rem; color: var(--ink-soft); margin-bottom: .5rem; }

.thread-head {
  display: flex; flex-wrap: wrap; gap: .75rem 1.5rem;
  align-items: flex-start; justify-content: space-between; margin-bottom: 1.25rem;
}
.thread-head h1 { margin: 0; flex: 1 1 16rem; }

/* The bell is a submit button, because turning emails on and off changes
   something and a link should not. It is styled to read as a toggle. */
.bell {
  display: inline-flex; align-items: center; gap: .45rem;
  padding: .45rem .8rem; border-radius: 999px; cursor: pointer;
  border: 1px solid var(--line); background: var(--card);
  color: var(--ink-soft); font: inherit; font-size: .8rem; font-weight: 600;
}
.bell:hover { border-color: var(--maroon); color: var(--maroon); }
.bell--on { border-color: var(--maroon); background: var(--paper-2); color: var(--maroon); }
.bell__icon { font-size: .95rem; line-height: 1; }

.post { padding: 1.1rem 0; border-bottom: 1px solid var(--line); }
.post--first { border-bottom: 2px solid var(--rose-soft); }
.post__by { margin: 0 0 .5rem; font-size: .84rem; color: var(--ink-soft); }
.post__by strong { color: var(--ink); font-size: .92rem; }
/* The line under a name: year groups for a parent, "Staff" for a teacher. It is
   what makes a name mean something to somebody who has not met them. */
.post__sub { display: block; font-size: .78rem; }
.post__when { display: block; font-size: .74rem; opacity: .8; }
.post__mod { margin-top: .6rem; }
.post--removed { color: var(--ink-soft); font-style: italic; }
.post__removed { margin: 0; font-size: .88rem; }

.reply-form { margin-top: 1.75rem; }

/* A button that reads as a link, for the destructive or secondary action inside
   a row that already has a real button in it. */
.linkish {
  background: none; border: 0; padding: 0; cursor: pointer;
  font: inherit; font-size: .85rem; font-weight: 600;
  color: var(--maroon); text-decoration: underline;
}
.linkish:hover { color: var(--teal); }
.linkish--danger { color: #9a2b2b; }
.inline-form { display: inline; }

.child-block { border-top: 1px solid var(--line); padding-top: 1.25rem; margin-top: .5rem; }
.child-row {
  border: 1px solid var(--line); border-radius: var(--radius);
  padding: .9rem 1rem; margin-bottom: .85rem; background: var(--paper);
}
.child-row legend { font-size: .78rem; font-weight: 700; text-transform: uppercase; letter-spacing: .06em; color: var(--ink-soft); padding-inline: .35rem; }
.choice { display: flex; gap: .5rem; align-items: baseline; margin-bottom: .4rem; }

/* The first-time guide: four pages, a Next button, and these dots to say how
   far through it you are. */
.tutorial-dots { list-style: none; display: flex; gap: .4rem; margin: 0 0 1.5rem; padding: 0; }
.tutorial-dots li { width: 2.25rem; height: .3rem; border-radius: 999px; background: var(--line); }
.tutorial-dots li.is-done { background: var(--rose-soft); }
.tutorial-dots li.is-current { background: var(--maroon); }

.pager { display: flex; gap: 1rem; align-items: center; justify-content: center; margin-top: 1.5rem; font-size: .88rem; }
.pager span { color: var(--ink-soft); }

.btn--small { padding: .35rem .7rem; font-size: .8rem; }
.is-muted { opacity: .55; }

/* What somebody is, next to their name. A person can be more than one of these
   at once -- the committee member answering a uniform question is usually a
   parent too, and saying so changes how the answer reads. */
.tags { display: inline-flex; flex-wrap: wrap; gap: .3rem; margin-left: .45rem; vertical-align: .05em; }
.tag {
  display: inline-block; padding: .05rem .45rem; border-radius: 999px;
  font-size: .68rem; font-weight: 700; letter-spacing: .02em; text-transform: uppercase;
  background: var(--paper-2); color: var(--ink-soft); border: 1px solid var(--line);
}
.tag--staff { background: var(--teal); border-color: var(--teal); color: #fff; }
/* Deliberately the loudest of the three. "This answer is from the committee" is
   the one thing on a byline worth spotting from half a screen away. */
.tag--psfa { background: var(--maroon); border-color: var(--maroon); color: #fff; }
/* A moderator speaks for the committee on the forum but is not the committee,
   so it reads as a quieter relative of the maroon rather than a fourth colour:
   plainly a badge, plainly not "Parent", plainly not the loudest thing here. */
.tag--mod { background: var(--paper); border-color: var(--maroon); color: var(--maroon); }

/* Tables written in a post or a page's text.
   Deliberately not the dashboard's .table, which carries a 34rem minimum width
   so that a list of many columns scrolls rather than crushes. A two-column table
   of uniform prices does not want that floor -- it would be forced wider than a
   phone and made to scroll sideways for no reason. This one sizes to its
   content and only scrolls when it genuinely does not fit. */
.md-table-wrap { overflow-x: auto; margin: 1.35rem 0; }
.md-table { border-collapse: collapse; font-size: .92rem; }
.md-table th {
  text-align: left; font-size: .74rem; font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--ink-soft);
  padding: 0 1rem .5rem 0; border-bottom: 2px solid var(--rose-soft); white-space: nowrap;
}
.md-table td {
  padding: .55rem 1rem .55rem 0; border-bottom: 1px solid var(--line); vertical-align: top;
}
.md-table tr:last-child td { border-bottom: 0; }
.md-table th:last-child, .md-table td:last-child { padding-right: 0; }
.md-table .al-right { text-align: right; font-variant-numeric: tabular-nums; }
.md-table .al-centre { text-align: center; }

/* Headings and dividers written in a post or a page's text.
   Sized well below the page's own headings on purpose. What this formatter
   produces always sits underneath a page or thread title that is already the
   h1, so a "# " heading is an h2 in the markup and looks like a subheading on
   the screen -- a parent should not be able to make a post shout across a
   thread. The steps between the three levels are deliberately small. */
.md-h { font-family: var(--display); color: var(--maroon); line-height: 1.25; margin: 1.75rem 0 .5rem; }
.md-h:first-child { margin-top: 0; }
h2.md-h { font-size: 1.3rem; }
h3.md-h { font-size: 1.12rem; }
h4.md-h { font-size: 1rem; letter-spacing: .01em; }
h5.md-h { font-size: .95rem; letter-spacing: .01em; }

.md-rule { border: 0; border-top: 1px solid var(--line); margin: 1.75rem 0; }

/* A table cell holding a list. A uniform guide is a grid of "which term" against
   "what to wear", and what to wear is four or five separate garments -- strung
   along one line they are unreadable, so a bullet in a cell becomes a real list.
   The width floor matters: without it, four list columns side by side squeeze
   into ribbons one word wide. With it the table scrolls sideways in its own box,
   which is the failure people can actually cope with. */
.md-cell-list { margin: 0; padding-left: 1.1rem; }
.md-cell-list li { margin: 0 0 .25rem; }
.md-cell-list li:last-child { margin-bottom: 0; }
.md-table td.has-list { min-width: 11.5rem; }
.md-table th, .md-table td { vertical-align: top; }

/* ---------- forum layout ----------
   The forum is the one part of this site that is a tool rather than a leaflet.
   The public pages are deliberately a narrow reading column, which is right for
   a paragraph about the summer fair and wrong for a thread list, a search
   result or a four-column uniform table -- on a desktop those were a ribbon
   down the middle of an empty screen.

   So the browsing pages get a wider frame, and the reading measure is protected
   separately, where it actually matters: inside the text of a post. */
:root { --wrap-forum: 84rem; }
.wrap--forum { max-width: var(--wrap-forum); }

/* The Monthly Draw.
 *
 * Wider than the 44rem reading column, because this page is not prose: it is a
 * set of figures, a list of rules and a form, and on a laptop all three were
 * stacked in a single narrow strip with most of the screen empty either side.
 * Short of the forum's 84rem, because two columns at that width give a measure
 * long enough to lose your place in.
 *
 * Everything below is additive and lives behind a min-width, so the phone
 * layout is untouched -- one column, in source order, exactly as before. */
:root { --wrap-draw: 76rem; }
.wrap--draw { max-width: var(--wrap-draw); }

/* 2rem is what the margins between these cards used to be, so stacked on a
   phone the rhythm of the page is unchanged. */


/* The three figures at the top of the Monthly Draw.
   These were three .card-grid cards, which below about 34rem collapse to one
   column -- three full-width cards stacked, taking most of a phone screen to
   say a date and two amounts. One strip divided into three stays three across
   at every width, because once the wording is gone there is nothing to wrap. */
.draw-figures {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 1px;
  background: var(--line, #e7dcdd);
  border: 1px solid var(--line, #e7dcdd);
  border-radius: 12px;
  overflow: hidden;
  margin: 1.5rem 0 2rem;
}

/* A two-column strip when the Snowball is switched off, rather than one wide
   third of empty space. */
.draw-figures:has(> :last-child:nth-child(2)) { grid-template-columns: repeat(2, minmax(0, 1fr)); }

.draw-figures > div {
  background: #fff;
  padding: 0.75rem 0.9rem;
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  min-width: 0;
}

.draw-figures__label {
  font-size: 0.7rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted, #6f6266);
  /* A label that wraps costs a whole line on a phone, and "Snowball · December"
     is the only one long enough to try. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.draw-figures__value {
  font-size: 1.15rem;
  font-weight: 600;
  line-height: 1.2;
}

@media (min-width: 40rem) {
  .draw-figures > div { padding: 1rem 1.25rem; }
  .draw-figures__value { font-size: 1.35rem; }
}

/* Two columns of fields inside a form on a wide screen.
   The Monthly Draw signup used to sit in a narrow right-hand column, which made
   it a very tall ribbon with a page of empty space beside it. Full width and
   two columns is the same fields in half the height. One column below 48rem,
   because a form field narrower than about 20rem is worse than a long page. */
.form-cols { display: grid; gap: 0 2rem; }

@media (min-width: 48rem) {
  .form-cols { grid-template-columns: repeat(2, minmax(0, 1fr)); align-items: start; }
}

/* The second row of settings tabs. Quieter and tucked under the family it
   belongs to, so the eye picks the draw first and the section second rather
   than reading eight equal-weight pills as one list. */
.chips--sub {
  margin: -0.35rem 0 1.5rem;
  padding-left: 0.9rem;
  border-left: 2px solid var(--line, #e7dcdd);
}

.chips--sub .chip { font-size: 0.82rem; padding-block: 0.3rem; }

/* The "another amount" box.
   The £ used to be a block-level <label>, so it sat on its own line above the
   field instead of against it. Flex puts them on one line. */
.topup-other {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  margin: 0.5rem 0 0 1.75rem;
}

.topup-other__sign { font-weight: 600; margin: 0; }
.topup-other input { max-width: 8rem; }

/* And it is only meaningful once "Another amount" is chosen. Written as a
   negative on purpose: a browser without :has() drops the whole rule and the
   box simply stays visible, which is what it did before. Hiding by default and
   revealing with :has() would leave that browser unable to type an amount at
   all. */
.topup-choices:not(:has(input[value="other"]:checked)) .topup-other { display: none; }

/* A border round a run of related settings. Two pairs of GoCardless
   credentials in a flat list are four boxes; with a line round each pair they
   are two accounts, which is what they are -- and pasting a live token into a
   sandbox slot becomes a harder mistake to make. */
.settings-box {
  border: 1px solid var(--line, #e7dcdd);
  border-radius: 12px;
  padding: 0.4rem 1.1rem 1rem;
  margin: 0 0 1.25rem;
  min-width: 0;
}

.settings-box > legend {
  padding: 0 0.45rem;
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted, #6f6266);
}

.forum-layout { display: grid; gap: 1.5rem; }

/* Below this width there is no room for a column beside anything, so the
   categories stay the horizontal row of chips they have always been and the
   threads follow underneath. This is the mobile layout and it is the default --
   the sidebar is what gets added when there is space, not the other way round. */
@media (min-width: 62rem) {
  .forum-layout {
    grid-template-columns: 14.5rem minmax(0, 1fr);
    gap: 2.5rem;
    align-items: start;
  }
  /* The chips become a list once they have a column of their own. Sticky, so the
     categories stay reachable while a long thread list scrolls past them. */
  .forum-side .chips {
    flex-direction: column;
    align-items: stretch;
    gap: .25rem;
    position: sticky;
    top: 1.25rem;
  }
  .forum-side .chip { text-align: left; border-radius: var(--radius-sm, 8px); }
  .forum-chips { margin-bottom: 0; }
}

/* A post's text, held to a readable measure inside the wider frame. Prose set
   the full width of a desktop screen is about 160 characters a line, which is
   miserable to read -- the eye loses its place on the way back to the left.
   Tables are deliberately exempt: a wide table wants every pixel it can get,
   and it is scanned rather than read. */
.forum-thread .post .prose > :not(.md-table-wrap) { max-width: 74ch; }
.forum-thread .post__by { max-width: 74ch; }
.forum-thread .reply-form { max-width: 52rem; }

/* Composing. A table typed into a 44rem box wraps every row while it is being
   written, which makes the columns impossible to line up by eye. */
.forum-compose .form-grid { max-width: 60rem; }
.forum-compose textarea, textarea.md-compose { min-height: 16rem; font-family: var(--mono, ui-monospace, "Cascadia Mono", Menlo, Consolas, monospace); font-size: .9rem; }

/* Grouped entries in the site header, built the same way as the dashboard's.
   No JavaScript: :hover for a mouse, :focus-within for a keyboard, and the
   element's own open state for a tap on a phone, where hover does not exist. */
.site-nav__group { position: relative; }
.site-nav__summary {
  display: inline-flex; align-items: center; gap: .35rem; cursor: pointer;
  list-style: none; white-space: nowrap;
  color: var(--on-main, #f6e6e8); font-size: .78rem; font-weight: 600;
  letter-spacing: .06em; text-transform: uppercase;
  padding: .35rem 0; border-bottom: 2px solid transparent;
}
.site-nav__summary::-webkit-details-marker { display: none; }
.site-nav__summary:hover { color: #fff; border-bottom-color: var(--rose); }
/* The group holding the page you are on, marked without being held open. */
.site-nav__summary[data-here] { color: #fff; border-bottom-color: var(--mustard); }
.site-nav__caret {
  width: .34rem; height: .34rem; margin-top: -.18rem; opacity: .75;
  border-right: 2px solid currentColor; border-bottom: 2px solid currentColor;
  transform: rotate(45deg);
}

/* The wide bar: a panel that drops from the header. It sits flush against the
   summary rather than a few pixels below it -- a gap there is a strip the
   pointer crosses on the way down, closing the menu just as it is reached. */
.site-nav--wide .site-nav__menu {
  display: none;
  position: absolute; left: 0; top: 100%; z-index: 45; min-width: 12.5rem;
  background: var(--maroon-deep); border-radius: var(--radius);
  padding: .4rem; margin-top: .45rem; box-shadow: var(--shadow-lg);
}
.site-nav--wide .site-nav__group:hover > .site-nav__menu,
.site-nav--wide .site-nav__group:focus-within > .site-nav__menu,
.site-nav--wide .site-nav__group[open] > .site-nav__menu { display: block; }
.site-nav--wide .site-nav__menu a {
  display: block; padding: .55rem .7rem; border-bottom: 0;
  border-radius: var(--radius-sm); font-size: .8rem;
  text-transform: none; letter-spacing: normal;
}
.site-nav--wide .site-nav__menu a:hover,
.site-nav--wide .site-nav__menu a[aria-current="page"] {
  background: rgba(255, 255, 255, .1); border-bottom: 0; color: #fff;
}

/* Inside the phone drawer there is no room to float anything, so a group is a
   section that expands in place. The browser hides a closed <details>'s content
   on its own, which is exactly the behaviour wanted here. */
.site-nav--drawer .site-nav__summary {
  width: 100%; justify-content: space-between;
  padding: .7rem .8rem; border-bottom: 0; border-radius: var(--radius-sm);
}
.site-nav--drawer .site-nav__summary:hover { background: rgba(255, 255, 255, .1); border-bottom: 0; }
.site-nav--drawer .site-nav__group[open] > .site-nav__summary { background: rgba(255, 255, 255, .07); }
.site-nav--drawer .site-nav__group[open] > .site-nav__summary .site-nav__caret { transform: rotate(-135deg); }
.site-nav--drawer .site-nav__menu { padding: .1rem 0 .35rem .7rem; }
/* One link to a line. Left inline, with the nav's nowrap, a group's links sat
   side by side in a single row as wide as all of them put together, and the
   drawer grew to fit it -- straight off the edge of the phone. */
.site-nav--drawer .site-nav__menu a { display: block; font-size: .74rem; }

/* A thumbs up under a post. A button, not a link, because pressing it changes
   something. Your own post shows the count with nothing to press. */
.post__foot { display: flex; flex-wrap: wrap; gap: .75rem 1.25rem; align-items: center; margin-top: .9rem; }
.like {
  display: inline-flex; align-items: center; gap: .4rem; cursor: pointer;
  padding: .3rem .7rem; border-radius: 999px;
  border: 1px solid var(--line); background: var(--card);
  color: var(--ink-soft); font: inherit; font-size: .8rem; font-weight: 600;
}
.like:hover { border-color: var(--maroon); color: var(--maroon); }
.like--on { border-color: var(--maroon); background: var(--paper-2); color: var(--maroon); }
.like--static { cursor: default; border-style: dashed; }
.like--static:hover { border-color: var(--line); color: var(--ink-soft); }
.like__count {
  background: var(--maroon); color: #fff; border-radius: 999px;
  padding: 0 .4rem; min-width: 1.3em; text-align: center; font-size: .72rem;
}
.like--on .like__count { background: var(--maroon); }

/* ==========================================================================
   Rapid entry — /admin/stock/rapid

   Three steps that open as you go, built out of hidden radios and the sibling
   combinator because the site runs script-src 'none'. The rules that name
   individual ids are generated per page in routes/admin/rapid.js; what is here
   is only how the thing looks and the default of "not yet revealed".

   Sized for a thumb rather than a mouse: this gets used standing over a bag of
   uniform with a phone in one hand.
   ========================================================================== */
/* Hidden, but fixed to the top-left corner rather than tucked in the flow at
   the top of the form. Pressing a label focuses the radio it names, and a
   browser scrolls a newly focused control into view -- with these sitting in
   the flow, choosing a size half way down the page yanked the page back up to
   wherever the radio was. Fixed, they are already on screen, so there is
   nothing for the browser to scroll to. */
.rapid__radio { position: fixed; top: 0; left: 0; width: 1px; height: 1px; margin: 0; padding: 0; overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0; opacity: 0; }

.rapid__step { margin-bottom: 1.6rem; }
/* Steps two and three stay shut until the step above them has been answered. */
.rapid__step--size, .rapid__step--qty { display: none; }
/* Any type at all opens step two, so this one rule covers the lot -- every type
   radio carries .rapid__type and every one of them sits before .rapid__steps.
   Step three is fussier: it has to know the size belongs to the type that is
   open, which needs an id per type, so those rules are generated with the page
   in routes/admin/rapid.js. */
.rapid__type:checked ~ .rapid__steps .rapid__step--size { display: block; }

.rapid__head { display: flex; align-items: center; gap: .6rem; font-size: 1.05rem; margin-bottom: .7rem; }
.rapid__n {
  display: inline-flex; align-items: center; justify-content: center;
  width: 1.7rem; height: 1.7rem; border-radius: 50%;
  background: var(--rose-wash); color: var(--maroon);
  font-size: .85rem; font-weight: 700;
}

.rapid__grid, .rapid__sizes {
  display: grid; gap: .6rem;
  grid-template-columns: repeat(auto-fill, minmax(9.5rem, 1fr));
}
/* Only the chosen type's sizes are in the document flow. */
.rapid__sizes { display: none; }

.rapid__opt {
  display: flex; flex-direction: column; justify-content: center; gap: .2rem;
  min-height: 3.9rem; padding: .7rem .8rem; cursor: pointer;
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius-sm);
  color: var(--ink); font-weight: 600; line-height: 1.25; text-align: center;
}
.rapid__opt:hover { border-color: var(--maroon); }
/* The radio itself is off-screen, so the ring that says "keyboard is here" has
   to be drawn on its label instead. Those rules name ids and so are generated
   with the page, alongside the checked ones. */
.rapid__opt--size .rapid__size { font-size: 1.15rem; }
.rapid__have { font-size: .74rem; font-weight: 500; color: var(--ink-soft); }
.rapid__tag {
  display: block; font-size: .7rem; font-weight: 500;
  color: var(--ink-soft); text-transform: lowercase;
}

/* The counter. One row per value, all hidden; the generated CSS shows the one
   whose radio is checked, and the arrows are labels pointing at its
   neighbours. */
.rapid__counters { margin-bottom: 1rem; }
.rapid__counter { display: none; align-items: center; gap: 1rem; }
.rapid__arrow {
  display: inline-flex; align-items: center; justify-content: center;
  width: 3.4rem; height: 3.4rem; cursor: pointer;
  background: var(--card); border: 1px solid var(--line); border-radius: 50%;
  color: var(--maroon); font-size: 1.9rem; line-height: 1; user-select: none;
}
.rapid__arrow:hover { background: var(--rose-wash); border-color: var(--maroon); }
.rapid__arrow:active { transform: translateY(1px); }
.rapid__arrow--off { opacity: .3; cursor: default; }
.rapid__num {
  min-width: 3.5rem; text-align: center;
  font-family: var(--display); font-size: 2.4rem; font-weight: 600; color: var(--ink);
}

.rapid__typed { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem .8rem; margin-bottom: 1.1rem; }
.rapid__typed label { font-weight: 600; font-size: .85rem; }
.rapid__typed input {
  width: 6rem; padding: .5rem .6rem;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  font: inherit; background: var(--card); color: var(--ink);
}

.rapid__add { font-size: 1.05rem; padding: .95rem 2.4rem; }

/* What you have just done, newest first, each line with its way back. */
.rapid__log { list-style: none; margin: 0 0 .8rem; padding: 0; }
.rapid__log li {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: .5rem; padding: .55rem 0; border-bottom: 1px solid var(--line);
}
.rapid__log li:last-of-type { border-bottom: 0; }
.rapid__log-size { color: var(--ink-soft); }

@media (max-width: 30rem) {
  .rapid__grid, .rapid__sizes { grid-template-columns: repeat(auto-fill, minmax(7.5rem, 1fr)); }
}

/* On a phone the counter used to open below the fold: you picked a size and
   then had to scroll to find out where the Add button had gone. Nothing here
   can scroll the page on its own -- that would be JavaScript -- so instead of
   chasing the counter down the page, the counter comes to the thumb. Once a
   size is chosen step three pins itself across the bottom of the screen, and
   stays there while you scroll the sizes behind it. */
@media (max-width: 46rem) {
  .rapid__step--qty {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 30;
    margin: 0; padding: .7rem clamp(.8rem, 4vw, 1.2rem);
    background: var(--card); border-top: 1px solid var(--line);
    box-shadow: 0 -6px 20px rgba(42, 31, 34, .13);
    display: none; /* still only shown once a size is picked */
  }
  /* The step-three heading is what the bar plainly is, so it goes; the numeral
     would cost a line of a small screen to say nothing. */
  .rapid__step--qty .rapid__head { display: none; }

  .rapid__step--qty .rapid__counters { margin: 0; }
  .rapid__counter { justify-content: center; gap: .8rem; }
  .rapid__arrow { width: 3rem; height: 3rem; font-size: 1.7rem; }
  .rapid__num { font-size: 2rem; min-width: 2.8rem; }

  /* The typed box keeps its place beside the arrows rather than being dropped
     on small screens -- a bag of forty is exactly when you are standing up
     with a phone. Its help line is the one thing that goes. */
  .rapid__typed { justify-content: center; margin: .5rem 0 .6rem; }
  .rapid__typed .field__help { display: none; }
  .rapid__typed input { width: 4.5rem; }

  .rapid__add { width: 100%; padding: .85rem 1rem; }

  /* Room at the foot of the page so the bar never covers the last row of
     sizes. Reserved as soon as a type is picked, which is one step before the
     bar can appear. */
  .rapid__type:checked ~ .rapid__steps { padding-bottom: 13rem; }
}

/* ---------- home learning: spelling practice ---------- */
/* Everything on the sheet is prefixed sheet-, and the controls spell-, because
   the tool was written with names like .tick and .choice that the site already
   uses for something else. The sheet is lettered in Andika, a face made for
   children learning to read, and falls back to the rounded faces most
   machines already have. */

.spell-page { --hand: "Andika", "Comic Sans MS", "Chalkboard SE", "Segoe Print", var(--body); }
/* The tool runs to the full 68rem, but a lede that long is hard to read. */
.spell-head .lede { max-width: var(--wrap-narrow); }

.spell__layout { display: grid; gap: clamp(1.25rem, 3vw, 2rem); align-items: start; }
@media (min-width: 62rem) {
  .spell__layout { grid-template-columns: minmax(19rem, 23rem) minmax(0, 1fr); }
}
.spell__controls { display: grid; gap: clamp(1rem, 2vw, 1.5rem); min-width: 0; }
.spell__panel h2 { font-size: 1.3rem; margin-bottom: 1rem; }
.spell__preview { display: grid; gap: 1rem; min-width: 0; }

.spell-add { display: flex; gap: .5rem; }
.spell-add input { flex: 1; min-width: 0; }
.spell-add .btn { flex: none; padding-inline: 1.2rem; }
.spell-hint.is-error { color: var(--maroon); font-weight: 700; }

.spell-words { list-style: none; margin: 1rem 0 0; padding: 0; display: flex; flex-wrap: wrap; gap: .4rem; }
.spell-words:empty { display: none; }
.spell-word {
  display: inline-flex; align-items: center; gap: .1rem; max-width: 100%; margin: 0;
  padding: .25rem .25rem .25rem .8rem; border-radius: 999px;
  background: var(--teal-wash); color: var(--teal-deep);
  font-weight: 600; font-size: .95rem; line-height: 1.3;
}
.spell-word__text { overflow-wrap: anywhere; }
.spell-word__remove {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 1.7rem; height: 1.7rem; border: 0; border-radius: 50%;
  background: transparent; color: inherit; font-size: 1.15rem; line-height: 1; cursor: pointer;
}
.spell-word__remove:hover { background: var(--card); color: var(--maroon); }
.spell-word.is-new { animation: spell-in .2s ease-out; }
@keyframes spell-in { from { opacity: 0; transform: scale(.92); } to { opacity: 1; transform: none; } }
.card .spell-empty { margin-top: 1rem; }

.spell-listbar {
  display: flex; flex-wrap: wrap; align-items: center; gap: .35rem 1rem;
  margin-top: 1.1rem; padding-top: .9rem; border-top: 1px solid var(--line);
  font-size: .85rem; color: var(--ink-soft);
}
.spell-count { margin-right: auto; }
/* Buttons, because they do something rather than go somewhere -- dressed as
   links because they are secondary to everything else on the panel. */
.spell-link {
  background: none; border: 0; padding: 0; cursor: pointer;
  font: inherit; font-weight: 600; color: var(--teal-deep);
  text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: .18em;
}
.spell-link:hover { color: var(--maroon); }

.spell-modes { margin-bottom: 1.25rem; }
.spell-modes .chip { font-family: var(--body); cursor: pointer; }
.spell-opts { display: grid; gap: .85rem; }
.spell-check .field__help { margin: .15rem 0 0 1.6rem; }

.spell-actions { display: flex; flex-wrap: wrap; align-items: center; gap: .7rem; }

/* On each maths page, under its heading: back to the Maths page, and across
   to the other maths tools. The chips are the site's own, the current one
   marked by aria-current as the diary's are. */
.maths-nav { display: flex; flex-wrap: wrap; align-items: center; gap: .6rem 1.1rem; margin-bottom: clamp(1.25rem, 3vw, 1.75rem); }
.maths-nav__all { font-weight: 700; font-size: .85rem; white-space: nowrap; }
.spell-toast { font-size: .85rem; font-weight: 700; color: var(--teal-deep); }

/* The sheet itself: white, like the paper it will be printed on. */
.spell-paper {
  background: #fff; color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--radius-sm); box-shadow: var(--shadow-lg);
  padding: clamp(1.25rem, 4vw, 2.5rem) clamp(1.1rem, 4vw, 2.75rem) clamp(1.5rem, 4vw, 3rem);
  min-height: 24rem;
}

.sheet-head {
  display: flex; flex-wrap: wrap; justify-content: space-between; align-items: baseline; gap: .5rem 1.5rem;
  border-bottom: 2px solid var(--rose-soft); padding-bottom: .7rem; margin-bottom: 1.4rem;
}
.sheet-title { font-size: clamp(1.2rem, 2.2vw, 1.45rem); margin: 0; }
.sheet-fill { font-size: .88rem; color: var(--ink-soft); white-space: nowrap; }
.sheet-note { color: var(--ink-soft); font-size: .95rem; max-width: 44ch; }
.sheet-fail { color: var(--maroon); font-weight: 700; font-size: .95rem; margin-top: 1rem; }

.sheet-row {
  display: flex; align-items: baseline; gap: .9rem;
  padding: .55rem 0 .7rem; border-bottom: 1px dotted #d9cbc8;
}
.sheet-row--shape { align-items: center; }
.sheet-num { width: 1.4rem; flex: none; text-align: right; color: var(--ink-soft); font-size: .8rem; font-variant-numeric: tabular-nums; }

.sheet-answers { margin-top: 2.25rem; padding-top: 1.25rem; border-top: 2px solid var(--rose-soft); }
.sheet-answers__title {
  font-family: var(--body); font-size: .74rem; font-weight: 700; letter-spacing: .17em;
  text-transform: uppercase; color: var(--maroon); margin: 0 0 .9rem;
}

/* fill the gaps */
.sheet-gapword { display: flex; flex-wrap: wrap; font-family: var(--hand); font-size: clamp(1.45rem, 3.4vw, 1.9rem); line-height: 1.35; }
.sheet-ch { display: inline-block; min-width: .62em; margin-right: .09em; text-align: center; }
.sheet-ch--space { min-width: .4em; }
.sheet-ch--gap { color: var(--teal); }
.is-answer .sheet-ch--gap { color: var(--maroon); font-weight: 700; }

/* spot the spelling */
.sheet-choices { display: flex; flex-wrap: wrap; gap: .5rem; flex: 1; }
.sheet-choice {
  display: inline-flex; align-items: center; gap: .45rem; padding: .1rem .75rem .1rem .2rem;
  font-family: var(--hand); font-size: clamp(1.1rem, 2.4vw, 1.3rem);
}
.sheet-tick {
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  width: 1.4rem; height: 1.4rem; border: 1.5px solid var(--rose); border-radius: 50%;
  font-family: var(--body); font-size: .7rem; color: var(--ink-soft);
}
.is-answer .sheet-choice--right { color: var(--maroon); font-weight: 700; }
.is-answer .sheet-choice--right .sheet-tick { border-color: var(--maroon); color: var(--maroon); }

/* after the two it overrides */
.sheet-plain { font-family: var(--body); }

/* word shapes: tall letters rise, tails drop, the rest sit on the line */
.sheet-shape { display: flex; align-items: flex-start; flex: none; height: 48px; }
.sheet-shape__cell { position: relative; flex: none; width: 21px; height: 48px; margin-right: -1.5px; }
.sheet-shape__cell--gap { width: 12px; }
.sheet-shape__box { position: absolute; left: 0; right: 0; border: 1.5px solid var(--teal); border-radius: 2px; }
.sheet-shape__box--small { top: 14px; height: 20px; }
.sheet-shape__box--tall { top: 0; height: 34px; }
.sheet-shape__box--drop { top: 14px; height: 34px; }
.sheet-writeline { flex: 1; min-width: 7rem; max-width: 15rem; height: 24px; border-bottom: 1px dotted #b9aaa7; }
.sheet-shapeword { font-family: var(--hand); font-size: 1.35rem; font-weight: 700; color: var(--maroon); }
.sheet-bank { margin-top: 1.6rem; padding: .9rem 1.1rem; border: 1.5px solid var(--rose-soft); border-radius: var(--radius-sm); }
.sheet-bank__title {
  font-family: var(--body); font-size: .74rem; font-weight: 700; letter-spacing: .14em;
  text-transform: uppercase; color: var(--ink-soft); margin: 0 0 .5rem;
}
.sheet-bank__words, .sheet-wslist {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(7.5rem, 1fr)); gap: .25rem 1.1rem;
  font-family: var(--hand); font-size: 1.1rem;
}

/* word search */
.sheet-grid { display: grid; max-width: 38rem; margin: 0 auto; font-family: var(--hand); }
.sheet-cell {
  display: flex; align-items: center; justify-content: center; aspect-ratio: 1 / 1;
  font-size: var(--cellsize, 20px); line-height: 1;
}
.sheet-cell--hit { color: var(--maroon); font-weight: 700; background: var(--rose-wash); border-radius: 50%; }
.sheet-wslist { margin-top: 1.6rem; padding-top: 1rem; border-top: 1px solid var(--line); }

/* The letterhead: the site header's own lockup on the same maroon band. A
   printer drops background colours unless it is told otherwise, and a white
   "PSFA" on no band at all would vanish, hence print-color-adjust. */
.sheet-brand {
  display: flex; flex-wrap: wrap; align-items: center; gap: .4rem 1rem;
  background: var(--maroon); color: #fff; border-radius: 7px;
  padding: .3rem .9rem; margin-bottom: 1.1rem;
  -webkit-print-color-adjust: exact; print-color-adjust: exact;
}
/* The header's lockup at about half its size: a letterhead, not a banner, so
   the page is left for the words. The proportions between the three lines are
   the header's own. */
.sheet-brand .brand { color: #fff; gap: .4rem; }
.sheet-brand .mark--brand { width: 22px; height: 22px; }
.sheet-brand .brand__place { font-size: .38rem; margin-bottom: .1rem; }
.sheet-brand .brand__name { font-size: .5rem; margin-bottom: .08rem; }
.sheet-brand .brand__sub { font-size: .95rem; }
.sheet-brand .mark__ground { fill: var(--mark-ground-light, #f0dcdf); }
.sheet-brand__tool {
  font-size: .58rem; font-weight: 700; letter-spacing: .17em;
  text-transform: uppercase; color: var(--rose-soft);
}
/* Each part of a sheet -- the questions, any .sheet-newpage sections, then the
   answers -- is a table whose header and footer rows hold the letterhead and
   footer (see frame() in home-learning.js). Printed, the browser repeats both on
   every page the table runs across, and each part after the first starts a
   page of its own. On screen the preview is one long sheet, so a later part's
   letterhead waits until it is actually a page of its own. */
.sheet-frame { width: 100%; border-collapse: collapse; }
.sheet-frame > * > tr > td { padding: 0; vertical-align: top; }
.sheet-frame--next > thead { display: none; }
/* A page break a sheet asked for, shown on screen as a dashed rule. */
.sheet-newpage { margin-top: 2.25rem; padding-top: 1.5rem; border-top: 2px dashed var(--rose-soft); }

.sheet-foot {
  display: flex; flex-wrap: wrap; justify-content: space-between; gap: .2rem 1rem;
  margin-top: 1.75rem; padding-top: .6rem; border-top: 1px solid var(--rose-soft);
  font-size: .75rem; color: var(--ink-soft);
}
.sheet-foot p { margin: 0; }

/* number bonds and times tables: the controls */
.spell-opts legend { font-weight: 700; font-size: .92rem; color: var(--maroon); }
.bonds-eg { margin-left: .35rem; color: var(--ink-soft); white-space: nowrap; }

/* number bonds: sentences, two to a row */
.sheet-bonds { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .2rem 2rem; }
@media (max-width: 40rem) { .sheet-bonds { grid-template-columns: 1fr; } }
.sheet-bond {
  display: flex; align-items: center; gap: .55rem;
  padding: .55rem 0; border-bottom: 1px dotted #d9cbc8; break-inside: avoid;
  font-family: var(--hand); font-size: clamp(1.25rem, 2.6vw, 1.5rem);
}
.sheet-bond .sheet-num { font-family: var(--body); }
.sheet-box {
  display: inline-block; min-width: 1.5em; height: 1.35em; line-height: 1.3em; text-align: center;
  border: 1.5px solid var(--teal); border-radius: 3px;
}
.is-answer .sheet-box { border-color: var(--maroon); color: var(--maroon); font-weight: 700; }

/* number bonds: part-whole circles */
.sheet-pwgrid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: .9rem 1.1rem; }
@media (max-width: 40rem) { .sheet-pwgrid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.sheet-pw { text-align: center; padding-bottom: .5rem; break-inside: avoid; }
.sheet-pw__num { display: block; text-align: left; font-size: .8rem; color: var(--ink-soft); }
/* The drawing is only the circles and lines; the numbers sit on top of it in
   HTML. Text inside an <svg> could not use Andika in a downloaded PDF (see
   home-learning.js), and the numbers are the part a child reads. Each number
   is centred on its circle, as fractions of the 170 x 132 drawing, and sized
   against the drawing's width so it stays in proportion at any size. */
.sheet-pw__model { position: relative; display: inline-block; width: 100%; max-width: 165px; container-type: inline-size; }
.sheet-pw__model svg { display: block; width: 100%; height: auto; }
.sheet-pw__ring { fill: none; stroke: var(--teal); stroke-width: 1.6; }
.sheet-pw__link { stroke: var(--teal); stroke-width: 1.6; }
.is-answer .sheet-pw__ring--fill { stroke: var(--maroon); }
.sheet-pw__val {
  position: absolute; width: 30%; margin: -.5em 0 0 -15%;
  font-family: var(--hand); font-size: 19px; font-size: 11.8cqw; line-height: 1;
  text-align: center; color: var(--ink);
}
.sheet-pw__val--top { left: 50%; top: 22.73%; }
.sheet-pw__val--left { left: 27.06%; top: 75.76%; }
.sheet-pw__val--right { left: 72.94%; top: 75.76%; }
.is-answer .sheet-pw__val--fill { color: var(--maroon); font-weight: 700; }

/* times tables: the table picker. A box per table, teal once it is picked --
   maroon is already the chosen kind of sheet, just below. The tick box itself
   is invisible and covers the whole box, so the box is what gets clicked and
   what shows the focus ring. */
.tt-tables { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: .4rem; }
.tt-table {
  position: relative; display: flex; align-items: center; justify-content: center;
  padding: .5rem 0; border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--card); color: var(--ink); font-weight: 700; font-size: .95rem;
  font-variant-numeric: tabular-nums; cursor: pointer; user-select: none;
}
.tt-table input { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; opacity: 0; cursor: pointer; }
.tt-table:hover { border-color: var(--rose); color: var(--maroon); }
.tt-table:has(input:checked) { background: var(--teal); border-color: var(--teal); color: #fff; }
.tt-table:has(input:focus-visible) { outline: 2px solid var(--focus); outline-offset: 2px; }
.tt-presets { margin-top: .85rem; }
.tt-presets .chip { font-family: var(--body); cursor: pointer; }
.tt-presets + .spell-hint { margin-top: .6rem; }
.tt-upto { margin-top: 1.25rem; }

/* times tables: a line under the title saying which tables */
.sheet-sub { margin: -.7rem 0 1.3rem; color: var(--ink-soft); font-size: .92rem; }

/* times tables: the grid. The whole square is 13 across, which fits the
   printed page; a phone scrolls it sideways instead. */
.tt-gridwrap { overflow-x: auto; }
.tt-grid { border-collapse: collapse; margin: 0 auto; font-family: var(--hand); break-inside: avoid; }
.tt-grid th, .tt-grid td {
  width: 2.5em; height: 2.5em; padding: 0; text-align: center;
  border: 1px solid var(--rose-soft); font-size: 1.05rem;
}
.tt-grid th { background: var(--rose-wash); color: var(--maroon); font-family: var(--body); font-size: .85rem; font-weight: 700; }
.is-answer .tt-grid__blank { color: var(--maroon); font-weight: 700; }

/* times tables: arrays. Each card keeps its total at the bottom, so the boxes
   line up along a row whatever the size of the pictures above them. */
.tt-arrays { display: grid; grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr)); gap: 1.4rem 1.6rem; }
.tt-array { display: flex; flex-direction: column; align-items: center; text-align: center; break-inside: avoid; }
.tt-array__num { align-self: flex-start; font-size: .8rem; color: var(--ink-soft); }
.tt-array__label { margin: 0 0 .5rem; font-size: .85rem; color: var(--ink-soft); }
.tt-array__dots { display: inline-grid; gap: 4px; margin-bottom: .7rem; }
/* The dots are nothing but a background colour, which a printer leaves out
   unless it is told otherwise. */
.tt-dot {
  width: 11px; height: 11px; border-radius: 50%; background: var(--teal);
  -webkit-print-color-adjust: exact; print-color-adjust: exact;
}
.tt-dot--square { border-radius: 2px; }
.tt-array__total { margin-top: auto; font-family: var(--hand); font-size: 1.3rem; }

/* times tables: number chains. The starting number is given, so it is boxed
   in rose and stays that way on the answer page; the boxes to fill in are the
   same as everywhere else. */
.tt-chain {
  display: flex; flex-wrap: wrap; align-items: center; gap: .5rem .45rem;
  padding: .75rem 0; border-bottom: 1px dotted #d9cbc8; break-inside: avoid;
  font-family: var(--hand); font-size: clamp(1.15rem, 2.4vw, 1.35rem);
}
.tt-chain .sheet-num { font-family: var(--body); }
.tt-chain__step { display: inline-flex; align-items: center; gap: .45rem; }
.tt-chain__start {
  display: inline-block; min-width: 1.5em; height: 1.35em; line-height: 1.3em; text-align: center;
  border: 1.5px solid var(--rose); border-radius: 3px; font-weight: 700;
}
.tt-chain__op {
  font-family: var(--body); font-size: .8rem; font-weight: 700; white-space: nowrap;
  color: var(--teal-deep); border: 1px solid var(--teal); border-radius: 999px; padding: .1rem .6rem;
}
.tt-chain__arrow { font-family: var(--body); font-size: .9rem; color: var(--ink-soft); }

/* times table reference: a card per table. On a page of its own a card is
   printed large, to be read from across a kitchen. */
.ttr-cards { display: grid; gap: 1.4rem 1.25rem; }
@media (max-width: 40rem) { .ttr-cards { grid-template-columns: 1fr !important; } }
.ttr-card { border: 1.5px solid var(--rose-soft); border-radius: var(--radius-sm); padding: 1rem 1.1rem 1.1rem; break-inside: avoid; }
.ttr-card__title { margin: 0 0 .7rem; text-align: center; font-size: 1.05rem; color: var(--maroon); }
.ttr-row {
  display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: .1rem 1rem;
  padding: .3rem 0; border-bottom: 1px dotted #d9cbc8; font-family: var(--hand); font-size: 1.1rem;
}
.ttr-row:last-child { border-bottom: 0; }
.ttr-row__div { color: var(--teal-deep); font-size: .92em; }
.ttr-card--page { max-width: 30rem; margin: 0 auto; padding: 1.4rem 1.8rem 1.6rem; }
.ttr-card--page .ttr-card__title { font-size: 1.5rem; margin-bottom: 1rem; }
.ttr-card--page .ttr-row { font-size: 1.65rem; padding: .45rem 0; }

/* hundred square. Ruled with borders rather than a gap over a coloured
   background, because a printer drops backgrounds and the lines would go with
   them; the shading is told to print. */
.hs-square {
  display: grid; grid-template-columns: repeat(10, minmax(0, 1fr)); max-width: 34rem; margin: 0 auto;
  border-top: 1px solid var(--rose); border-left: 1px solid var(--rose);
  font-family: var(--hand); break-inside: avoid;
}
.hs-cell {
  display: flex; align-items: center; justify-content: center; aspect-ratio: 1 / 1;
  border-right: 1px solid var(--rose); border-bottom: 1px solid var(--rose);
  font-size: clamp(.85rem, 2.4vw, 1.2rem); font-variant-numeric: tabular-nums;
}
.hs-cell--shade { background: var(--mustard-soft); -webkit-print-color-adjust: exact; print-color-adjust: exact; }
.is-answer .hs-cell--blank { color: var(--maroon); font-weight: 700; }

/* number lines. The line and its marks are an <svg>; the numbers sit under it
   in HTML, each centred on its mark, and are sized by the script against the
   line's own width (cqw) so a crowded line shrinks rather than overlapping. */
.nl-range { display: grid; grid-template-columns: 1fr 1fr; gap: .85rem; }
.nl-lines { display: grid; gap: clamp(1.6rem, 4vw, 2.4rem); }
.nl { container-type: inline-size; break-inside: avoid; }
.nl svg { display: block; width: 100%; height: auto; overflow: visible; }
.nl__axis { stroke: var(--teal); stroke-width: 1.6; fill: none; stroke-linecap: round; stroke-linejoin: round; }
.nl__tick { stroke: var(--teal); stroke-width: 1.6; }
.nl__nums { position: relative; height: 1.7em; margin-top: .2em; font-family: var(--hand); font-size: 15px; }
.nl__num { position: absolute; top: 0; transform: translateX(-50%); line-height: 1.3; white-space: nowrap; text-align: center; }
.nl__num--gap { width: 1.5em; height: 1.35em; border: 1.5px solid var(--teal); border-radius: 3px; }
.is-answer .nl__num--gap { border-color: var(--maroon); color: var(--maroon); font-weight: 700; }

/* fraction wall. A row a colour, from the site's own palette, six to a cycle
   so neighbours never match; the dark ones take white figures, the light ones
   ink. Black and white swaps the fills for outlines, which a printer keeps. */
.fw-wall { display: grid; gap: 3px; break-inside: avoid; }
.fw-row { display: flex; align-items: stretch; gap: .6rem; }
.fw-label {
  flex: none; width: 2.6rem; display: flex; align-items: center; justify-content: flex-end;
  font-size: .85rem; font-weight: 700; color: var(--ink-soft); font-variant-numeric: tabular-nums;
}
.fw-bar { flex: 1; display: flex; gap: 3px; min-width: 0; }
.fw-piece {
  flex: 1 1 0; min-width: 0; height: 2.6rem; display: flex; align-items: center; justify-content: center;
  border-radius: 3px; font-size: clamp(.62rem, 1.7vw, .9rem); font-weight: 700; overflow: hidden;
  -webkit-print-color-adjust: exact; print-color-adjust: exact;
}
.fw-row:nth-child(6n+1) .fw-piece { background: var(--maroon); color: #fff; }
.fw-row:nth-child(6n+2) .fw-piece { background: var(--teal); color: #fff; }
.fw-row:nth-child(6n+3) .fw-piece { background: var(--mustard); color: var(--ink); }
.fw-row:nth-child(6n+4) .fw-piece { background: var(--rose); color: var(--ink); }
.fw-row:nth-child(6n+5) .fw-piece { background: var(--teal-deep); color: #fff; }
.fw-row:nth-child(6n+6) .fw-piece { background: var(--mustard-soft); color: var(--ink); }
.fw-wall--bw .fw-row .fw-piece { background: #fff; color: var(--ink); border: 1.5px solid var(--ink); }

/* Kept whole across a page break, by the printer and by the Download PDF
   button alike -- the button reads this same rule, so a new sheet only has to
   say it once. (.sheet-bond and .sheet-pw say it where they are defined.) */
.sheet-head, .sheet-row, .sheet-grid, .sheet-bank, .sheet-wslist { break-inside: avoid; }

/* The Download PDF button lays the sheet out in a hidden, page-wide frame with
   this class on its body (see home-learning.js). What differs from the screen
   is only what differs in print: each part starts its own page, so the answers
   need no rule above them. */
.sheet-pdf { background: #fff; }
.sheet-pdf .sheet-answers, .sheet-pdf .sheet-newpage { margin-top: 0; padding-top: 0; border-top: 0; }

/* Printed, it is the sheet and nothing else, with the answers on a page of
   their own so they can be kept back. */
@media print {
  .sheet-frame--next { break-before: page; }
  .sheet-frame--next > thead { display: table-header-group; }
  .sheet-brand, .sheet-foot { break-inside: avoid; }
  .spell-page .page-head, .maths-nav, .spell__controls, .spell-actions, .spell-page noscript { display: none !important; }
  .spell-page .section { padding: 0; }
  .spell-page .wrap { max-width: none; padding: 0; }
  .spell__layout { display: block; }
  .spell-paper { border: 0; border-radius: 0; box-shadow: none; padding: 0; min-height: 0; }
  .sheet-row { break-inside: avoid; }
  .sheet-answers, .sheet-newpage { margin-top: 0; padding-top: 0; border-top: 0; }
  .sheet-cell--hit { background: none; box-shadow: inset 0 0 0 1.5px var(--maroon); }
  .tt-gridwrap { overflow: visible; }
}

/* ---------- a school's own logo ---------- */

/* An uploaded logo is an <img> in the places the arch mark sat. It keeps the
   height the arch had there and takes whatever width its own shape needs. */
img.mark--logo { width: auto !important; max-width: 10rem; object-fit: contain; }
.mark--tile { background: #fff; border-radius: 8px; padding: 3px; box-sizing: border-box; }
.site-header img.mark--logo { height: 44px; }
@media (max-width: 480px) { .site-header img.mark--logo { height: 36px; max-width: 4.5rem; } }
.site-footer img.mark--logo { height: 48px; }

/* The hero arch and the photo arches keep their curved tops whatever logo a
   school uploads. They used to flatten into rounded panels once a logo was
   set, which took the rainbow off the home page. */

/* ---------- event tickets ---------- */

/* auto-fill, not auto-fit: with only one or two events on sale, auto-fit let
   the cards stretch across the whole page and blew the picture up to banner
   size. auto-fill keeps the empty columns, so a card stays a card -- about a
   third of the page on a computer, the full width on a phone. */
.tevent-cards { grid-template-columns: repeat(auto-fill, minmax(17rem, 1fr)); }
.tevent-card { padding: 0; overflow: hidden; }
.tevent-card__body { padding: 1.25rem 1.4rem 1.4rem; }
.tevent-card__body h2 { font-size: 1.3rem; margin-bottom: .35rem; }
/* The whole picture, never cropped: a poster with the date along its edge is
   no use with the edge cut off. It sits inside the same 16:9 box, so the cards
   still line up, and any space left round it takes the page's soft background. */
.tevent-card__photo img { width: 100%; aspect-ratio: 16 / 9; object-fit: contain; background: var(--paper); }
/* The event page: picture and details side by side on a wide screen, stacked
   on a phone. The picture is capped in height either way -- it is there to set
   the scene, not to push the booking form below the fold. */
/* minmax(0, ...) everywhere: a plain grid column grows to fit its widest
   content, and the photo's natural width pushed a phone's page sideways. */
.tevent { display: grid; grid-template-columns: minmax(0, 1fr); gap: 1.5rem 2.5rem; margin-bottom: 2rem; }
/* The whole picture, scaled down to fit -- never cropped. A tall one is limited
   by height and sits centred in its space. */
.tevent__media img { display: block; width: auto; max-width: 100%; height: auto; max-height: 15rem; margin-inline: auto; border-radius: var(--radius); }
.tevent__about > :first-child { margin-top: 0; }
.tevent__about .prose { max-width: 40rem; }
@media (min-width: 56rem) {
  .tevent {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr); align-items: start;
    grid-template-areas: "media about" "sessions prices";
  }
  .tevent--nophoto { grid-template-areas: "about about" "sessions prices"; }
  .tevent__media { grid-area: media; }
  .tevent__about { grid-area: about; }
  .tevent__sessions { grid-area: sessions; }
  .tevent__prices { grid-area: prices; }
  .tevent__prices .tevent__groups { margin-top: 0; }
  .tevent__media img { max-height: 24rem; }
}
.tevent__booking { border-top: 1px solid var(--line); padding-top: .5rem; }

/* The booking form. One column on a phone. On a wide screen every section --
   each child, You, Emergency contact, Donation, Consent -- runs the full width
   of the form, one under another, with its own fields spread along the line
   rather than stacked; the summary and Buy Now ride along on the right. */
.ticket-form { display: grid; grid-template-columns: minmax(0, 1fr); gap: 0 2.5rem; }
.tchildren { grid-template-columns: minmax(0, 1fr); }
.tchild__names, .tblock__row { display: grid; gap: 0 1.1rem; grid-template-columns: minmax(0, 1fr) 7rem; }
.tblock__row { grid-template-columns: minmax(0, 1fr); }
.tdonation { display: flex; flex-wrap: wrap; align-items: end; gap: .75rem 2rem; }
@media (min-width: 40rem) {
  .tblock__row { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (min-width: 48rem) {
  .tblock__row--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  /* A child's card: name and initial beside the group, allergies beside the extras. */
  .tchild { grid-template-columns: minmax(0, 3fr) minmax(0, 2fr); gap: 1rem 1.75rem; align-items: start; }
  .tchild__remove, .tchild__price { grid-column: 1 / -1; }
}
@media (min-width: 64rem) {
  .ticket-form { grid-template-columns: minmax(0, 1fr) 19rem; align-items: start; }
  .ticket-form__side { position: sticky; top: 1.25rem; padding-top: 1.5rem; }
}
.ticket-form__side .summary-box { margin-top: 0; }
.ticket-form__side .form-actions .btn { width: 100%; justify-content: center; }
.tevent__facts {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(9.5rem, 1fr)); gap: .75rem;
  margin: 0 0 1.5rem; padding: 0;
}
.tfact {
  background: var(--card); border: 1px solid var(--line); border-left: 4px solid var(--teal);
  border-radius: var(--radius-sm); padding: .65rem .85rem;
}
.tfact--date { border-left-color: var(--maroon); }
.tfact dt { font-size: .72rem; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--ink-soft); margin: 0 0 .2rem; }
.tfact dd { margin: 0; font-weight: 700; font-size: 1.15rem; line-height: 1.35; color: var(--ink); }

/* The sessions timetable: time on the left in a coloured tab, then the
   session and its groups. Rows stack, so two sessions read top to bottom in
   the order they happen. */
.tsessions { margin: 0 0 1.5rem; }
.tsessions__title { font-size: .72rem; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--ink-soft); margin: 0 0 .5rem; font-family: inherit; }
.tsessions__list { list-style: none; margin: 0; padding: 0; display: grid; gap: .6rem; }
.tsession {
  display: grid; grid-template-columns: 7.5rem minmax(0, 1fr); align-items: stretch;
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius-sm); overflow: hidden;
}
.tsession__time {
  display: flex; flex-direction: column; justify-content: center; gap: .1rem;
  padding: .6rem .8rem; background: var(--mustard-wash); border-right: 4px solid var(--mustard); color: var(--ink);
}
.tsession__time strong { font-size: 1.15rem; line-height: 1.1; }
.tsession__time span { font-size: .82rem; font-weight: 600; }
.tsession__what { padding: .6rem .9rem; display: flex; flex-direction: column; justify-content: center; gap: .35rem; }
.tsession__what > strong { font-size: 1.05rem; }
.tsession__groups { display: flex; flex-wrap: wrap; gap: .3rem; }
.tsession__group {
  font-size: .78rem; font-weight: 600; padding: .12rem .55rem; border-radius: 999px;
  background: var(--teal-wash, #e3efef); color: var(--teal-deep, #1f4f52);
}
.tevent__groups { margin: 1.25rem 0 1.75rem; }

.ticket-form h2 { margin-top: 1.5rem; }
.tchildren { display: grid; gap: 1.25rem; }
.tchild, .tblock {
  border: 1px solid var(--line); border-radius: var(--radius); background: var(--card);
  padding: 1rem 1.25rem 1.1rem; margin: 0 0 1.25rem; display: grid; gap: .9rem;
}
.tchild[hidden], .tchild__extra[hidden], .tchild__extras[hidden] { display: none; }
.tchild > legend, .tblock > legend { font-weight: 700; padding-inline: .35rem; }
.tchild__remove { justify-self: end; margin-top: -.25rem; }
.tchild__allergy, .tchild__extras { border: 0; padding: 0; margin: 0; display: grid; gap: .5rem; }
.tchild__allergy > legend, .tchild__extras > legend { font-weight: 600; padding: 0; margin-bottom: .35rem; }
.tchild__price { margin: 0; font-weight: 700; }
.tchild__price:empty { display: none; }
.choice-row { display: flex; flex-wrap: wrap; gap: .4rem 1.25rem; }
.field--short { max-width: 10rem; }
.input--initial { width: 4rem; text-transform: uppercase; }
.input--small { width: 7rem; }
.input--tiny { width: 4.5rem; }
.ticket-summary ul { margin: 0 0 .5rem; }
.ticket-no { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; white-space: nowrap; }

.tsetup h2 { margin-top: 1.75rem; }
.tsetup h2:first-of-type { margin-top: 0; }
.tsetup td input[type="text"] { min-width: 10rem; }
.tsetup__groups { min-width: 12rem; }
.tsetup__groups .checkbox { margin: 0 0 .2rem; font-size: .9rem; }

/* The door register. Allergies are the one thing nobody on the door may miss. */
.register { margin-bottom: 1.5rem; }
.register__session { margin-top: 1.5rem; }
.register__group { font-size: 1.05rem; margin: 1rem 0 .4rem; }
.register__allergy { background: #fff1c2; font-weight: 700; color: #7a2400; }
.register__tick { width: 2.25rem; text-align: center; }
.register__box { display: inline-block; width: 1.1rem; height: 1.1rem; border: 2px solid currentColor; border-radius: 3px; }

@media print {
  .admin-header, .no-print { display: none !important; }
  .register { font-size: 10pt; }
  .register th, .register td { padding: 4px 6px; }
  .register tr, .register__group { break-inside: avoid; }
  .register__session { break-before: page; }
  .register__session:first-of-type { break-before: auto; }
  .register__allergy { background: #ddd !important; -webkit-print-color-adjust: exact; print-color-adjust: exact; }
}
.tsetup__namebox { display: flex; align-items: center; gap: .6rem; }
.tsetup__number { flex: none; font-weight: 700; white-space: nowrap; }
/* A fieldset will not shrink below its content unless told to, which pushes a
   phone's page sideways. */
.tchild, .tblock { min-width: 0; }
.tchildren .tchild { margin: 0; }
.tcopy { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem .9rem; font-weight: 600; }
/* The prices table is four short columns, so on the event page it fits a phone
   rather than scrolling sideways like the dashboard's wide tables. */
.tevent .table-scroll { margin-inline: 0; padding-inline: 0; }
.tevent__groups { min-width: 0; }
@media (max-width: 30rem) {
  .tevent__groups th, .tevent__groups td { padding-inline: .35rem; }
  .tevent__groups td .field__help { display: block; }
}
.tallergy__details[hidden], .tchild__allergy[hidden], .tallergy__note[hidden] { display: none; }
.event__tickets { margin: .75rem 0 0; }
.event__tickets--note { font-weight: 600; }
.tkind { border: 0; padding: 0; margin: 0; grid-column: 1 / -1; }
.tkind > legend { font-weight: 600; padding: 0; margin-bottom: .35rem; }

/* ---------- the demo site ---------- */
/* Only ever drawn on the demo (DEMO_MODE in .env): nothing else puts these
   classes on a page. */

.demo-bar { background: var(--mustard-wash); color: var(--ink); border-bottom: 1px solid var(--mustard-soft); font-size: .9rem; line-height: 1.4; padding-block: .55rem; }

/* Where SumUp's page would have opened. A native popover, so no script. */
.demo-modal { width: min(32rem, calc(100vw - 2rem)); max-height: calc(100vh - 2rem); overflow: auto; margin: auto; padding: 1.75rem 1.75rem 1.25rem; border: 0; border-top: 6px solid var(--mustard); border-radius: var(--radius-sm, 10px); background: #fff; color: var(--ink); box-shadow: 0 24px 60px rgba(0,0,0,.28); }
.demo-modal::backdrop { background: rgba(20,20,20,.55); }
.demo-modal h2 { margin-top: 0; font-size: 1.35rem; }
.demo-modal .btn-row { margin: 1.25rem 0 0; }

/* The one-click login box, above the normal login forms. */
.demo-login { background: var(--mustard-wash); border-left: 4px solid var(--mustard); border-radius: var(--radius-sm, 10px); padding: 1rem 1.15rem; margin-bottom: 1.5rem; }
.demo-login p { margin: 0 0 .75rem; }
.login-card .demo-login { padding: .85rem .9rem; font-size: .9rem; line-height: 1.45; }
.login-card .demo-login .btn { padding-inline: .75rem; font-size: .9rem; }
