/* Two apps out of one stylesheet (§17). `data-app` is set from the URL prefix
   before this file paints, so every color below is a token and no component
   rule names a color of its own. That is what makes the kid theme picker a
   later token block rather than a second pass over every rule.

   Both apps commit to a light ground. There is no prefers-color-scheme block:
   an identity the OS can repaint is not an identity, and two apps that both
   turn the same gray in dark mode are the thing the co-op asked us to fix. */

:root {
  color-scheme: light;

  /* Structure. Shared by both apps — the difference between them is color,
     scale and where the navigation sits, not what a card is. */
  --r-sm: 6px;
  --r-md: 10px;
  --r-lg: 16px;
  --pad: 1rem;
  --sans: "Inter", "Helvetica Neue", Helvetica, Arial, system-ui, sans-serif;
  --mono: ui-monospace, "SF Mono", "Roboto Mono", Menlo, monospace;

  /* Semantic color is constant across both apps and, later, across every kid
     theme. A theme may repaint the ground and the accent; it may never repaint
     a warning. A held message has to read as held in Mint and in Lavender. */
  --bad: #a4271f;
  --good: #14602c;
  --warn: #8a5a12;
}

/* MCCE Parents — deep indigo off the logo, a cool ground, white cards, and
   density. This is a queue read on a phone between other things. */
:root, [data-app="parents"] {
  --brand: #2e2a6e;
  --brand-ink: #ffffff;
  --accent: #3c62a8;
  --ground: #f1f2f6;
  --surface: #ffffff;
  --ink: #1a1c2a;
  --quiet: #5b6072;
  --line: #1a1c2a1f;
  --base: 16px;
  --radius: var(--r-md);
  --h1: 1.65rem;
}

/* MCCE Kids — a warm ground, a brighter blue, larger everything. Same family
   of color as the parents app, deliberately: these are two apps from one
   co-op, not two products. The difference a kid feels is the warmth, the
   scale, and the tabs under their thumb. */
[data-app="kids"] {
  --brand: #1f4e8c;
  --brand-ink: #ffffff;
  --accent: #2f6db5;
  --ground: #fdf3e7;
  --surface: #ffffff;
  --ink: #173254;
  --quiet: #5d6b80;
  --line: #17325424;
  --base: 17px;
  --radius: var(--r-lg);
  --h1: 1.4rem;
}

/* MCCE Guests — the check-in at the door, the one screen in this app a person
   who is not in the co-op ever sees (§16). The mid blue off the logo's outer
   ring, on the logo's own off-white: it is recognizably the same co-op as the
   other two apps and recognizably not either of them, which is what a visitor
   holding a phone at a welcome table needs. Larger than MCCE Parents
   throughout, because it is read once, standing up, by somebody who has never
   seen it before. */
[data-app="guests"] {
  --brand: #4a7fb5;
  --brand-ink: #ffffff;
  --accent: #2e2a6e;
  --ground: #fafaf7;
  --surface: #ffffff;
  --ink: #1a1c2a;
  --quiet: #5b6072;
  --line: #1a1c2a26;
  --base: 17px;
  --radius: var(--r-md);
  --h1: 1.5rem;
}

/* MCCE Admin — the console under /parents/admin. Not a third identity so much
   as the same app with the lights up: a near-black band instead of indigo, a
   cooler ground, and an amber accent that appears nowhere in either app. The
   point is that a screenshot of the console is never mistaken for a screenshot
   of a parent's screen, which is the confusion the console exists to end. */
[data-admin] {
  --brand: #23272e;
  --brand-ink: #ffffff;
  --accent: #8f4f14;
  --ground: #e8eaee;
  --surface: #ffffff;
  --ink: #191c22;
  --quiet: #565d6b;
  --line: #191c2224;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  font: var(--base)/1.55 var(--sans);
  font-variant-numeric: tabular-nums;
  -webkit-text-size-adjust: 100%;
}

main {
  max-width: 44rem;
  margin: 0 auto;
  padding: 1.25rem var(--pad) 4rem;
}
/* Clear of the tab bar, which is pinned to the bottom of the viewport. The top
   is tight on purpose: a kid's screen is read on a 360x620 phone, the greeting
   and the tabs are already spending it, and every pixel above the first message
   is one the conversation does not get. */
[data-app="kids"] main { padding-top: .45rem; padding-bottom: 6.5rem; }

h1 { font-size: var(--h1); line-height: 1.2; margin: 1rem 0 .35rem; letter-spacing: -0.02em; }
[data-app="kids"] h1 { margin: .15rem 0 .3rem; }
h2 { font-size: 1.05rem; margin: 2rem 0 .6rem; }
h3 { font-size: .85rem; margin: 1.5rem 0 .35rem; color: var(--quiet); font-weight: 600; text-transform: uppercase; letter-spacing: .04em; }
p { margin: .55rem 0; }
a { color: var(--accent); text-underline-offset: .18em; }

/* The line under a heading that says what the screen is for. Every screen gets
   one — a kid should not have to tap something to find out what it does. */
.lede { color: var(--quiet); margin: 0 0 1.25rem; font-size: 1rem; }
[data-app="kids"] .lede { margin-bottom: .6rem; font-size: .95rem; }

.quiet { color: var(--quiet); font-size: .9rem; }
.mono { font-family: var(--mono); font-size: .85em; }
.bad { color: var(--bad); }
.good { color: var(--good); }

/* ---- MCCE Parents chrome: a brand bar across the top ---- */

.brandbar { background: var(--brand); color: var(--brand-ink); }
.brandbar-top {
  display: flex; gap: .75rem; align-items: center; flex-wrap: wrap;
  max-width: 44rem; margin: 0 auto; padding: .8rem var(--pad) .5rem;
}
/* The second row: who is holding the app, and what they can do about that.
   On one row the mark and five links shared a baseline and the phone wrapped
   them wherever it happened to run out. */
.brandbar-links {
  display: flex; gap: .75rem; align-items: baseline; flex-wrap: wrap;
  max-width: 44rem; margin: 0 auto; padding: 0 var(--pad) .6rem;
}
/* The mark at the far end of the first row, opposite the app's name. The band
   is the logo's own deep indigo, which is also the wordmark's, so it carries
   the off-white the mark was drawn on rather than disappearing into the bar. */
.brandbar-mark {
  height: 2.6rem; width: 2.6rem; flex: none; margin-left: auto;
  background: #fafaf7; border-radius: 999px;
}
.brandbar strong { font-size: 1.35rem; letter-spacing: -0.01em; }
.brandbar strong a { font-size: inherit; }
.brandbar .who { color: #ffffffb8; font-size: .85rem; }
.brandbar-top .who { margin-left: auto; }
.brandbar a { color: #ffffffd0; }
.brandbar-top a, .brandbar-links a { font-size: .85rem; }

/* ---- The drawer: every parents screen behind one button (§16) ---- */

/* The button sits first in the bar, before the brand, because it is the thing
   pressed on every screen and the brand is the thing pressed to leave one. */
.drawer-button {
  min-height: 2rem; padding: .25rem .6rem; font-size: .85rem;
  background: #ffffff26; color: var(--brand-ink); border: 0;
  border-radius: var(--r-sm); flex: none;
}
.drawer { position: fixed; inset: 0; z-index: 20; display: flex; }
/* The line the panel is shut with. An author `display` beats the browser's own
   `[hidden] { display: none }` — origin wins over specificity — so a rule that
   sets display on something the client hides with the property has to say so
   itself, or the thing is never hidden at all. */
.drawer[hidden] { display: none; }
.drawer-scrim { position: absolute; inset: 0; background: #0009; }
.drawer-panel {
  position: relative; display: flex; flex-direction: column;
  width: min(17rem, 82vw); height: 100%; overflow-y: auto;
  background: var(--surface); color: var(--ink);
  padding: .5rem 0 1.5rem; box-shadow: 0 0 2rem #00000040;
}
/* A group is a heading that is also the control opening it, so the whole strip
   is the target rather than a chevron beside one. */
.drawer-group {
  min-height: 2.9rem; padding: .5rem 1rem; border: 0; border-radius: 0;
  background: none; color: var(--quiet); text-align: left; font-size: .85rem;
  font-weight: 600; letter-spacing: .04em; text-transform: uppercase;
}
.drawer-group.on { color: var(--ink); }
.drawer-screens { display: flex; flex-direction: column; padding-bottom: .4rem; }
.drawer-screens a {
  padding: .6rem 1rem .6rem 1.5rem; font-size: 1rem; text-decoration: none;
  color: var(--ink); border-left: 3px solid transparent;
}
.drawer-screens a.on {
  border-left-color: var(--brand); background: var(--ground); font-weight: 600;
}

/* ---- MCCE Kids chrome: a greeting up top, tabs under the thumb ---- */

/* One line: who the app is, who is holding it, and the way to hand it over.
   It sits above every screen in the app, so anything it spends it spends
   everywhere — which is why it is a line rather than a masthead. It wraps
   rather than truncates: a long name is a second line, not an ellipsis. */
.greet { max-width: 44rem; margin: 0 auto; padding: .45rem var(--pad) .1rem; }
.eyebrow {
  margin: 0; font-size: 1rem; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--accent); flex: none;
}
.greet-row { display: flex; gap: .55rem; align-items: baseline; flex-wrap: wrap; }
.greet-name { margin: 0; font-size: 1.1rem; font-weight: 700; letter-spacing: -0.02em; }
.greet-row a { font-size: .85rem; }
/* The first of them takes the slack, so the pair sits together on the right
   rather than one at each end. */
.greet-row a:first-of-type { margin-left: auto; }
.greet-note { margin: .15rem 0 0; color: var(--quiet); font-size: .9rem; }
/* The mark on the kids door. No disc under it, unlike the parents bar: this
   header sits on the app's own ground rather than on the band. */
.door-mark { height: 2.6rem; width: 2.6rem; flex: none; margin-left: auto; }

.tabbar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 5;
  display: flex; background: var(--surface); border-top: 1px solid var(--line);
  padding-bottom: env(safe-area-inset-bottom);
}
.tabbar a {
  flex: 1; text-align: center; padding: .85rem .25rem 1rem;
  text-decoration: none; color: var(--quiet); font-size: .95rem; font-weight: 600;
  border-top: 3px solid transparent;
}
.tabbar a.on { color: var(--accent); border-top-color: var(--accent); }

/* ---- The kids door ---- */

.tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(8.5rem, 1fr)); gap: .85rem; margin: 1.25rem 0; }
.tile {
  display: flex; flex-direction: column; align-items: center; gap: .35rem;
  background: var(--surface); border: 2px solid var(--line); border-radius: var(--r-lg);
  padding: 1.25rem .75rem; cursor: pointer; color: inherit; font: inherit; min-height: 0;
}
.tile:hover, .tile:focus-visible { border-color: var(--accent); }
/* The kid whose session is already live. Marked, not removed: the door is a
   handover as well as a first tap, and backing out has to be one of the tiles. */
.tile.on { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 8%, var(--surface)); }
.tile-face {
  width: 3.25rem; height: 3.25rem; border-radius: 50%;
  background: var(--accent); color: #fff;
  display: grid; place-items: center; font-size: 1.5rem; font-weight: 700;
}
.tile-name { font-size: 1.1rem; font-weight: 700; }
.tile-note { font-size: .8rem; color: var(--quiet); }
.crosslink { margin-top: 2rem; }

/* ---- Forms and buttons ---- */

label { display: block; margin: .75rem 0 .25rem; font-size: .9rem; font-weight: 500; }
input, select, textarea {
  font: inherit; width: 100%; padding: .65rem .7rem; color: inherit;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-sm);
}
input:focus, select:focus, textarea:focus { border-color: var(--accent); }
/* A name the screen already knows: shown, not editable. It is a real field
   because a password manager matches on a username, and that match is what
   offers Face ID instead of a password typed in front of a child. */
input[readonly] { background: var(--ground); color: var(--quiet); cursor: default; }
input[type="checkbox"] { width: auto; margin-right: .5rem; }

button, a.button {
  font: inherit; font-weight: 600; padding: .7rem 1.15rem; min-height: 2.9rem;
  color: var(--brand-ink); background: var(--brand); border: 1px solid var(--brand);
  border-radius: var(--radius); cursor: pointer;
}
/* A link that opens something rather than doing something, given a button's
   weight because it is the action the modal it sits in was opened for. The one
   of these is the term report, which has to stay a link: the URL is the whole of
   the answer and a parent keeping it reprints the same document (D58). */
a.button { display: inline-flex; align-items: center; text-decoration: none; }
[data-app="kids"] button { padding: .85rem 1.5rem; min-height: 3.2rem; font-size: 1.02rem; }
button.plain { color: var(--ink); background: var(--surface); border-color: var(--line); font-weight: 500; }
button.small { min-height: 2.4rem; padding: .45rem .8rem; font-size: .85rem; border-radius: var(--r-sm); }
[data-app="kids"] button.small { min-height: 2.6rem; font-size: .9rem; }
button:disabled { opacity: .55; cursor: default; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ---- Surfaces ---- */

.card {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 1rem; margin: .85rem 0;
}
.row { display: flex; gap: .5rem; flex-wrap: wrap; align-items: center; }
/* A whole card that is one link — a hub in the hubs list. The tap target is the
   card rather than the words in it, which is the difference between a hub a kid
   can open on a phone and one they have to aim at. */
a.card { display: block; color: inherit; text-decoration: none; }
a.card:hover, a.card:focus-visible { border-color: var(--accent); }
.row > * { flex: 0 1 auto; }
.grow { flex: 1 1 8rem; }
.notice {
  border-left: 4px solid var(--accent); background: var(--surface);
  border-radius: 0 var(--r-sm) var(--r-sm) 0; padding: .7rem .9rem; margin: 1rem 0;
}
.notice.bad { border-left-color: var(--bad); color: var(--bad); }
.notice.good { border-left-color: var(--good); }
.empty { color: var(--quiet); }
ul.plain { list-style: none; padding: 0; margin: .5rem 0; }
ul.plain li { padding: .6rem 0; border-bottom: 1px solid var(--line); }

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

/* ---- The forum (§5) ---- */
/* Dense enough to read a conversation on a phone, and nothing that moves. A
   held or blocked message of your own is marked rather than hidden — a message
   that vanishes without a reason is how the forum loses a kid. */

.tabs { display: flex; gap: .35rem; flex-wrap: wrap; margin: 1rem 0 .75rem; }
[data-app="kids"] .tabs { margin: .45rem 0 .5rem; }
button.tab {
  color: var(--ink); background: var(--surface); border: 1px solid var(--line);
  border-radius: 999px; padding: .45rem .9rem; min-height: 2.4rem; font-weight: 500;
}
button.tab.on { color: var(--brand-ink); background: var(--brand); border-color: var(--brand); font-weight: 600; }
.badge { margin-left: .4rem; font-size: .75em; color: var(--quiet); }

/* Club badges (§6). A club is an identity a kid chose, so it gets a glyph they
   picked rather than a word the app abbreviated. Two at most beside a name
   (D8), and never the only carrier of meaning: the club screen always spells
   the name out. Old text labels still render, just smaller. */
.club-badge { margin-left: .25rem; font-size: 1.05em; line-height: 1; white-space: nowrap; }
.club-badge.text {
  font-family: var(--mono); font-size: .7rem; letter-spacing: .02em;
  border: 1px solid var(--line); border-radius: var(--r-sm);
  padding: .05rem .3rem; color: var(--quiet);
}

/* MCCE Kids: the second level of the forum, and the one way out of it.
   A channel is a row the width of the screen rather than a pill in a wrapping
   line — at six, a target you can miss is a target you stop using. */
.channel-row {
  display: flex; align-items: center; gap: .5rem; width: 100%; text-align: left;
  color: var(--ink); background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); padding: .75rem .85rem; margin: .4rem 0; font-weight: 600;
}
.channel-row:hover, .channel-row:focus-visible { border-color: var(--accent); }

/* A call to action a six-year-old can see from across the room. Rendered as
   either an <a> or a <button>, so it carries the button rules itself rather
   than inheriting half of them. */
.cta {
  display: inline-block; font: inherit; font-weight: 600; font-size: .95rem;
  padding: .4rem 1rem; min-height: 2.4rem; line-height: 1.4rem;
  color: var(--brand-ink); background: var(--accent); border: 1px solid var(--accent);
  border-radius: var(--radius); text-decoration: none; cursor: pointer;
}
.cta:hover, .cta:focus-visible { background: var(--brand); border-color: var(--brand); }
/* The way back out and the name of where you are. MCCE Kids puts them on one
   line: two lines of navigation over a conversation is a message's worth of
   height on every channel a kid opens. */
.crumb-row { margin-top: .3rem; }
[data-app="kids"] .crumb-row { display: flex; align-items: baseline; gap: .55rem; flex-wrap: wrap; margin: 0; }
[data-app="kids"] .crumb-row a { font-size: .9rem; flex: none; }
[data-app="kids"] .crumb-row h1 { margin: .1rem 0 .3rem; }

/* The line a `.cta` sits on when it is the only thing on it. */
.cta-row { margin: .35rem 0 .55rem; }
.heading-row { margin: 1.5rem 0 .5rem; }
.heading-row h2 { margin: 0; }

/* Who is speaking, before their name. Three words and no color doing the work
   on its own — the word is the whole signal, and it is the first thing on the
   line because that is the order a kid reads it in. */
.who-tag {
  font-size: .68rem; letter-spacing: .04em; text-transform: uppercase; font-weight: 700;
  border-radius: var(--r-sm); padding: .1rem .4rem; white-space: nowrap; flex: none;
  color: var(--brand-ink); background: var(--quiet);
}
.who-you { background: var(--accent); }
.who-friend { background: #1f7a4d; }
.who-grownup { background: #6b4aa8; }

.composer textarea { resize: vertical; }
.composer p { margin: .5rem 0 0; }

/* MCCE Kids writes on one line with Send beside it, the way every chat app a
   kid has seen does. A two-row composer spends about a hundred and fifty
   pixels of a six-hundred pixel screen, which is two messages. The box still
   drags taller for a long one. */
[data-app="kids"] .composer { display: flex; gap: .5rem; align-items: flex-start; margin: .35rem 0 .2rem; }
[data-app="kids"] .composer textarea { flex: 1 1 auto; padding: .5rem .6rem; }
[data-app="kids"] .composer p { flex: none; margin: 0; }
[data-app="kids"] .composer button { min-height: 2.8rem; padding: .5rem 1rem; font-size: .95rem; }

/* Newest first, directly under the composer. A phone lands on what was just
   said rather than on a month of scrollback. */
.messages { display: flex; flex-direction: column-reverse; }
.msg { border-top: 1px solid var(--line); padding: .8rem 0; }
/* Density, not a different design: the same byline, body and controls, spending
   what a phone actually has. What a kid can do to a message rides on the end of
   the byline rather than on a row of its own — a row of its own costs more
   height per message than the message does. */
[data-app="kids"] .msg { padding: .4rem 0; }
[data-app="kids"] .msg .body { margin: .15rem 0 .1rem; line-height: 1.4; }
[data-app="kids"] .msg .row { gap: .4rem; }
[data-app="kids"] .msg-tools { margin-left: auto; gap: .5rem; }
/* When it was said. The parents app reads a column of these and wants the
   mono figures lined up; a kid reads one line and wants the room. Proportional
   digits in the text face, and it never breaks in the middle of itself. */
[data-app="kids"] .msg .when {
  font-size: .8rem; font-variant-numeric: normal; white-space: nowrap;
}
/* Which day the messages under it were said on. Said once for the whole day,
   which is what keeps the day off every byline. */
[data-app="kids"] .daymark {
  margin: .7rem 0 .1rem; font-size: .75rem; font-weight: 700; letter-spacing: .07em;
  text-transform: uppercase; color: var(--quiet);
}
/* Still boxes, and still the size of a target: the parents app turns these
   into words and the reason it does not here is that at six a target that does
   not look like one is not one. Compact, and it drops to a line of its own
   when the byline has no room — a wrapped row is cheaper than a row every
   message pays for. */
[data-app="kids"] .msg-tools button.small { min-height: 2.1rem; padding: .15rem .5rem; font-size: .85rem; }
.msg.pending { border-left: 3px solid var(--warn); padding-left: .7rem; }
.body { white-space: pre-wrap; overflow-wrap: anywhere; }
.msg .body { margin: .3rem 0 .4rem; }
.msg .row { gap: .5rem; }

/* The house rules, over whatever screen asked for them. */
.sheet { position: fixed; inset: 0; background: #0009; display: flex; align-items: center; justify-content: center; padding: 1rem; z-index: 10; }
.sheet-inner {
  background: var(--surface); border-radius: var(--radius); padding: 1.25rem 1.4rem;
  max-width: 34rem; max-height: 85vh; overflow: auto;
}

/* ---- The badge picker (§6) ---- */

.badgepick { display: grid; grid-template-columns: repeat(auto-fill, minmax(2.9rem, 1fr)); gap: .3rem; margin: .5rem 0; }
.badgepick button {
  min-height: 2.9rem; padding: 0; font-size: 1.35rem; line-height: 1;
  background: var(--surface); border: 1px solid var(--line); color: inherit;
  border-radius: var(--r-sm);
}
.badgepick button.on { border-color: var(--accent); box-shadow: inset 0 0 0 2px var(--accent); }

/* ---- The calendar (§8) ---- */
/* D24 retires the single-hue rule: each event kind gets its own hue, because
   the calendar is the most-used parent screen and four kinds of thing have to
   be told apart at a glance. Hue is never the only carrier — the chip says the
   word, a canceled day is struck through, and the date is in mono. These sit
   outside the app blocks on purpose: an event kind means the same thing in
   both apps and under every kid theme. */
.kind {
  font-size: .7rem; letter-spacing: .02em; text-transform: uppercase; font-weight: 600;
  border: 1px solid currentColor; border-radius: var(--r-sm); padding: .1rem .4rem;
  white-space: nowrap; flex: none;
}
.kind-class_day { color: #1f5d8f; }
.kind-meetup { color: #14602c; }
.kind-field_trip { color: #a8265c; }
.kind-fundraiser { color: #8a4a12; }
.kind-service { color: #5b3d8f; }
.kind-milestone { color: #0e6a72; }
.kind-other { color: var(--quiet); }

/* A day that has already happened, and who is leading one. Both are facts about
   the co-op's day, so both keep the event-kind shape. */
.kind-past { color: var(--quiet); }
.kind-teacher { color: #5b3d8f; }

/* A parent's answer, which is not a fact about the day — it is about the kid.
   Deliberately a different object from an event kind: rounded, filled and in
   the sentence case a child reads, rather than the small uppercase label the
   parents app uses for density. Color is never the carrier; each badge says
   its word, and `Ask your grown-up` is deliberately the quiet one, because it
   is a question rather than a refusal. Outside the app blocks with the event
   kinds: an answer means the same thing under every kid theme. */
.rsvp {
  font-size: .8rem; font-weight: 700; line-height: 1.35;
  border-radius: 999px; padding: .2rem .65rem;
  white-space: nowrap; flex: none;
}
.rsvp-going { color: #0d4a22; background: #d6efdd; }
.rsvp-maybe { color: #6b4409; background: #fbead0; }
.rsvp-no { color: #7a1c15; background: #f6dcd8; }
.rsvp-ask { color: #4a4436; background: #e9e3d6; }

/* ---- MCCE Kids: an event card ---- */
/* Two columns and nothing that wraps between them. What it is on the left, when
   and whether you are going on the right, the first line of each sitting on the
   same baseline — a card whose right-hand column floats half a line above the
   title is a card a kid reads twice. `align-items: baseline` is what does that
   and it is the whole reason this is a grid rather than a flex row. */
.event { display: grid; grid-template-columns: 1fr auto; column-gap: .9rem; align-items: baseline; }
.ev-main { min-width: 0; }
.ev-main > p { margin: .45rem 0 0; }
.ev-title { font-size: 1.05rem; }
.ev-when { display: flex; flex-direction: column; align-items: flex-end; gap: .3rem; }
.ev-when .mono { font-size: .95rem; color: var(--quiet); font-variant-numeric: tabular-nums; }
/* The end time reads as the other half of a range rather than as a second
   event. The dash hangs outside the column so that the two times stay digit
   over digit — a range whose halves are one character out of line is the thing
   this column exists to avoid. */
.ev-when .to { position: relative; }
.ev-when .to::before { content: "–"; position: absolute; right: 100%; margin-right: .12rem; }
.event.struck .ev-title { text-decoration: line-through; }

/* The kind, as a strip down the left edge and a color learned once, rather than
   an uppercase label read every time. The key at the top of the screen spells
   each one out, so the color is never carrying the meaning alone. Same five
   hues the parents app puts in its chips: an event kind means the same thing in
   both apps and under every kid theme. */
.event { border-left: 7px solid var(--strip, var(--quiet)); padding-left: .85rem; }
.ev-class_day { --strip: #1f5d8f; }
.ev-meetup { --strip: #14602c; }
.ev-field_trip { --strip: #a8265c; }
.ev-fundraiser { --strip: #8a4a12; }
.ev-service { --strip: #5b3d8f; }
.ev-milestone { --strip: #0e6a72; }
.ev-other { --strip: #7b8497; }

.key { display: flex; flex-wrap: wrap; gap: .35rem .9rem; margin: .9rem 0 .25rem; }
.key-item { display: inline-flex; align-items: center; gap: .35rem; font-size: .82rem; color: var(--quiet); }
.swatch { width: .7rem; height: .7rem; border-radius: 3px; background: var(--strip, var(--quiet)); flex: none; }

/* Scoped to a card, because a text decoration cannot be turned off further
   down the tree: on a `.panel-row` the same selector would reach the quiet line
   under the title as well, and strike out the sentence that explains why the
   row is struck out. */
.card.struck > .row > strong, .card.struck > span:first-child { text-decoration: line-through; }
/* In a list the line goes on the title alone, the way the calendar's rows
   already do it. */
.panel-row.struck .panel-title { text-decoration: line-through; }
.day { margin: 1.5rem 0 .25rem; font-weight: 700; }

.form { border-left: 3px solid var(--line); padding-left: .9rem; margin: .5rem 0 1rem; }
.field { margin: .5rem 0; }
.field > label { display: block; font-size: .8rem; color: var(--quiet); }
.field p { margin: .15rem 0 0; }
.field-row { display: flex; gap: .75rem; flex-wrap: wrap; }
.field-row > .field { flex: 1 1 9rem; }
label.check { display: flex; gap: .4rem; align-items: baseline; font-size: .95rem; font-weight: 400; }
label.check input { flex: none; }
.checks { display: flex; flex-direction: column; gap: .25rem; }
details summary { cursor: pointer; font-size: .9rem; color: var(--quiet); padding: .3rem 0; }

/* ---- MCCE Parents: the index vocabulary ---- */
/*
   The grown-up screens are lists, not decks of cards. A parent opening this
   app is scanning for the one row that changed since yesterday, and a column
   of separated white cards makes every row equally loud — which is the same as
   making none of them loud. So the surface is one panel with hairlines in it,
   the way a mail client or a forum has looked for thirty years, and the only
   things allowed to interrupt it are a banner that names what is actually new
   and a count on the row it is in.

   Everything here is deliberately app-neutral in its colors — it is all
   tokens — but it is tuned to the parents app's density. MCCE Kids keeps
   `.channel-row` and its big targets: a six-year-old aims at a card, and an
   adult reads a list. */

/* The segmented strip. One object with a seam down it, not two buttons with a
   gap: a gap reads as two controls, and these are two halves of one choice.
   The seam is a border on the second child rather than a gap, so the strip
   stays solid at every width. */
.seg {
  display: flex; margin: 1.1rem 0 1rem;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); overflow: hidden;
}
.seg button {
  flex: 1; min-height: 2.6rem; padding: .5rem .75rem;
  display: flex; align-items: center; justify-content: center; gap: .45rem;
  color: var(--quiet); background: transparent; border: 0; border-radius: 0;
  font-weight: 600; font-size: .95rem;
}
.seg button + button { border-left: 1px solid var(--line); }
.seg button.on { color: var(--brand-ink); background: var(--brand); }
.seg button:not(.on):hover { color: var(--ink); background: color-mix(in srgb, var(--ink) 4%, var(--surface)); }

/* Eight choices do not fit on one strip at phone width. The wrapping variant is
   the same object let onto a second line: the seam becomes a 1px gap over the
   strip's own line color, so a wrapped row still reads as one control rather
   than as a scattering of buttons. */
.seg.wrap { flex-wrap: wrap; gap: 1px; background: var(--line); }
.seg.wrap button { flex: 1 1 6.5rem; border-left: 0; background: var(--surface); }
.seg.wrap button.on { background: var(--brand); }

/* A panel is a run of rows on one surface. The hairline belongs to the row
   below rather than the row above, so the last row has no trailing rule and
   the panel closes on its own border. */
.panel {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); overflow: hidden; margin: .85rem 0;
}
.panel > * + * { border-top: 1px solid var(--line); }
.panel-empty { padding: 1rem; color: var(--quiet); margin: 0; }

/* A panel that is one thing to do rather than a list to read, on a screen with
   several panels on it. The border is the month grid's `today` — same weight,
   same accent — so the one place in the app that already says "this one, now"
   says it the same way twice. Sparingly: two of these on a screen is neither. */
.panel.attention { border: 2px solid var(--accent); }

/* A row is the whole width and the whole tap target, whether it is rendered as
   a button or a link. Two lines: what it is, then the quiet line that says
   where it stands. */
.panel-row {
  display: flex; align-items: center; gap: .75rem; width: 100%; text-align: left;
  padding: .8rem 1rem; min-height: 0;
  font: inherit; font-weight: 400; color: inherit; text-decoration: none;
  background: transparent; border: 0; border-radius: 0; cursor: pointer;
}
.panel-row:hover, .panel-row:focus-visible { background: color-mix(in srgb, var(--accent) 6%, var(--surface)); }
.panel-row .grow { min-width: 0; }
.panel-title { display: flex; align-items: baseline; gap: .45rem; flex-wrap: wrap; font-weight: 600; }
.panel-sub { display: block; color: var(--quiet); font-size: .85rem; margin-top: .1rem; }
.panel-sub .mono { font-variant-numeric: tabular-nums; }

/* The row that makes a new thing. Accent-colored and led by a glyph so it
   reads as an action inside a list of nouns, and pinned to the top of the
   panel because that is where somebody looks for it. */
.panel-add { color: var(--accent); font-weight: 600; }
.panel-add::before { content: "+"; font-weight: 700; font-size: 1.1em; line-height: 1; }

/* Unread, on the row it belongs to. A filled pill rather than the `.badge`
   the kids app uses beside a tab: in a list of forty rows a gray number is
   scenery. */
.count {
  flex: none; min-width: 1.5rem; padding: .05rem .4rem; border-radius: 999px;
  background: var(--accent); color: var(--brand-ink);
  font-size: .75rem; font-weight: 700; text-align: center;
  font-family: var(--mono); font-variant-numeric: tabular-nums;
}

/* Two numbers on one row, each under the word that says which it is. A points
   row prints what a kid earned beside what they have left, and the pair is only
   readable with both words on it. Stacked rather than inline so two of them fit
   beside a name on a phone. */
.stat { flex: none; display: flex; flex-direction: column; align-items: center; gap: .15rem; max-width: 4.5rem; }
.stat-k {
  font-size: .66rem; letter-spacing: .05em; text-transform: uppercase; font-weight: 700;
  color: var(--quiet); text-align: center; line-height: 1.15;
}

/* A neutral label for what a row is — General, Class, Club. Same small
   uppercase shape as `.kind` on the calendar, in the quiet ink rather than a
   hue of its own: an event kind is a fact a parent acts on and this is a fact
   that only sorts. */
.tag {
  flex: none; font-size: .66rem; letter-spacing: .05em; text-transform: uppercase;
  font-weight: 700; color: var(--quiet);
  border: 1px solid var(--line); border-radius: var(--r-sm); padding: .05rem .35rem;
}

/* A fact about the row, behind its name on the same line: what somebody is,
   who teaches a class, when a slot is. Quiet ink at normal weight, so a report
   line reads "Okafor · Ada — Kid, 4th" as one sentence rather than as a name
   with two badges stuck to it. `.tag` above stays for the things an admin is
   scanning *for* — an alarm earns a box, a label does not. */
.panel-what { font-weight: 400; color: var(--quiet); font-size: .85rem; }

/* A tag that is a state rather than a name. The quiet gray above says what a
   row is — Teaching, Canceled — and this says the row still wants something
   doing, which is the one thing on the sheet an admin is scanning for. */
.tag.warn { color: var(--warn); border-color: color-mix(in srgb, var(--warn) 45%, var(--line)); }

/* What is actually new, above everything. It earns the interruption by naming
   the reason on every row — "Your child posted" is worth a look and "there are
   messages" is not — so the panel is headed rather than counted, and it is not
   rendered at all when there is nothing in it. */
.banner { border: 1px solid var(--accent); border-radius: var(--radius); overflow: hidden; margin: 1rem 0; }
.banner > h2 {
  margin: 0; padding: .45rem 1rem; background: var(--accent); color: var(--brand-ink);
  font-size: .78rem; letter-spacing: .08em; text-transform: uppercase;
}
.banner .panel { margin: 0; border: 0; border-radius: 0; }
.banner .panel-sub { color: var(--accent); font-weight: 600; }

/* The bar over a list: what it is on the left, the one control on the right.
   The heading takes the slack whichever level it is, so the control sits at the
   right edge rather than against the last word of the title. */
.bar { display: flex; align-items: baseline; gap: .75rem; margin: 1.75rem 0 .35rem; flex-wrap: wrap; }
.bar h1, .bar h2 { margin: 0; flex: 1 1 auto; }
main > .bar:first-child { margin-top: .25rem; }

/* A modal, over the screen that opened it. Same object as the house rules
   sheet, with a form in it instead of prose. */
.sheet-inner h2:first-child { margin-top: 0; }
.sheet-actions { display: flex; gap: .5rem; margin-top: 1.1rem; }

/* Row tools, in the parents app only. A bordered button beside every line of a
   conversation gives Edit and Report the same weight as Send, and a column of
   forty of them is a screen made of chrome — the same is true of Archive beside
   every term and every class. So on a message, on a chip and at the right edge
   of a list row, the action is a word rather than a box. MCCE Kids keeps the
   real buttons: at six, a target that does not look like a target is not one. */
[data-app="parents"] .msg .row.quiet button.small.plain {
  min-height: 0; padding: 0; border: 0; background: none;
  color: var(--quiet); font-size: .82rem; font-weight: 500; text-decoration: underline;
  text-underline-offset: .18em;
}
[data-app="parents"] .msg .row.quiet { gap: .9rem; margin-top: .15rem; }
[data-app="parents"] .msg .row.quiet button.small.plain:hover { color: var(--accent); }

/* The one destructive-looking control on a screen, kept away from the
   conversation it acts on and separated by a rule so it cannot be mistaken for
   part of the last message. */
.footnote { margin-top: 2.5rem; padding-top: .9rem; border-top: 1px solid var(--line); }
.footnote button { color: var(--bad); border-color: var(--line); background: var(--surface); font-weight: 500; }

/* ---- MCCE Parents: a club in the list (§6) ---- */
/* A club row carries more than a channel row does — a badge, a blurb, which of
   this family's children are in it — so it is a block inside the panel rather
   than a single line, and it is not a tap target: everything on it that does
   something says what it does. */
.clubs > .club { padding: .9rem 1rem; }
.club > .row:first-child { gap: .45rem; }
.club .body { margin: .35rem 0 .15rem; color: var(--quiet); font-size: .9rem; }
.club-family { margin: .55rem 0 .35rem; }
/* The select and the button beside it are one control in two halves, so they
   are the same height. A dropdown two-thirds the height of the button next to
   it reads as an afterthought. */
.club-family select { width: auto; min-width: 9rem; min-height: 2.4rem; padding: .35rem .5rem; }
.club-family .row { gap: .5rem; }
/* A child's name and the way to take them back out, kept together so a family
   with three of them does not read as a row of names then a row of buttons. */
.chip-row {
  gap: .4rem; background: var(--ground); border-radius: 999px; padding: .1rem .6rem .1rem .7rem;
}
.chip-row strong { font-size: .9rem; }
[data-app="parents"] .club .row.quiet button.small.plain,
[data-app="parents"] .chip-row button.small.plain,
[data-app="parents"] .panel-row > button.small.plain {
  min-height: 0; padding: 0; border: 0; background: none;
  color: var(--quiet); font-size: .82rem; font-weight: 500; text-decoration: underline;
  text-underline-offset: .18em;
}
[data-app="parents"] .club .row.quiet { gap: .9rem; margin-top: .3rem; }

/* A control that is a word rather than a box, inside a line of text. Used where
   the action belongs to the sentence it is in — "Enrolled: None  Enroll now" —
   and a bordered button would break the line in half. */
button.linkish {
  display: inline; font: inherit; font-size: inherit; font-weight: 600;
  min-height: 0; padding: 0; border: 0; background: none; cursor: pointer;
  color: var(--accent); text-decoration: underline; text-underline-offset: .18em;
}

/* A group of rows under a heading that opens. The marker and the name sit on
   one line with the count at the right edge, and the panel inside carries the
   rows, so an open group looks like the list it is rather than like a nested
   card. */
details.subject { margin: .9rem 0; }
details.subject > summary {
  display: flex; align-items: baseline; gap: .6rem;
  font-size: 1rem; font-weight: 600; color: var(--ink); padding: .5rem 0;
}
details.subject > summary::-webkit-details-marker { display: none; }
details.subject > summary::marker { content: ""; }
details.subject > summary::before {
  content: "▸"; color: var(--quiet); font-size: .8em; flex: none;
}
details.subject[open] > summary::before { content: "▾"; }
details.subject > .panel { margin-top: 0; }

/* ---- MCCE Parents: the calendar (§8) ---- */
/*
   The same list vocabulary as the index screens, with the event kind carried on
   it. A day is a `.panel` and an event is a row in it that opens: closed, it is
   the title, the time, and what this family answered, because those are the
   three things a parent opens the calendar to check. Opening it is where the
   co-op's answers, the classes on the day and the day's packet live.

   The kind is the `.ev-*` strip down the row's left edge and the key at the top
   of the screen, the same shape and the same tokens MCCE Kids uses, because an
   event kind means the same thing in both apps. Nothing here defines a hue of
   its own, and no row repeats the kind in words: down a month those words are a
   column of scenery. */

/* The month picker sits in the bar beside the heading and is a control rather
   than a field: full width would make paging the calendar look like filling in
   a form. */
.cal-months { width: auto; min-width: 10rem; min-height: 2.6rem; padding: .35rem .5rem; flex: none; }

/* The two views, named on the control that swaps them (§16). The one being read
   is the solid button and the other is plain, so the pair says which is on
   without a third thing on the bar saying it in words. */
.cal-views { display: flex; gap: .35rem; flex: none; }

/* The Month view (P12). Seven columns at every width: a month that reflows to
   three columns has stopped being a month. The cells shrink instead, and a
   cell's whole job on a phone is the date, whether anything is on it, and of
   what kind — the day modal is where the answering happens. */
.cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: .25rem; margin: .9rem 0 .25rem; }

.cal-head {
  padding: .2rem 0; text-align: center;
  font-size: .7rem; font-weight: 600; letter-spacing: .04em; text-transform: uppercase;
  color: var(--quiet);
}

.cal-cell {
  display: flex; flex-direction: column; align-items: stretch; gap: .25rem;
  min-height: 3.2rem; padding: .3rem;
  border: 1px solid var(--line); border-radius: var(--r-sm);
  background: var(--surface);
}

button.cal-cell { width: 100%; text-align: left; cursor: pointer; font: inherit; }
button.cal-cell:hover { border-color: var(--accent); }

/* The days either side of the month. They hold the grid's shape and are not
   dates: nothing is drawn in them and nothing in them is a target. */
.cal-cell.blank { border-color: transparent; background: none; min-height: 0; }

.cal-cell.today { border: 2px solid var(--accent); padding: calc(.3rem - 1px); }
.cal-date { font-size: .78rem; color: var(--quiet); font-variant-numeric: tabular-nums; }
.cal-cell.today .cal-date { color: var(--accent); font-weight: 700; }

/* One bar per kind, stacked rather than side by side: three kinds split across a
   phone's column width are three slivers, and a sliver of color is not a
   color anybody recognizes. */
.cal-bands { display: flex; flex-direction: column; gap: .15rem; }
.cal-band { height: .34rem; border-radius: 2px; background: var(--strip, var(--quiet)); }

/* The day the grid opens. It is the list's own rows, so it is as tall as the day
   is busy and scrolls inside the sheet rather than pushing it off the screen. */
.day-sheet .sheet-inner { max-height: 86vh; overflow-y: auto; }

/* An event, closed. The strip is the kind; the row inside it is the same
   `.panel-row` every other parents screen uses, so a calendar row and a channel
   row are the same object with different contents. */
.cal-row { border-left: 5px solid var(--strip, var(--quiet)); }
.cal-row > summary { list-style: none; }
.cal-row > summary::-webkit-details-marker { display: none; }
.cal-row > summary::marker { content: ""; }
/* The chevron says the row opens. It is after the time rather than before the
   title, so the left edge of every row is the colored strip and then the kind,
   at the same offset all the way down the day. */
.cal-row > summary::after { content: "▸"; color: var(--quiet); font-size: .8em; flex: none; }
.cal-row[open] > summary::after { content: "▾"; }
.cal-row.struck .panel-title { text-decoration: line-through; }
.cal-when { flex: none; color: var(--quiet); font-size: .85rem; font-variant-numeric: tabular-nums; white-space: nowrap; }

/* What is inside an open row, indented to the row's text rather than to the
   panel, and ruled off from the summary so an open row still reads as one
   thing. */
.cal-body { padding: .3rem 1rem 1rem; border-top: 1px solid var(--line); }
.cal-body > p { margin: .45rem 0; }

/* A section inside a row — RSVPs, Resources, the roll. Named and countable, so
   a day with a packet on it says so before it is opened. */
details.cal-sec > summary {
  display: flex; align-items: center; gap: .5rem;
  color: var(--ink); font-size: .9rem; font-weight: 600; padding: .45rem 0;
}
details.cal-sec > summary::-webkit-details-marker { display: none; }
details.cal-sec > summary::marker { content: ""; }
details.cal-sec > summary::before { content: "▸"; color: var(--quiet); font-size: .8em; flex: none; }
details.cal-sec[open] > summary::before { content: "▾"; }
details.cal-sec .count { margin-left: -.15rem; }

/* A person and their answer, or a child and the class they are going into. One
   shape for both, because both are "pick a thing for one of your people" — and
   the select and the button beside it are one control in two halves, so they
   are the same height. */
.cal-answer { display: flex; align-items: center; gap: .6rem; padding: .25rem 0; }
.cal-answer select { width: auto; min-width: 8.5rem; min-height: 2.4rem; padding: .35rem .5rem; }
.cal-answer .grow { min-width: 0; }

/* A class meeting on this day. Ruled off rather than boxed: it is part of the
   day, not a card sitting inside one. */
.cal-class { border-top: 1px solid var(--line); padding-top: .6rem; margin-top: .7rem; }
.cal-class > p { margin: .3rem 0 .5rem; }

/* A row that states a fact and carries its own controls, rather than being one
   tap itself. Same shape as `.panel-row`, without the cursor and the hover that
   promise the whole row does something — on a list where only the button at the
   right edge acts, that promise is a row a person taps and nothing happens. */
.panel-row.static { cursor: default; }
.panel-row.static:hover, .panel-row.static:focus-within { background: transparent; }

/* The row a list is currently showing the contents of — the term whose classes
   and days fill the other buckets. A tinted ground and a bar down the left
   edge, so which one is selected survives a phone screenshot; the row also says
   so in words, because a tint is not a label. */
.panel-row.on { background: color-mix(in srgb, var(--accent) 8%, var(--surface)); box-shadow: inset 3px 0 0 var(--accent); }

/* The one control in a `.bar`, beside the heading. Same object as the
   calendar's month picker: a control sitting on a line of its own width, not a
   form field stretched across the screen. */
.barpick { width: auto; min-width: 10rem; min-height: 2.6rem; padding: .35rem .5rem; flex: none; }

/* ---- MCCE Admin: the console chrome, its tiles, and the mode switch ---- */

/* The band is squarer than the parents one and carries a rule under it, so the
   difference survives a phone screenshot cropped to the top two centimeters. */
.brandbar.admin { border-bottom: 3px solid var(--accent); }
/* The console keeps a row of sections in its bar, where the parents app has a
   drawer: an admin is at a desk with eleven screens in front of them, and a
   panel they open to reach the next one is a panel they open eleven times. It
   wraps rather than scrolls, because a section scrolled off the end of a row
   is a section nobody presses. */
.brandbar.admin nav {
  display: flex; flex-wrap: wrap; gap: .25rem; row-gap: .3rem;
  max-width: 44rem; margin: 0 auto; padding: 0 var(--pad) .55rem;
}
.brandbar.admin nav a {
  padding: .35rem .7rem; border-radius: 999px; font-size: .9rem;
  text-decoration: none; white-space: nowrap;
}
.brandbar.admin nav a.on { background: var(--accent); color: #fff; font-weight: 600; }
/* Where in the console this is, for a screen deep enough to need saying. */
.brandbar-where {
  max-width: 44rem; margin: 0 auto; padding: 0 var(--pad) .6rem;
  font-size: .85rem; color: #ffffffb0;
}

/* The group a run of tiles sits in. Smaller and quieter than a tile's own
   name on purpose: the heading is how an admin finds the right corner of the
   console, and the tile is the thing they press. */
.tiles-head {
  margin: 1.5rem 0 -.5rem; font-size: .85rem; font-weight: 600;
  letter-spacing: .04em; text-transform: uppercase; color: var(--quiet);
}

/* The console's own tiles. Wider than the kids door's — these carry a sentence
   each, because "Moderation" and "Club ideas" are not self-explanatory to
   somebody who took the job last term. */
.tiles.console { grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr)); }
.tiles.console .tile {
  align-items: stretch; text-align: left; gap: .3rem;
  padding: 1rem; text-decoration: none; border-width: 1px;
}
.tiles.console .tile-name { display: flex; align-items: baseline; gap: .5rem; font-size: 1.02rem; }
.tiles.console .tile-note { line-height: 1.4; }
/* How much is waiting. Zero is not printed: an empty queue should read as an
   empty queue, not as a badge saying nothing is wrong. */
.tile-count {
  margin-left: auto; flex: none; font-size: .78rem; font-weight: 700;
  padding: .1rem .45rem; border-radius: 999px;
  background: var(--accent); color: #fff; font-variant-numeric: tabular-nums;
}
.tile-count.calm { background: color-mix(in srgb, var(--ink) 8%, var(--surface)); color: var(--quiet); }

/* The switch on the parents screens the console cannot empty. Off, it is a
   quiet line under the heading; on, it is a rule the reader cannot miss,
   because "am I looking at what a parent looks at" has to be answerable
   without thinking about it. */
.adminmode {
  display: flex; gap: .6rem; align-items: center; flex-wrap: wrap;
  margin: .75rem 0 1rem; padding: .5rem .7rem;
  border: 1px dashed var(--line); border-radius: var(--radius);
  font-size: .85rem; color: var(--quiet); background: var(--surface);
}
.adminmode .grow { flex: 1 1 12rem; }
.adminmode.on {
  border: 1px solid #8f4f14; border-left-width: 5px; border-style: solid;
  background: #fdf3e6; color: #6d3d0f; font-weight: 600;
}
.adminmode button.small.plain { flex: none; }
.adminmode.on button.small.plain { color: #6d3d0f; border-color: #8f4f1440; }

/* A member's profile picture (D37), drawn in the directory and beside the
   switch that sets it. The slot is the same size whether or not there is a
   picture in it, so a family with one photo and one without does not render as
   two different rows. */
.avatar {
  flex: none; width: 44px; height: 44px; border-radius: 50%;
  background: color-mix(in srgb, var(--ink) 7%, var(--surface));
  border: 1px solid var(--line); object-fit: cover; display: block;
}
.avatar.lg { width: 64px; height: 64px; }

/* A campaign against its goal (§16). The bar is what makes the number quick to
   read; the number is beside it, because a bar on its own is not an amount. */
.meter {
  height: .5rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--ink) 10%, var(--surface));
  overflow: hidden;
  margin: .5rem 0;
}
.meter > span { display: block; height: 100%; background: var(--accent); }

/* A piece of work with bytes in it (§12). The photograph is the submission, so
   it gets the card's full width and its own aspect ratio; a video is the same
   object with the browser's controls under it. Capped in height because a
   portrait photo from a phone is three screens tall otherwise, and a wall of
   them is a wall nobody scrolls to the end of. */
.work-shot {
  display: block; width: 100%; max-height: 22rem;
  margin: .6rem 0 .2rem; border-radius: var(--radius);
  background: color-mix(in srgb, var(--ink) 6%, var(--surface));
  object-fit: contain;
}

/* ---- Globetrotters (§11) ---- */

/* The passport is nine rows with a month down the gutter, and the gutter is the
   same object as the number down the left of a task row: a fixed, quiet column
   so the names beside it line up whatever is in them. */
.gt-month-name {
  flex: none; width: 3.2rem;
  color: var(--quiet); font-family: var(--mono); font-size: .85rem;
}
.gt-row { min-height: 3.4rem; }

/* The stamp. The one thing in either app that is allowed to look hand-made:
   it is the artifact a month produces, and a rectangle of body text is not an
   artifact. The tilt is derived from the stamp's id, never re-rolled, so it is
   crooked once rather than crooked differently each time the page repaints. */
.gt-stamp {
  display: inline-flex; flex-direction: column; align-items: center; gap: .05rem;
  padding: .35rem .7rem;
  border: 2px solid var(--accent); border-radius: var(--r-sm);
  color: var(--accent); background: transparent;
  transform: rotate(var(--rot, 0deg)) translate(var(--dx, 0), var(--dy, 0));
}
.gt-stamp-country { font-weight: 700; letter-spacing: .02em; text-transform: uppercase; font-size: .95rem; }
.gt-stamp-month, .gt-stamp-focus { font-size: .7rem; letter-spacing: .04em; }
.gt-stamp-large { padding: .8rem 1.4rem; margin: 1rem 0; }
.gt-stamp-large .gt-stamp-country { font-size: 1.5rem; }
.gt-stamp-large .gt-stamp-month, .gt-stamp-large .gt-stamp-focus { font-size: .85rem; }
/* The line the month chose for itself, under its stamp. */
.gt-headline { font-size: 1.05rem; margin: .2rem 0 .8rem; }

/* Setting a month up. Three steps, and which one is on is said in the list as
   well as in the heading below it. */
.gt-steps {
  display: flex; gap: .5rem; flex-wrap: wrap;
  list-style: none; padding: 0; margin: .5rem 0 1rem;
  font-size: .85rem; color: var(--quiet);
}
.gt-steps li::after { content: "›"; margin-left: .5rem; }
.gt-steps li:last-child::after { content: ""; }
.gt-steps li.on { color: var(--ink); font-weight: 700; }
.gt-steps li.done { color: var(--accent); }

/* A country is a card with a hook on it rather than a row with a name on it:
   the hook is what the choice is actually between. */
.gt-countries { display: grid; gap: .55rem; margin: .75rem 0; }
.gt-country {
  display: flex; flex-direction: column; align-items: flex-start; gap: .15rem;
  width: 100%; text-align: left; padding: .7rem .85rem;
  font: inherit; font-weight: 400; color: inherit;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
}
.gt-country:hover, .gt-country:focus-visible { border-color: var(--accent); }
.gt-hook { font-size: .95rem; }
.gt-hooks { margin: .5rem 0; padding-left: 1.1rem; }
.gt-hooks li { margin: .35rem 0; }
.gt-deal { margin: .75rem 0; }
.gt-samples { padding: 0 1rem .6rem; }

/* This week. One card up, and the prompt on it is read at arm's length by
   somebody standing over a workbook — so it gets the largest type in either
   app, and the title above it stays small. */
.gt-card { padding: 1rem; }
.gt-card-title { margin: .2rem 0 .1rem; font-weight: 600; }
.gt-prompt { margin: .1rem 0 .6rem; font-size: 1.35rem; line-height: 1.25; }
[data-app="kids"] .gt-prompt { font-size: 1.5rem; }
.gt-card[data-state="done"] { border-color: var(--good); }
.gt-card[data-state="progress"] { border-color: var(--accent); }

/* The rest of the week, as pips. Order is free — the pips are how a kid takes a
   different card, not a queue they have to work through in order. */
.gt-pips { display: flex; gap: .4rem; flex-wrap: wrap; list-style: none; padding: 0; margin: .6rem 0; }
.gt-pip {
  min-height: 2.4rem; min-width: 2.4rem; padding: .3rem;
  border-radius: 999px; border: 1px solid var(--line);
  background: var(--surface); color: var(--quiet); font-weight: 600;
}
.gt-pip[data-state="progress"] { border-color: var(--accent); color: var(--accent); }
.gt-pip[data-state="done"] { border-color: var(--good); background: var(--good); color: #fff; }
.gt-pip[aria-current="true"] { box-shadow: 0 0 0 2px var(--accent); }

/* A finished card in the month's list, marked on the row rather than only in
   the word beside it. */
.panel-row[data-state="done"] .panel-title { color: var(--quiet); }

/* ---- the module library editor (11f) ---- */

/* A row of small controls: the filter strip over a shelf, and the hook and
   affinity lines that are a field or two and a button. A field is its own line
   everywhere else in the app, which is why `input` is full width — here the row
   is the control, so the fields size to their `size` attribute and whichever one
   carries prose is the one that grows. */
.libr { display: flex; gap: .5rem; flex-wrap: wrap; align-items: center; margin: .6rem 0; }
.libr input:not([type="checkbox"]), .libr select { width: auto; min-height: 2.4rem; padding: .35rem .5rem; }
.libr .grow { flex: 1 1 8rem; min-width: 6rem; }
.libr button { flex: none; }
.libr label { margin: 0; }

/* The knobs of one printed form, which are mostly two-character numbers and
   tick boxes. Laid out as a wrapping grid so a layout with nine of them is one
   block rather than nine lines. */
.libknobs { display: flex; flex-wrap: wrap; gap: .25rem 1.25rem; }
.libknobs > * { margin: 0; }
.libknobs input:not([type="checkbox"]), .libknobs select { width: auto; min-height: 2.4rem; padding: .35rem .5rem; }
.libr p { margin: 0; }

/* A project type's week 4 sequence, read in order on the library editor's
   shelf. Numbered by the list, because `position` is what put them in this
   order and a printed number beside a counting list is two numbers that can
   disagree. */
.libknobs + p, ol.steps { margin-top: .4rem; }
ol.steps { padding-left: 1.4rem; }
ol.steps li + li { margin-top: .5rem; }


/* ---- MCCE Guests (§16) ---- */

/* The masthead: the co-op's mark and the app's name, and nothing to press. A
   guest has no session to sign out of and nowhere else to go from here. */
.guestbar { display: flex; gap: .7rem; align-items: center; max-width: 34rem; margin: 0 auto; padding: 1.1rem var(--pad) .2rem; }
.guestbar img { width: 3.4rem; height: 3.4rem; object-fit: contain; }
[data-app="guests"] main { max-width: 34rem; padding-bottom: 3rem; }
[data-app="guests"] .lede { color: var(--ink); font-size: 1.05rem; }

/* The event picker. Bigger than the same control in MCCE Parents on purpose:
   it is the one choice on the screen that has a wrong answer, and it is made
   by somebody holding a phone in one hand at a table. */
[data-app="guests"] select.big { font-size: 1.2rem; min-height: 3.4rem; padding: .8rem .7rem; border-radius: var(--r-md); }

/* One child: a first name, an age, and the button that takes the row off. The
   name takes the space and the age takes what a two-digit number needs. */
.guest-kid { display: flex; gap: .5rem; align-items: flex-end; margin: .5rem 0; }
.guest-kid .grow { flex: 1 1 8rem; min-width: 0; }
.guest-kid .age { flex: 0 0 5rem; }
.guest-kid label { margin: 0 0 .2rem; font-size: .8rem; color: var(--quiet); }
.guest-kid button { flex: none; }
.guest-add { margin-top: .85rem; }

/* First Time or Returning, as two tap targets rather than two dots. */
.guest-visit { display: flex; gap: .6rem; flex-wrap: wrap; margin: .35rem 0 0; }
.guest-visit label {
  flex: 1 1 8rem; margin: 0; display: flex; gap: .5rem; align-items: center; cursor: pointer;
  padding: .8rem .9rem; font-size: 1rem; font-weight: 500;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-md);
}
.guest-visit input { width: auto; flex: none; }
.guest-visit label:has(input:checked) { border-color: var(--brand); box-shadow: inset 0 0 0 1px var(--brand); }

/* The thank-you. It replaces the form rather than sitting under it: the guest
   is done, and the next person picks the phone up on a fresh screen. */
.guest-thanks { font-size: 1.15rem; line-height: 1.5; }
