/* ==========================================================================
   MDV Kids — the classroom screen.
   ==========================================================================

   The same family as Hub and Services: one dark ground, one accent, greys that
   were chosen rather than inherited. The kid twist is in the SHAPE and the
   MOTION, not in a different palette — squircles instead of rounded rectangles,
   things that arrive with a bounce, a tick that draws itself. A children's
   screen that reads as a different product from the rest of the church's
   software is a children's screen that looks like a toy.

   Dark-grounded on every season, deliberately. This is a television in a lit
   room, several metres from the audience: light grounds glare, and the seasonal
   colour has more room to speak against dark.

   TWO RULES FROM THE MEMBERS APP'S SEASONS, LEARNED EXPENSIVELY:

   1. A season is a FULL-HEIGHT gradient, not a tint at the top stop. Christmas
      as pine-over-silver measured a large colour difference at the anchor and
      still looked exactly like spring, because two thirds of the screen was
      unchanged. The eye takes the average.
   2. Christmas is red INTO green. The accent stays green because --danger is
      red, and an accent that matches the danger colour makes every confirm
      button look like a delete button.
   ========================================================================== */

:root {
  /* Ground: two stops, full height. Seasons replace both. */
  --sky-top: #101418;
  --sky-bottom: #06080a;

  --accent: #4ade80;
  --accent-ink: #06140c;
  --accent-wash: rgb(74 222 128 / 0.14);

  --surface: rgb(255 255 255 / 0.06);
  --surface-2: rgb(255 255 255 / 0.10);
  --hairline: rgb(255 255 255 / 0.12);

  --ink: #f4f4f5;
  --ink-soft: #b6bcc2;
  --ink-faint: #7c848c;

  --ok: #4ade80;
  --warn: #fbbf24;
  --danger: #f87171;

  /* The kid twist, as numbers. Bigger radii and a springier curve than the
     staff-facing apps use — everything else is shared. */
  --squircle: 28px;
  --squircle-sm: 18px;
  --bounce: cubic-bezier(0.34, 1.56, 0.64, 1);
  --ease: cubic-bezier(0.22, 1, 0.36, 1);
}

/* --------------------------------------------------------------------------
   Seasons. Set by the server from the date — see Domain/Kids/Season.php.
   Easter is deliberately absent; this church does not observe it.
   -------------------------------------------------------------------------- */

[data-season='fall'] {
  --sky-top: #3a1f0c;
  --sky-bottom: #0d0704;
  --accent: #fb9b3c;
  --accent-ink: #24120a;
  --accent-wash: rgb(251 155 60 / 0.16);
  --ink-soft: #d0bda8;
  --ink-faint: #93826f;
}

/* Red INTO green, the whole height. See rule 2 above. */
[data-season='christmas'] {
  --sky-top: #4a1116;
  --sky-bottom: #04140d;
  --accent: #5fd18a;
  --accent-ink: #05170d;
  --accent-wash: rgb(95 209 138 / 0.16);
  --ink-soft: #cfc4c2;
  --ink-faint: #94847f;
}

[data-season='spring'] {
  --sky-top: #16341c;
  --sky-bottom: #060d07;
  --accent: #b6e34a;
  --accent-ink: #16200a;
  --accent-wash: rgb(182 227 74 / 0.16);
  --ink-soft: #c3cfb6;
  --ink-faint: #85907a;
}

/* --------------------------------------------------------------------------
   Stage
   -------------------------------------------------------------------------- */

* { box-sizing: border-box; }

html, body {
  height: 100%;
  margin: 0;
}

body {
  font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
  color: var(--ink);
  background: linear-gradient(178deg, var(--sky-top) 0%, var(--sky-bottom) 100%);
  /* The gradient is on the body and the body is the full height, so the season
     covers the screen rather than the top of it. */
  background-attachment: fixed;
  overflow: hidden;
  -webkit-font-smoothing: antialiased;
}

/*
 * Every screen is `position: fixed; inset: 0` — so `stage.clientHeight` is the
 * REAL visible height whatever `vh` believes. The foyer iPad taught this the
 * hard way: iOS resolves vh against the chrome-hidden viewport, which is taller
 * than the screen, and centred content spills off both ends.
 */
.stage {
  position: fixed;
  inset: 0;
  display: grid;
  place-items: center;
  padding: clamp(20px, 4vmin, 56px);
}

/* --------------------------------------------------------------------------
   Preflight — what the room sees before the system starts
   -------------------------------------------------------------------------- */

.pf {
  width: min(1500px, 94vw);
  display: grid;
  gap: clamp(18px, 3vmin, 34px);
  justify-items: center;
}

.pf-head {
  display: grid;
  justify-items: center;
  gap: 6px;
  text-align: center;
}

.pf-mark {
  width: clamp(56px, 8vmin, 88px);
  height: clamp(56px, 8vmin, 88px);
  display: grid;
  place-items: center;
  border-radius: var(--squircle);
  background: var(--accent);
  color: var(--accent-ink);
  font-weight: 900;
  font-size: clamp(24px, 3.6vmin, 38px);
  letter-spacing: -0.04em;
  margin-bottom: 8px;
  animation: pf-pop 620ms var(--bounce) both;
}

.pf-title {
  margin: 0;
  font-size: clamp(28px, 4.4vmin, 52px);
  font-weight: 800;
  letter-spacing: -0.035em;
}

.pf-sub {
  margin: 0;
  color: var(--ink-soft);
  font-size: clamp(14px, 1.9vmin, 20px);
}

/*
 * The module list. Three columns on a television, fewer on anything narrower.
 *
 * Capped at three explicitly rather than left to auto-fit: a 1920 screen would
 * otherwise take five, and a five-column grid of short labels reads as a wall
 * of text rather than a list somebody scans from across the room.
 */
.pf-list {
  width: 100%;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: clamp(8px, 1.4vmin, 14px);
  list-style: none;
  margin: 0;
  padding: 0;
}

@media (min-width: 1200px) {
  .pf-list { grid-template-columns: repeat(3, 1fr); }
}

/*
 * One module, centred.
 *
 * The "could not reach the server" case renders a single card, and a lone card
 * in the first cell of a three-column grid reads as a layout that broke rather
 * than the whole of the message. Degrades to the left-hand column where :has
 * is unsupported, which is untidy but not wrong.
 */
.pf-list:has(> li:only-child) {
  grid-template-columns: minmax(320px, 620px);
  justify-content: center;
}

.pf-item {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: clamp(12px, 1.8vmin, 18px) clamp(14px, 2vmin, 20px);
  border-radius: var(--squircle-sm);
  background: var(--surface);
  border: 1px solid var(--hairline);
  /* Arrives from below as its check starts, so the list builds rather than
     appearing whole. The delay is set inline, per item. */
  animation: pf-rise 420ms var(--ease) both;
}

.pf-item[data-state='ok']     { border-color: rgb(74 222 128 / 0.36); }
.pf-item[data-state='warn']   { border-color: rgb(251 191 36 / 0.40); }
.pf-item[data-state='fail']   { border-color: rgb(248 113 113 / 0.46); background: rgb(248 113 113 / 0.07); }

.pf-name {
  flex: 1;
  min-width: 0;
  font-size: clamp(15px, 2vmin, 21px);
  font-weight: 600;
}

.pf-note {
  display: block;
  margin-top: 2px;
  font-size: clamp(12px, 1.5vmin, 15px);
  font-weight: 400;
  color: var(--ink-faint);
  /* One line. A check that needs a paragraph is a check that should be two. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.pf-item[data-state='fail'] .pf-note { color: var(--danger); }
.pf-item[data-state='warn'] .pf-note { color: var(--warn); }

/* The status dot. Spins while checking, becomes a tick that draws itself. */
.pf-dot {
  width: clamp(26px, 3.4vmin, 34px);
  height: clamp(26px, 3.4vmin, 34px);
  flex: none;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--surface-2);
  color: var(--ink-faint);
}

.pf-item[data-state='checking'] .pf-dot {
  border: 2px solid var(--hairline);
  border-top-color: var(--accent);
  background: none;
  animation: pf-spin 780ms linear infinite;
}

.pf-item[data-state='ok']   .pf-dot { background: var(--ok);     color: var(--accent-ink); }
.pf-item[data-state='warn'] .pf-dot { background: var(--warn);   color: #241a02; }
.pf-item[data-state='fail'] .pf-dot { background: var(--danger); color: #240808; }

.pf-dot svg { width: 62%; height: 62%; }
.pf-dot path {
  stroke: currentColor;
  stroke-width: 3.2;
  stroke-linecap: round;
  stroke-linejoin: round;
  fill: none;
  /* Drawn, not popped. 24 is comfortably longer than either path. */
  stroke-dasharray: 24;
  stroke-dashoffset: 24;
}
.pf-item[data-state='ok'] .pf-dot path,
.pf-item[data-state='warn'] .pf-dot path,
.pf-item[data-state='fail'] .pf-dot path {
  animation: pf-draw 340ms var(--ease) 60ms forwards;
}

/* The foot: what happens next. */
.pf-foot {
  display: flex;
  align-items: center;
  gap: 14px;
  min-height: 48px;
  padding: 12px 22px;
  border-radius: 999px;
  background: var(--surface);
  border: 1px solid var(--hairline);
  font-size: clamp(14px, 1.9vmin, 19px);
  color: var(--ink-soft);
}

.pf-foot[data-state='ready'] {
  background: var(--accent-wash);
  border-color: color-mix(in srgb, var(--accent) 45%, transparent);
  color: var(--ink);
}

.pf-foot[data-state='held'] {
  background: rgb(248 113 113 / 0.10);
  border-color: rgb(248 113 113 / 0.45);
  color: var(--ink);
}

.pf-count {
  font-variant-numeric: tabular-nums;
  font-weight: 800;
  color: var(--accent);
}

/*
 * The number reads before the words when it is a count of problems ("2 modules
 * with problems") and after them when it is a countdown ("starting in 3").
 * Same two elements, ordered by state, rather than the JS rewriting the whole
 * sentence and the two versions drifting apart.
 */
.pf-foot[data-state='ready'] .pf-count { order: 2; }

.pf-foot[data-state='held'] .pf-count { color: var(--danger); }

/*
 * Who to tell.
 *
 * Only when something has actually failed, and then it is the biggest thing on
 * the screen after the problem itself. Whoever is standing in that room on a
 * Sunday morning is usually a volunteer who cannot fix a database — the useful
 * instruction is not "check the logs", it is a name.
 *
 * Hidden the rest of the time. A permanent "call for help" line is furniture
 * that stops being read long before the day it matters.
 */
.pf-help {
  display: none;
  align-items: center;
  gap: 12px;
  padding: clamp(14px, 2vmin, 20px) clamp(18px, 2.6vmin, 28px);
  border-radius: var(--squircle-sm);
  background: rgb(248 113 113 / 0.12);
  border: 1px solid rgb(248 113 113 / 0.4);
  text-align: left;
}

.stage[data-held='1'] .pf-help { display: flex; }

.pf-help-icon {
  width: clamp(30px, 4vmin, 40px);
  height: clamp(30px, 4vmin, 40px);
  flex: none;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--danger);
  color: #240808;
  font-weight: 900;
  font-size: clamp(17px, 2.4vmin, 23px);
}

.pf-help-who {
  display: block;
  font-size: clamp(19px, 2.7vmin, 30px);
  font-weight: 800;
  letter-spacing: -0.02em;
}

.pf-help-note {
  display: block;
  margin-top: 2px;
  color: var(--ink-soft);
  font-size: clamp(13px, 1.7vmin, 17px);
}

/* --------------------------------------------------------------------------
   Handover — the preflight leaves, the classroom screen arrives
   -------------------------------------------------------------------------- */

.stage[data-leaving='1'] .pf {
  animation: pf-away 460ms var(--ease) forwards;
}

@keyframes pf-pop  { from { transform: scale(0.6) rotate(-8deg); opacity: 0; } }
@keyframes pf-rise { from { transform: translateY(10px);         opacity: 0; } }
@keyframes pf-spin { to   { transform: rotate(360deg); } }
@keyframes pf-draw { to   { stroke-dashoffset: 0; } }
@keyframes pf-away { to   { transform: scale(0.94) translateY(-12px); opacity: 0; } }

/*
 * Motion is decoration here — every state is also carried by colour, the dot's
 * shape and the text. Turning it all off costs nothing.
 */
@media (prefers-reduced-motion: reduce) {
  .pf-mark, .pf-item, .pf-dot, .pf-dot path, .stage[data-leaving='1'] .pf {
    animation: none !important;
  }
  .pf-dot path { stroke-dashoffset: 0; }
}
